~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-06 07:13:51 UTC
  • mto: This revision was merged to the branch mainline in revision 2071.
  • Revision ID: john@arbash-meinel.com-20061006071351-e3fdd47eed1c3e7e
lazy import revisionspec and errors for bzrlib.options

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
 
19
19
from bzrlib.lazy_import import lazy_import
20
20
lazy_import(globals(), """
 
21
from binascii import hexlify
 
22
from copy import deepcopy
21
23
import re
22
24
import time
 
25
import unittest
23
26
 
24
27
from bzrlib import (
25
28
    bzrdir,
26
29
    check,
27
 
    deprecated_graph,
 
30
    delta,
28
31
    errors,
29
 
    generate_ids,
30
32
    gpg,
31
33
    graph,
32
 
    lazy_regex,
 
34
    knit,
33
35
    lockable_files,
34
36
    lockdir,
35
37
    osutils,
36
 
    registry,
37
 
    remote,
38
38
    revision as _mod_revision,
39
39
    symbol_versioning,
40
40
    transactions,
41
41
    ui,
 
42
    weave,
 
43
    weavefile,
 
44
    xml5,
 
45
    xml6,
 
46
    )
 
47
from bzrlib.osutils import (
 
48
    rand_bytes,
 
49
    compact_date, 
 
50
    local_time_offset,
42
51
    )
43
52
from bzrlib.revisiontree import RevisionTree
44
53
from bzrlib.store.versioned import VersionedFileStore
45
54
from bzrlib.store.text import TextStore
46
55
from bzrlib.testament import Testament
47
 
 
48
56
""")
49
57
 
50
58
from bzrlib.decorators import needs_read_lock, needs_write_lock
61
69
_deprecation_warning_done = False
62
70
 
63
71
 
64
 
######################################################################
65
 
# Repositories
66
 
 
67
72
class Repository(object):
68
73
    """Repository holding history for one or more branches.
69
74
 
76
81
    remote) disk.
77
82
    """
78
83
 
79
 
    _file_ids_altered_regex = lazy_regex.lazy_compile(
80
 
        r'file_id="(?P<file_id>[^"]+)"'
81
 
        r'.*revision="(?P<revision_id>[^"]+)"'
82
 
        )
83
 
 
84
84
    @needs_write_lock
85
 
    def add_inventory(self, revision_id, inv, parents):
86
 
        """Add the inventory inv to the repository as revision_id.
 
85
    def add_inventory(self, revid, inv, parents):
 
86
        """Add the inventory inv to the repository as revid.
87
87
        
88
 
        :param parents: The revision ids of the parents that revision_id
 
88
        :param parents: The revision ids of the parents that revid
89
89
                        is known to have and are in the repository already.
90
90
 
91
91
        returns the sha1 of the serialized inventory.
92
92
        """
93
 
        revision_id = osutils.safe_revision_id(revision_id)
94
 
        _mod_revision.check_not_reserved_id(revision_id)
95
 
        assert inv.revision_id is None or inv.revision_id == revision_id, \
 
93
        assert inv.revision_id is None or inv.revision_id == revid, \
96
94
            "Mismatch between inventory revision" \
97
 
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
 
95
            " id and insertion revid (%r, %r)" % (inv.revision_id, revid)
98
96
        assert inv.root is not None
99
97
        inv_text = self.serialise_inventory(inv)
100
98
        inv_sha1 = osutils.sha_string(inv_text)
101
99
        inv_vf = self.control_weaves.get_weave('inventory',
102
100
                                               self.get_transaction())
103
 
        self._inventory_add_lines(inv_vf, revision_id, parents,
104
 
                                  osutils.split_lines(inv_text))
 
101
        self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
105
102
        return inv_sha1
106
103
 
107
 
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
 
104
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
108
105
        final_parents = []
109
106
        for parent in parents:
110
107
            if parent in inv_vf:
111
108
                final_parents.append(parent)
112
109
 
113
 
        inv_vf.add_lines(revision_id, final_parents, lines)
 
110
        inv_vf.add_lines(revid, final_parents, lines)
114
111
 
115
112
    @needs_write_lock
116
 
    def add_revision(self, revision_id, rev, inv=None, config=None):
117
 
        """Add rev to the revision store as revision_id.
 
113
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
114
        """Add rev to the revision store as rev_id.
118
115
 
119
 
        :param revision_id: the revision id to use.
 
116
        :param rev_id: the revision id to use.
120
117
        :param rev: The revision object.
121
118
        :param inv: The inventory for the revision. if None, it will be looked
122
119
                    up in the inventory storer
124
121
                       If supplied its signature_needed method will be used
125
122
                       to determine if a signature should be made.
126
123
        """
127
 
        revision_id = osutils.safe_revision_id(revision_id)
128
 
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
129
 
        #       rev.parent_ids?
130
 
        _mod_revision.check_not_reserved_id(revision_id)
131
124
        if config is not None and config.signature_needed():
132
125
            if inv is None:
133
 
                inv = self.get_inventory(revision_id)
 
126
                inv = self.get_inventory(rev_id)
134
127
            plaintext = Testament(rev, inv).as_short_text()
135
128
            self.store_revision_signature(
136
 
                gpg.GPGStrategy(config), plaintext, revision_id)
137
 
        if not revision_id in self.get_inventory_weave():
 
129
                gpg.GPGStrategy(config), plaintext, rev_id)
 
130
        if not rev_id in self.get_inventory_weave():
138
131
            if inv is None:
139
 
                raise errors.WeaveRevisionNotPresent(revision_id,
 
132
                raise errors.WeaveRevisionNotPresent(rev_id,
140
133
                                                     self.get_inventory_weave())
141
134
            else:
142
135
                # yes, this is not suitable for adding with ghosts.
143
 
                self.add_inventory(revision_id, inv, rev.parent_ids)
 
136
                self.add_inventory(rev_id, inv, rev.parent_ids)
144
137
        self._revision_store.add_revision(rev, self.get_transaction())
145
138
 
146
139
    @needs_read_lock
168
161
        if self._revision_store.text_store.listable():
169
162
            return self._revision_store.all_revision_ids(self.get_transaction())
170
163
        result = self._all_possible_ids()
171
 
        # TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
172
 
        #       ids. (It should, since _revision_store's API should change to
173
 
        #       return utf8 revision_ids)
174
164
        return self._eliminate_revisions_not_present(result)
175
165
 
176
166
    def break_lock(self):
224
214
        # TODO: make sure to construct the right store classes, etc, depending
225
215
        # on whether escaping is required.
226
216
        self._warn_if_deprecated()
 
217
        self._serializer = xml5.serializer_v5
227
218
 
228
219
    def __repr__(self):
229
220
        return '%s(%r)' % (self.__class__.__name__, 
232
223
    def is_locked(self):
233
224
        return self.control_files.is_locked()
234
225
 
235
 
    def lock_write(self, token=None):
236
 
        """Lock this repository for writing.
237
 
        
238
 
        :param token: if this is already locked, then lock_write will fail
239
 
            unless the token matches the existing lock.
240
 
        :returns: a token if this instance supports tokens, otherwise None.
241
 
        :raises TokenLockingNotSupported: when a token is given but this
242
 
            instance doesn't support using token locks.
243
 
        :raises MismatchedToken: if the specified token doesn't match the token
244
 
            of the existing lock.
245
 
 
246
 
        A token should be passed in if you know that you have locked the object
247
 
        some other way, and need to synchronise this object's state with that
248
 
        fact.
249
 
 
250
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
251
 
        """
252
 
        return self.control_files.lock_write(token=token)
 
226
    def lock_write(self):
 
227
        self.control_files.lock_write()
253
228
 
254
229
    def lock_read(self):
255
230
        self.control_files.lock_read()
257
232
    def get_physical_lock_status(self):
258
233
        return self.control_files.get_physical_lock_status()
259
234
 
260
 
    def leave_lock_in_place(self):
261
 
        """Tell this repository not to release the physical lock when this
262
 
        object is unlocked.
263
 
        
264
 
        If lock_write doesn't return a token, then this method is not supported.
265
 
        """
266
 
        self.control_files.leave_in_place()
267
 
 
268
 
    def dont_leave_lock_in_place(self):
269
 
        """Tell this repository to release the physical lock when this
270
 
        object is unlocked, even if it didn't originally acquire it.
271
 
 
272
 
        If lock_write doesn't return a token, then this method is not supported.
273
 
        """
274
 
        self.control_files.dont_leave_in_place()
275
 
 
276
 
    @needs_read_lock
277
 
    def gather_stats(self, revid=None, committers=None):
278
 
        """Gather statistics from a revision id.
279
 
 
280
 
        :param revid: The revision id to gather statistics from, if None, then
281
 
            no revision specific statistics are gathered.
282
 
        :param committers: Optional parameter controlling whether to grab
283
 
            a count of committers from the revision specific statistics.
284
 
        :return: A dictionary of statistics. Currently this contains:
285
 
            committers: The number of committers if requested.
286
 
            firstrev: A tuple with timestamp, timezone for the penultimate left
287
 
                most ancestor of revid, if revid is not the NULL_REVISION.
288
 
            latestrev: A tuple with timestamp, timezone for revid, if revid is
289
 
                not the NULL_REVISION.
290
 
            revisions: The total revision count in the repository.
291
 
            size: An estimate disk size of the repository in bytes.
292
 
        """
293
 
        result = {}
294
 
        if revid and committers:
295
 
            result['committers'] = 0
296
 
        if revid and revid != _mod_revision.NULL_REVISION:
297
 
            if committers:
298
 
                all_committers = set()
299
 
            revisions = self.get_ancestry(revid)
300
 
            # pop the leading None
301
 
            revisions.pop(0)
302
 
            first_revision = None
303
 
            if not committers:
304
 
                # ignore the revisions in the middle - just grab first and last
305
 
                revisions = revisions[0], revisions[-1]
306
 
            for revision in self.get_revisions(revisions):
307
 
                if not first_revision:
308
 
                    first_revision = revision
309
 
                if committers:
310
 
                    all_committers.add(revision.committer)
311
 
            last_revision = revision
312
 
            if committers:
313
 
                result['committers'] = len(all_committers)
314
 
            result['firstrev'] = (first_revision.timestamp,
315
 
                first_revision.timezone)
316
 
            result['latestrev'] = (last_revision.timestamp,
317
 
                last_revision.timezone)
318
 
 
319
 
        # now gather global repository information
320
 
        if self.bzrdir.root_transport.listable():
321
 
            c, t = self._revision_store.total_size(self.get_transaction())
322
 
            result['revisions'] = c
323
 
            result['size'] = t
324
 
        return result
325
 
 
326
235
    @needs_read_lock
327
236
    def missing_revision_ids(self, other, revision_id=None):
328
237
        """Return the revision ids that other has that this does not.
331
240
 
332
241
        revision_id: only return revision ids included by revision_id.
333
242
        """
334
 
        revision_id = osutils.safe_revision_id(revision_id)
335
243
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
336
244
 
337
245
    @staticmethod
344
252
        control = bzrdir.BzrDir.open(base)
345
253
        return control.open_repository()
346
254
 
347
 
    def copy_content_into(self, destination, revision_id=None):
 
255
    def copy_content_into(self, destination, revision_id=None, basis=None):
348
256
        """Make a complete copy of the content in self into destination.
349
257
        
350
258
        This is a destructive operation! Do not use it on existing 
351
259
        repositories.
352
260
        """
353
 
        revision_id = osutils.safe_revision_id(revision_id)
354
 
        return InterRepository.get(self, destination).copy_content(revision_id)
 
261
        return InterRepository.get(self, destination).copy_content(revision_id, basis)
355
262
 
356
263
    def fetch(self, source, revision_id=None, pb=None):
357
264
        """Fetch the content required to construct revision_id from source.
358
265
 
359
266
        If revision_id is None all content is copied.
360
267
        """
361
 
        revision_id = osutils.safe_revision_id(revision_id)
362
 
        inter = InterRepository.get(source, self)
363
 
        try:
364
 
            return inter.fetch(revision_id=revision_id, pb=pb)
365
 
        except NotImplementedError:
366
 
            raise errors.IncompatibleRepositories(source, self)
 
268
        return InterRepository.get(source, self).fetch(revision_id=revision_id,
 
269
                                                       pb=pb)
367
270
 
368
271
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
369
272
                           timezone=None, committer=None, revprops=None, 
379
282
        :param revprops: Optional dictionary of revision properties.
380
283
        :param revision_id: Optional revision id.
381
284
        """
382
 
        revision_id = osutils.safe_revision_id(revision_id)
383
285
        return _CommitBuilder(self, parents, config, timestamp, timezone,
384
286
                              committer, revprops, revision_id)
385
287
 
387
289
        self.control_files.unlock()
388
290
 
389
291
    @needs_read_lock
390
 
    def clone(self, a_bzrdir, revision_id=None):
 
292
    def clone(self, a_bzrdir, revision_id=None, basis=None):
391
293
        """Clone this repository into a_bzrdir using the current format.
392
294
 
393
295
        Currently no check is made that the format of this repository and
394
296
        the bzrdir format are compatible. FIXME RBC 20060201.
395
 
 
396
 
        :return: The newly created destination repository.
397
 
        """
398
 
        # TODO: deprecate after 0.16; cloning this with all its settings is
399
 
        # probably not very useful -- mbp 20070423
400
 
        dest_repo = self._create_sprouting_repo(a_bzrdir, shared=self.is_shared())
401
 
        self.copy_content_into(dest_repo, revision_id)
402
 
        return dest_repo
403
 
 
404
 
    @needs_read_lock
405
 
    def sprout(self, to_bzrdir, revision_id=None):
406
 
        """Create a descendent repository for new development.
407
 
 
408
 
        Unlike clone, this does not copy the settings of the repository.
409
 
        """
410
 
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
411
 
        dest_repo.fetch(self, revision_id=revision_id)
412
 
        return dest_repo
413
 
 
414
 
    def _create_sprouting_repo(self, a_bzrdir, shared):
 
297
        """
415
298
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
416
299
            # use target default format.
417
 
            dest_repo = a_bzrdir.create_repository()
 
300
            result = a_bzrdir.create_repository()
 
301
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
 
302
        elif isinstance(a_bzrdir._format,
 
303
                      (bzrdir.BzrDirFormat4,
 
304
                       bzrdir.BzrDirFormat5,
 
305
                       bzrdir.BzrDirFormat6)):
 
306
            result = a_bzrdir.open_repository()
418
307
        else:
419
 
            # Most control formats need the repository to be specifically
420
 
            # created, but on some old all-in-one formats it's not needed
421
 
            try:
422
 
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
423
 
            except errors.UninitializableFormat:
424
 
                dest_repo = a_bzrdir.open_repository()
425
 
        return dest_repo
 
308
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
 
309
        self.copy_content_into(result, revision_id, basis)
 
310
        return result
426
311
 
427
312
    @needs_read_lock
428
313
    def has_revision(self, revision_id):
429
314
        """True if this repository has a copy of the revision."""
430
 
        revision_id = osutils.safe_revision_id(revision_id)
431
315
        return self._revision_store.has_revision_id(revision_id,
432
316
                                                    self.get_transaction())
433
317
 
443
327
        if not revision_id or not isinstance(revision_id, basestring):
444
328
            raise errors.InvalidRevisionId(revision_id=revision_id,
445
329
                                           branch=self)
446
 
        return self.get_revisions([revision_id])[0]
447
 
 
 
330
        return self._revision_store.get_revisions([revision_id],
 
331
                                                  self.get_transaction())[0]
448
332
    @needs_read_lock
449
333
    def get_revisions(self, revision_ids):
450
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
451
 
        revs = self._revision_store.get_revisions(revision_ids,
 
334
        return self._revision_store.get_revisions(revision_ids,
452
335
                                                  self.get_transaction())
453
 
        for rev in revs:
454
 
            assert not isinstance(rev.revision_id, unicode)
455
 
            for parent_id in rev.parent_ids:
456
 
                assert not isinstance(parent_id, unicode)
457
 
        return revs
458
336
 
459
337
    @needs_read_lock
460
338
    def get_revision_xml(self, revision_id):
461
 
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
462
 
        #       would have already do it.
463
 
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
464
 
        revision_id = osutils.safe_revision_id(revision_id)
465
 
        rev = self.get_revision(revision_id)
 
339
        rev = self.get_revision(revision_id) 
466
340
        rev_tmp = StringIO()
467
341
        # the current serializer..
468
342
        self._revision_store._serializer.write_revision(rev, rev_tmp)
472
346
    @needs_read_lock
473
347
    def get_revision(self, revision_id):
474
348
        """Return the Revision object for a named revision"""
475
 
        # TODO: jam 20070210 get_revision_reconcile should do this for us
476
 
        revision_id = osutils.safe_revision_id(revision_id)
477
349
        r = self.get_revision_reconcile(revision_id)
478
350
        # weave corruption can lead to absent revision markers that should be
479
351
        # present.
535
407
 
536
408
    @needs_write_lock
537
409
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
538
 
        revision_id = osutils.safe_revision_id(revision_id)
539
410
        signature = gpg_strategy.sign(plaintext)
540
411
        self._revision_store.add_revision_signature_text(revision_id,
541
412
                                                         signature,
552
423
        assert self._serializer.support_altered_by_hack, \
553
424
            ("fileids_altered_by_revision_ids only supported for branches " 
554
425
             "which store inventory as unnested xml, not on %r" % self)
555
 
        selected_revision_ids = set(osutils.safe_revision_id(r)
556
 
                                    for r in revision_ids)
 
426
        selected_revision_ids = set(revision_ids)
557
427
        w = self.get_inventory_weave()
558
428
        result = {}
559
429
 
565
435
        # revisions. We don't need to see all lines in the inventory because
566
436
        # only those added in an inventory in rev X can contain a revision=X
567
437
        # line.
568
 
        unescape_revid_cache = {}
569
 
        unescape_fileid_cache = {}
570
 
 
571
 
        # jam 20061218 In a big fetch, this handles hundreds of thousands
572
 
        # of lines, so it has had a lot of inlining and optimizing done.
573
 
        # Sorry that it is a little bit messy.
574
 
        # Move several functions to be local variables, since this is a long
575
 
        # running loop.
576
 
        search = self._file_ids_altered_regex.search
577
 
        unescape = _unescape_xml
578
 
        setdefault = result.setdefault
579
438
        pb = ui.ui_factory.nested_progress_bar()
580
439
        try:
581
440
            for line in w.iter_lines_added_or_present_in_versions(
582
 
                                        selected_revision_ids, pb=pb):
583
 
                match = search(line)
584
 
                if match is None:
585
 
                    continue
586
 
                # One call to match.group() returning multiple items is quite a
587
 
                # bit faster than 2 calls to match.group() each returning 1
588
 
                file_id, revision_id = match.group('file_id', 'revision_id')
589
 
 
590
 
                # Inlining the cache lookups helps a lot when you make 170,000
591
 
                # lines and 350k ids, versus 8.4 unique ids.
592
 
                # Using a cache helps in 2 ways:
593
 
                #   1) Avoids unnecessary decoding calls
594
 
                #   2) Re-uses cached strings, which helps in future set and
595
 
                #      equality checks.
596
 
                # (2) is enough that removing encoding entirely along with
597
 
                # the cache (so we are using plain strings) results in no
598
 
                # performance improvement.
599
 
                try:
600
 
                    revision_id = unescape_revid_cache[revision_id]
601
 
                except KeyError:
602
 
                    unescaped = unescape(revision_id)
603
 
                    unescape_revid_cache[revision_id] = unescaped
604
 
                    revision_id = unescaped
605
 
 
 
441
                selected_revision_ids, pb=pb):
 
442
                start = line.find('file_id="')+9
 
443
                if start < 9: continue
 
444
                end = line.find('"', start)
 
445
                assert end>= 0
 
446
                file_id = _unescape_xml(line[start:end])
 
447
 
 
448
                start = line.find('revision="')+10
 
449
                if start < 10: continue
 
450
                end = line.find('"', start)
 
451
                assert end>= 0
 
452
                revision_id = _unescape_xml(line[start:end])
606
453
                if revision_id in selected_revision_ids:
607
 
                    try:
608
 
                        file_id = unescape_fileid_cache[file_id]
609
 
                    except KeyError:
610
 
                        unescaped = unescape(file_id)
611
 
                        unescape_fileid_cache[file_id] = unescaped
612
 
                        file_id = unescaped
613
 
                    setdefault(file_id, set()).add(revision_id)
 
454
                    result.setdefault(file_id, set()).add(revision_id)
614
455
        finally:
615
456
            pb.finished()
616
457
        return result
623
464
    @needs_read_lock
624
465
    def get_inventory(self, revision_id):
625
466
        """Get Inventory object by hash."""
626
 
        # TODO: jam 20070210 Technically we don't need to sanitize, since all
627
 
        #       called functions must sanitize.
628
 
        revision_id = osutils.safe_revision_id(revision_id)
629
467
        return self.deserialise_inventory(
630
468
            revision_id, self.get_inventory_xml(revision_id))
631
469
 
635
473
        :param revision_id: The expected revision id of the inventory.
636
474
        :param xml: A serialised inventory.
637
475
        """
638
 
        revision_id = osutils.safe_revision_id(revision_id)
639
476
        result = self._serializer.read_inventory_from_string(xml)
640
477
        result.root.revision = revision_id
641
478
        return result
646
483
    @needs_read_lock
647
484
    def get_inventory_xml(self, revision_id):
648
485
        """Get inventory XML as a file object."""
649
 
        revision_id = osutils.safe_revision_id(revision_id)
650
486
        try:
651
 
            assert isinstance(revision_id, str), type(revision_id)
 
487
            assert isinstance(revision_id, basestring), type(revision_id)
652
488
            iw = self.get_inventory_weave()
653
489
            return iw.get_text(revision_id)
654
490
        except IndexError:
658
494
    def get_inventory_sha1(self, revision_id):
659
495
        """Return the sha1 hash of the inventory entry
660
496
        """
661
 
        # TODO: jam 20070210 Shouldn't this be deprecated / removed?
662
 
        revision_id = osutils.safe_revision_id(revision_id)
663
497
        return self.get_revision(revision_id).inventory_sha1
664
498
 
665
499
    @needs_read_lock
674
508
        # special case NULL_REVISION
675
509
        if revision_id == _mod_revision.NULL_REVISION:
676
510
            return {}
677
 
        revision_id = osutils.safe_revision_id(revision_id)
678
511
        a_weave = self.get_inventory_weave()
679
512
        all_revisions = self._eliminate_revisions_not_present(
680
513
                                a_weave.versions())
703
536
        :param revision_ids: an iterable of revisions to graph or None for all.
704
537
        :return: a Graph object with the graph reachable from revision_ids.
705
538
        """
706
 
        result = deprecated_graph.Graph()
 
539
        result = graph.Graph()
707
540
        if not revision_ids:
708
541
            pending = set(self.all_revision_ids())
709
542
            required = set([])
710
543
        else:
711
 
            pending = set(osutils.safe_revision_id(r) for r in revision_ids)
 
544
            pending = set(revision_ids)
712
545
            # special case NULL_REVISION
713
546
            if _mod_revision.NULL_REVISION in pending:
714
547
                pending.remove(_mod_revision.NULL_REVISION)
734
567
            done.add(revision_id)
735
568
        return result
736
569
 
737
 
    def _get_history_vf(self):
738
 
        """Get a versionedfile whose history graph reflects all revisions.
739
 
 
740
 
        For weave repositories, this is the inventory weave.
741
 
        """
742
 
        return self.get_inventory_weave()
743
 
 
744
 
    def iter_reverse_revision_history(self, revision_id):
745
 
        """Iterate backwards through revision ids in the lefthand history
746
 
 
747
 
        :param revision_id: The revision id to start with.  All its lefthand
748
 
            ancestors will be traversed.
749
 
        """
750
 
        revision_id = osutils.safe_revision_id(revision_id)
751
 
        if revision_id in (None, _mod_revision.NULL_REVISION):
752
 
            return
753
 
        next_id = revision_id
754
 
        versionedfile = self._get_history_vf()
755
 
        while True:
756
 
            yield next_id
757
 
            parents = versionedfile.get_parents(next_id)
758
 
            if len(parents) == 0:
759
 
                return
760
 
            else:
761
 
                next_id = parents[0]
762
 
 
763
570
    @needs_read_lock
764
571
    def get_revision_inventory(self, revision_id):
765
572
        """Return inventory of a past revision."""
788
595
        reconciler = RepoReconciler(self, thorough=thorough)
789
596
        reconciler.reconcile()
790
597
        return reconciler
791
 
 
 
598
    
792
599
    @needs_read_lock
793
600
    def revision_tree(self, revision_id):
794
601
        """Return Tree for a revision on this branch.
798
605
        # TODO: refactor this to use an existing revision object
799
606
        # so we don't need to read it in twice.
800
607
        if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
801
 
            return RevisionTree(self, Inventory(root_id=None), 
802
 
                                _mod_revision.NULL_REVISION)
 
608
            return RevisionTree(self, Inventory(), _mod_revision.NULL_REVISION)
803
609
        else:
804
 
            revision_id = osutils.safe_revision_id(revision_id)
805
610
            inv = self.get_revision_inventory(revision_id)
806
611
            return RevisionTree(self, inv, revision_id)
807
612
 
818
623
            yield RevisionTree(self, inv, revision_id)
819
624
 
820
625
    @needs_read_lock
821
 
    def get_ancestry(self, revision_id, topo_sorted=True):
 
626
    def get_ancestry(self, revision_id):
822
627
        """Return a list of revision-ids integrated by a revision.
823
628
 
824
629
        The first element of the list is always None, indicating the origin 
829
634
        """
830
635
        if revision_id is None:
831
636
            return [None]
832
 
        revision_id = osutils.safe_revision_id(revision_id)
833
637
        if not self.has_revision(revision_id):
834
638
            raise errors.NoSuchRevision(self, revision_id)
835
639
        w = self.get_inventory_weave()
836
 
        candidates = w.get_ancestry(revision_id, topo_sorted)
 
640
        candidates = w.get_ancestry(revision_id)
837
641
        return [None] + candidates # self._eliminate_revisions_not_present(candidates)
838
642
 
839
643
    @needs_read_lock
844
648
        - it writes to stdout, it assumes that that is valid etc. Fix
845
649
        by creating a new more flexible convenience function.
846
650
        """
847
 
        revision_id = osutils.safe_revision_id(revision_id)
848
651
        tree = self.revision_tree(revision_id)
849
652
        # use inventory as it was in that revision
850
653
        file_id = tree.inventory.path2id(file)
858
661
    def get_transaction(self):
859
662
        return self.control_files.get_transaction()
860
663
 
861
 
    def revision_parents(self, revision_id):
862
 
        revision_id = osutils.safe_revision_id(revision_id)
863
 
        return self.get_inventory_weave().parent_names(revision_id)
864
 
 
865
 
    def get_parents(self, revision_ids):
866
 
        """See StackedParentsProvider.get_parents"""
867
 
        parents_list = []
868
 
        for revision_id in revision_ids:
869
 
            if revision_id == _mod_revision.NULL_REVISION:
870
 
                parents = []
871
 
            else:
872
 
                try:
873
 
                    parents = self.get_revision(revision_id).parent_ids
874
 
                except errors.NoSuchRevision:
875
 
                    parents = None
876
 
                else:
877
 
                    if len(parents) == 0:
878
 
                        parents = [_mod_revision.NULL_REVISION]
879
 
            parents_list.append(parents)
880
 
        return parents_list
881
 
 
882
 
    def _make_parents_provider(self):
883
 
        return self
884
 
 
885
 
    def get_graph(self, other_repository=None):
886
 
        """Return the graph walker for this repository format"""
887
 
        parents_provider = self._make_parents_provider()
888
 
        if (other_repository is not None and
889
 
            other_repository.bzrdir.transport.base !=
890
 
            self.bzrdir.transport.base):
891
 
            parents_provider = graph._StackedParentsProvider(
892
 
                [parents_provider, other_repository._make_parents_provider()])
893
 
        return graph.Graph(parents_provider)
 
664
    def revision_parents(self, revid):
 
665
        return self.get_inventory_weave().parent_names(revid)
894
666
 
895
667
    @needs_write_lock
896
668
    def set_make_working_trees(self, new_value):
910
682
 
911
683
    @needs_write_lock
912
684
    def sign_revision(self, revision_id, gpg_strategy):
913
 
        revision_id = osutils.safe_revision_id(revision_id)
914
685
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
915
686
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
916
687
 
917
688
    @needs_read_lock
918
689
    def has_signature_for_revision_id(self, revision_id):
919
690
        """Query for a revision signature for revision_id in the repository."""
920
 
        revision_id = osutils.safe_revision_id(revision_id)
921
691
        return self._revision_store.has_signature(revision_id,
922
692
                                                  self.get_transaction())
923
693
 
924
694
    @needs_read_lock
925
695
    def get_signature_text(self, revision_id):
926
696
        """Return the text for a signature."""
927
 
        revision_id = osutils.safe_revision_id(revision_id)
928
697
        return self._revision_store.get_signature_text(revision_id,
929
698
                                                       self.get_transaction())
930
699
 
940
709
        if not revision_ids:
941
710
            raise ValueError("revision_ids must be non-empty in %s.check" 
942
711
                    % (self,))
943
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
944
712
        return self._check(revision_ids)
945
713
 
946
714
    def _check(self, revision_ids):
959
727
    def supports_rich_root(self):
960
728
        return self._format.rich_root_data
961
729
 
962
 
    def _check_ascii_revisionid(self, revision_id, method):
963
 
        """Private helper for ascii-only repositories."""
964
 
        # weave repositories refuse to store revisionids that are non-ascii.
965
 
        if revision_id is not None:
966
 
            # weaves require ascii revision ids.
967
 
            if isinstance(revision_id, unicode):
968
 
                try:
969
 
                    revision_id.encode('ascii')
970
 
                except UnicodeEncodeError:
971
 
                    raise errors.NonAsciiRevisionId(method, self)
972
 
            else:
973
 
                try:
974
 
                    revision_id.decode('ascii')
975
 
                except UnicodeDecodeError:
976
 
                    raise errors.NonAsciiRevisionId(method, self)
977
 
 
978
 
 
979
 
 
980
 
# remove these delegates a while after bzr 0.15
981
 
def __make_delegated(name, from_module):
982
 
    def _deprecated_repository_forwarder():
983
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
984
 
            % (name, from_module),
985
 
            DeprecationWarning,
986
 
            stacklevel=2)
987
 
        m = __import__(from_module, globals(), locals(), [name])
988
 
        try:
989
 
            return getattr(m, name)
990
 
        except AttributeError:
991
 
            raise AttributeError('module %s has no name %s'
992
 
                    % (m, name))
993
 
    globals()[name] = _deprecated_repository_forwarder
994
 
 
995
 
for _name in [
996
 
        'AllInOneRepository',
997
 
        'WeaveMetaDirRepository',
998
 
        'PreSplitOutRepositoryFormat',
999
 
        'RepositoryFormat4',
1000
 
        'RepositoryFormat5',
1001
 
        'RepositoryFormat6',
1002
 
        'RepositoryFormat7',
1003
 
        ]:
1004
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
1005
 
 
1006
 
for _name in [
1007
 
        'KnitRepository',
1008
 
        'RepositoryFormatKnit',
1009
 
        'RepositoryFormatKnit1',
1010
 
        ]:
1011
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
 
730
 
 
731
class AllInOneRepository(Repository):
 
732
    """Legacy support - the repository behaviour for all-in-one branches."""
 
733
 
 
734
    def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
 
735
        # we reuse one control files instance.
 
736
        dir_mode = a_bzrdir._control_files._dir_mode
 
737
        file_mode = a_bzrdir._control_files._file_mode
 
738
 
 
739
        def get_store(name, compressed=True, prefixed=False):
 
740
            # FIXME: This approach of assuming stores are all entirely compressed
 
741
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
742
            # some existing branches where there's a mixture; we probably 
 
743
            # still want the option to look for both.
 
744
            relpath = a_bzrdir._control_files._escape(name)
 
745
            store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
 
746
                              prefixed=prefixed, compressed=compressed,
 
747
                              dir_mode=dir_mode,
 
748
                              file_mode=file_mode)
 
749
            #if self._transport.should_cache():
 
750
            #    cache_path = os.path.join(self.cache_root, name)
 
751
            #    os.mkdir(cache_path)
 
752
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
753
            return store
 
754
 
 
755
        # not broken out yet because the controlweaves|inventory_store
 
756
        # and text_store | weave_store bits are still different.
 
757
        if isinstance(_format, RepositoryFormat4):
 
758
            # cannot remove these - there is still no consistent api 
 
759
            # which allows access to this old info.
 
760
            self.inventory_store = get_store('inventory-store')
 
761
            text_store = get_store('text-store')
 
762
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
 
763
 
 
764
    @needs_read_lock
 
765
    def is_shared(self):
 
766
        """AllInOne repositories cannot be shared."""
 
767
        return False
 
768
 
 
769
    @needs_write_lock
 
770
    def set_make_working_trees(self, new_value):
 
771
        """Set the policy flag for making working trees when creating branches.
 
772
 
 
773
        This only applies to branches that use this repository.
 
774
 
 
775
        The default is 'True'.
 
776
        :param new_value: True to restore the default, False to disable making
 
777
                          working trees.
 
778
        """
 
779
        raise NotImplementedError(self.set_make_working_trees)
 
780
    
 
781
    def make_working_trees(self):
 
782
        """Returns the policy for making working trees on new branches."""
 
783
        return True
1012
784
 
1013
785
 
1014
786
def install_revision(repository, rev, revision_tree):
1100
872
        return not self.control_files._transport.has('no-working-trees')
1101
873
 
1102
874
 
1103
 
class RepositoryFormatRegistry(registry.Registry):
1104
 
    """Registry of RepositoryFormats.
1105
 
    """
1106
 
 
1107
 
    def get(self, format_string):
1108
 
        r = registry.Registry.get(self, format_string)
1109
 
        if callable(r):
1110
 
            r = r()
1111
 
        return r
 
875
class KnitRepository(MetaDirRepository):
 
876
    """Knit format repository."""
 
877
 
 
878
    def _warn_if_deprecated(self):
 
879
        # This class isn't deprecated
 
880
        pass
 
881
 
 
882
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
883
        inv_vf.add_lines_with_ghosts(revid, parents, lines)
 
884
 
 
885
    @needs_read_lock
 
886
    def _all_revision_ids(self):
 
887
        """See Repository.all_revision_ids()."""
 
888
        # Knits get the revision graph from the index of the revision knit, so
 
889
        # it's always possible even if they're on an unlistable transport.
 
890
        return self._revision_store.all_revision_ids(self.get_transaction())
 
891
 
 
892
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
893
        """Find file_id(s) which are involved in the changes between revisions.
 
894
 
 
895
        This determines the set of revisions which are involved, and then
 
896
        finds all file ids affected by those revisions.
 
897
        """
 
898
        vf = self._get_revision_vf()
 
899
        from_set = set(vf.get_ancestry(from_revid))
 
900
        to_set = set(vf.get_ancestry(to_revid))
 
901
        changed = to_set.difference(from_set)
 
902
        return self._fileid_involved_by_set(changed)
 
903
 
 
904
    def fileid_involved(self, last_revid=None):
 
905
        """Find all file_ids modified in the ancestry of last_revid.
 
906
 
 
907
        :param last_revid: If None, last_revision() will be used.
 
908
        """
 
909
        if not last_revid:
 
910
            changed = set(self.all_revision_ids())
 
911
        else:
 
912
            changed = set(self.get_ancestry(last_revid))
 
913
        if None in changed:
 
914
            changed.remove(None)
 
915
        return self._fileid_involved_by_set(changed)
 
916
 
 
917
    @needs_read_lock
 
918
    def get_ancestry(self, revision_id):
 
919
        """Return a list of revision-ids integrated by a revision.
 
920
        
 
921
        This is topologically sorted.
 
922
        """
 
923
        if revision_id is None:
 
924
            return [None]
 
925
        vf = self._get_revision_vf()
 
926
        try:
 
927
            return [None] + vf.get_ancestry(revision_id)
 
928
        except errors.RevisionNotPresent:
 
929
            raise errors.NoSuchRevision(self, revision_id)
 
930
 
 
931
    @needs_read_lock
 
932
    def get_revision(self, revision_id):
 
933
        """Return the Revision object for a named revision"""
 
934
        return self.get_revision_reconcile(revision_id)
 
935
 
 
936
    @needs_read_lock
 
937
    def get_revision_graph(self, revision_id=None):
 
938
        """Return a dictionary containing the revision graph.
 
939
 
 
940
        :param revision_id: The revision_id to get a graph from. If None, then
 
941
        the entire revision graph is returned. This is a deprecated mode of
 
942
        operation and will be removed in the future.
 
943
        :return: a dictionary of revision_id->revision_parents_list.
 
944
        """
 
945
        # special case NULL_REVISION
 
946
        if revision_id == _mod_revision.NULL_REVISION:
 
947
            return {}
 
948
        a_weave = self._get_revision_vf()
 
949
        entire_graph = a_weave.get_graph()
 
950
        if revision_id is None:
 
951
            return a_weave.get_graph()
 
952
        elif revision_id not in a_weave:
 
953
            raise errors.NoSuchRevision(self, revision_id)
 
954
        else:
 
955
            # add what can be reached from revision_id
 
956
            result = {}
 
957
            pending = set([revision_id])
 
958
            while len(pending) > 0:
 
959
                node = pending.pop()
 
960
                result[node] = a_weave.get_parents(node)
 
961
                for revision_id in result[node]:
 
962
                    if revision_id not in result:
 
963
                        pending.add(revision_id)
 
964
            return result
 
965
 
 
966
    @needs_read_lock
 
967
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
968
        """Return a graph of the revisions with ghosts marked as applicable.
 
969
 
 
970
        :param revision_ids: an iterable of revisions to graph or None for all.
 
971
        :return: a Graph object with the graph reachable from revision_ids.
 
972
        """
 
973
        result = graph.Graph()
 
974
        vf = self._get_revision_vf()
 
975
        versions = set(vf.versions())
 
976
        if not revision_ids:
 
977
            pending = set(self.all_revision_ids())
 
978
            required = set([])
 
979
        else:
 
980
            pending = set(revision_ids)
 
981
            # special case NULL_REVISION
 
982
            if _mod_revision.NULL_REVISION in pending:
 
983
                pending.remove(_mod_revision.NULL_REVISION)
 
984
            required = set(pending)
 
985
        done = set([])
 
986
        while len(pending):
 
987
            revision_id = pending.pop()
 
988
            if not revision_id in versions:
 
989
                if revision_id in required:
 
990
                    raise errors.NoSuchRevision(self, revision_id)
 
991
                # a ghost
 
992
                result.add_ghost(revision_id)
 
993
                # mark it as done so we don't try for it again.
 
994
                done.add(revision_id)
 
995
                continue
 
996
            parent_ids = vf.get_parents_with_ghosts(revision_id)
 
997
            for parent_id in parent_ids:
 
998
                # is this queued or done ?
 
999
                if (parent_id not in pending and
 
1000
                    parent_id not in done):
 
1001
                    # no, queue it.
 
1002
                    pending.add(parent_id)
 
1003
            result.add_node(revision_id, parent_ids)
 
1004
            done.add(revision_id)
 
1005
        return result
 
1006
 
 
1007
    def _get_revision_vf(self):
 
1008
        """:return: a versioned file containing the revisions."""
 
1009
        vf = self._revision_store.get_revision_file(self.get_transaction())
 
1010
        return vf
 
1011
 
 
1012
    @needs_write_lock
 
1013
    def reconcile(self, other=None, thorough=False):
 
1014
        """Reconcile this repository."""
 
1015
        from bzrlib.reconcile import KnitReconciler
 
1016
        reconciler = KnitReconciler(self, thorough=thorough)
 
1017
        reconciler.reconcile()
 
1018
        return reconciler
1112
1019
    
1113
 
 
1114
 
format_registry = RepositoryFormatRegistry()
1115
 
"""Registry of formats, indexed by their identifying format string.
1116
 
 
1117
 
This can contain either format instances themselves, or classes/factories that
1118
 
can be called to obtain one.
1119
 
"""
1120
 
 
1121
 
 
1122
 
#####################################################################
1123
 
# Repository Formats
 
1020
    def revision_parents(self, revision_id):
 
1021
        return self._get_revision_vf().get_parents(revision_id)
 
1022
 
 
1023
 
 
1024
class KnitRepository2(KnitRepository):
 
1025
    """"""
 
1026
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
 
1027
                 control_store, text_store):
 
1028
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
1029
                              _revision_store, control_store, text_store)
 
1030
        self._serializer = xml6.serializer_v6
 
1031
 
 
1032
    def deserialise_inventory(self, revision_id, xml):
 
1033
        """Transform the xml into an inventory object. 
 
1034
 
 
1035
        :param revision_id: The expected revision id of the inventory.
 
1036
        :param xml: A serialised inventory.
 
1037
        """
 
1038
        result = self._serializer.read_inventory_from_string(xml)
 
1039
        assert result.root.revision is not None
 
1040
        return result
 
1041
 
 
1042
    def serialise_inventory(self, inv):
 
1043
        """Transform the inventory object into XML text.
 
1044
 
 
1045
        :param revision_id: The expected revision id of the inventory.
 
1046
        :param xml: A serialised inventory.
 
1047
        """
 
1048
        assert inv.revision_id is not None
 
1049
        assert inv.root.revision is not None
 
1050
        return KnitRepository.serialise_inventory(self, inv)
 
1051
 
 
1052
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
 
1053
                           timezone=None, committer=None, revprops=None, 
 
1054
                           revision_id=None):
 
1055
        """Obtain a CommitBuilder for this repository.
 
1056
        
 
1057
        :param branch: Branch to commit to.
 
1058
        :param parents: Revision ids of the parents of the new revision.
 
1059
        :param config: Configuration to use.
 
1060
        :param timestamp: Optional timestamp recorded for commit.
 
1061
        :param timezone: Optional timezone for timestamp.
 
1062
        :param committer: Optional committer to set for commit.
 
1063
        :param revprops: Optional dictionary of revision properties.
 
1064
        :param revision_id: Optional revision id.
 
1065
        """
 
1066
        return RootCommitBuilder(self, parents, config, timestamp, timezone,
 
1067
                                 committer, revprops, revision_id)
 
1068
 
1124
1069
 
1125
1070
class RepositoryFormat(object):
1126
1071
    """A repository format.
1146
1091
    parameterisation.
1147
1092
    """
1148
1093
 
 
1094
    _default_format = None
 
1095
    """The default format used for new repositories."""
 
1096
 
 
1097
    _formats = {}
 
1098
    """The known formats."""
 
1099
 
1149
1100
    def __str__(self):
1150
1101
        return "<%s>" % self.__class__.__name__
1151
1102
 
1152
 
    def __eq__(self, other):
1153
 
        # format objects are generally stateless
1154
 
        return isinstance(other, self.__class__)
1155
 
 
1156
 
    def __ne__(self, other):
1157
 
        return not self == other
1158
 
 
1159
1103
    @classmethod
1160
1104
    def find_format(klass, a_bzrdir):
1161
 
        """Return the format for the repository object in a_bzrdir.
1162
 
        
1163
 
        This is used by bzr native formats that have a "format" file in
1164
 
        the repository.  Other methods may be used by different types of 
1165
 
        control directory.
1166
 
        """
 
1105
        """Return the format for the repository object in a_bzrdir."""
1167
1106
        try:
1168
1107
            transport = a_bzrdir.get_repository_transport(None)
1169
1108
            format_string = transport.get("format").read()
1170
 
            return format_registry.get(format_string)
 
1109
            return klass._formats[format_string]
1171
1110
        except errors.NoSuchFile:
1172
1111
            raise errors.NoRepositoryPresent(a_bzrdir)
1173
1112
        except KeyError:
1174
1113
            raise errors.UnknownFormatError(format=format_string)
1175
1114
 
1176
 
    @classmethod
1177
 
    def register_format(klass, format):
1178
 
        format_registry.register(format.get_format_string(), format)
1179
 
 
1180
 
    @classmethod
1181
 
    def unregister_format(klass, format):
1182
 
        format_registry.remove(format.get_format_string())
 
1115
    def _get_control_store(self, repo_transport, control_files):
 
1116
        """Return the control store for this repository."""
 
1117
        raise NotImplementedError(self._get_control_store)
1183
1118
    
1184
1119
    @classmethod
1185
1120
    def get_default_format(klass):
1186
1121
        """Return the current default format."""
1187
 
        from bzrlib import bzrdir
1188
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
1189
 
 
1190
 
    def _get_control_store(self, repo_transport, control_files):
1191
 
        """Return the control store for this repository."""
1192
 
        raise NotImplementedError(self._get_control_store)
 
1122
        return klass._default_format
1193
1123
 
1194
1124
    def get_format_string(self):
1195
1125
        """Return the ASCII format string that identifies this format.
1222
1152
        from bzrlib.store.revision.text import TextRevisionStore
1223
1153
        dir_mode = control_files._dir_mode
1224
1154
        file_mode = control_files._file_mode
1225
 
        text_store = TextStore(transport.clone(name),
 
1155
        text_store =TextStore(transport.clone(name),
1226
1156
                              prefixed=prefixed,
1227
1157
                              compressed=compressed,
1228
1158
                              dir_mode=dir_mode,
1230
1160
        _revision_store = TextRevisionStore(text_store, serializer)
1231
1161
        return _revision_store
1232
1162
 
1233
 
    # TODO: this shouldn't be in the base class, it's specific to things that
1234
 
    # use weaves or knits -- mbp 20070207
1235
1163
    def _get_versioned_file_store(self,
1236
1164
                                  name,
1237
1165
                                  transport,
1238
1166
                                  control_files,
1239
1167
                                  prefixed=True,
1240
 
                                  versionedfile_class=None,
 
1168
                                  versionedfile_class=weave.WeaveFile,
1241
1169
                                  versionedfile_kwargs={},
1242
1170
                                  escaped=False):
1243
 
        if versionedfile_class is None:
1244
 
            versionedfile_class = self._versionedfile_class
1245
1171
        weave_transport = control_files._transport.clone(name)
1246
1172
        dir_mode = control_files._dir_mode
1247
1173
        file_mode = control_files._file_mode
1257
1183
 
1258
1184
        :param a_bzrdir: The bzrdir to put the new repository in it.
1259
1185
        :param shared: The repository should be initialized as a sharable one.
1260
 
        :returns: The new repository object.
1261
 
        
 
1186
 
1262
1187
        This may raise UninitializableFormat if shared repository are not
1263
1188
        compatible the a_bzrdir.
1264
1189
        """
1265
 
        raise NotImplementedError(self.initialize)
1266
1190
 
1267
1191
    def is_supported(self):
1268
1192
        """Is this format supported?
1283
1207
        """
1284
1208
        raise NotImplementedError(self.open)
1285
1209
 
 
1210
    @classmethod
 
1211
    def register_format(klass, format):
 
1212
        klass._formats[format.get_format_string()] = format
 
1213
 
 
1214
    @classmethod
 
1215
    def set_default_format(klass, format):
 
1216
        klass._default_format = format
 
1217
 
 
1218
    @classmethod
 
1219
    def unregister_format(klass, format):
 
1220
        assert klass._formats[format.get_format_string()] is format
 
1221
        del klass._formats[format.get_format_string()]
 
1222
 
 
1223
 
 
1224
class PreSplitOutRepositoryFormat(RepositoryFormat):
 
1225
    """Base class for the pre split out repository formats."""
 
1226
 
 
1227
    rich_root_data = False
 
1228
 
 
1229
    def initialize(self, a_bzrdir, shared=False, _internal=False):
 
1230
        """Create a weave repository.
 
1231
        
 
1232
        TODO: when creating split out bzr branch formats, move this to a common
 
1233
        base for Format5, Format6. or something like that.
 
1234
        """
 
1235
        if shared:
 
1236
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
1237
 
 
1238
        if not _internal:
 
1239
            # always initialized when the bzrdir is.
 
1240
            return self.open(a_bzrdir, _found=True)
 
1241
        
 
1242
        # Create an empty weave
 
1243
        sio = StringIO()
 
1244
        weavefile.write_weave_v5(weave.Weave(), sio)
 
1245
        empty_weave = sio.getvalue()
 
1246
 
 
1247
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1248
        dirs = ['revision-store', 'weaves']
 
1249
        files = [('inventory.weave', StringIO(empty_weave)),
 
1250
                 ]
 
1251
        
 
1252
        # FIXME: RBC 20060125 don't peek under the covers
 
1253
        # NB: no need to escape relative paths that are url safe.
 
1254
        control_files = lockable_files.LockableFiles(a_bzrdir.transport,
 
1255
                                'branch-lock', lockable_files.TransportLock)
 
1256
        control_files.create_lock()
 
1257
        control_files.lock_write()
 
1258
        control_files._transport.mkdir_multi(dirs,
 
1259
                mode=control_files._dir_mode)
 
1260
        try:
 
1261
            for file, content in files:
 
1262
                control_files.put(file, content)
 
1263
        finally:
 
1264
            control_files.unlock()
 
1265
        return self.open(a_bzrdir, _found=True)
 
1266
 
 
1267
    def _get_control_store(self, repo_transport, control_files):
 
1268
        """Return the control store for this repository."""
 
1269
        return self._get_versioned_file_store('',
 
1270
                                              repo_transport,
 
1271
                                              control_files,
 
1272
                                              prefixed=False)
 
1273
 
 
1274
    def _get_text_store(self, transport, control_files):
 
1275
        """Get a store for file texts for this format."""
 
1276
        raise NotImplementedError(self._get_text_store)
 
1277
 
 
1278
    def open(self, a_bzrdir, _found=False):
 
1279
        """See RepositoryFormat.open()."""
 
1280
        if not _found:
 
1281
            # we are being called directly and must probe.
 
1282
            raise NotImplementedError
 
1283
 
 
1284
        repo_transport = a_bzrdir.get_repository_transport(None)
 
1285
        control_files = a_bzrdir._control_files
 
1286
        text_store = self._get_text_store(repo_transport, control_files)
 
1287
        control_store = self._get_control_store(repo_transport, control_files)
 
1288
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1289
        return AllInOneRepository(_format=self,
 
1290
                                  a_bzrdir=a_bzrdir,
 
1291
                                  _revision_store=_revision_store,
 
1292
                                  control_store=control_store,
 
1293
                                  text_store=text_store)
 
1294
 
 
1295
    def check_conversion_target(self, target_format):
 
1296
        pass
 
1297
 
 
1298
 
 
1299
class RepositoryFormat4(PreSplitOutRepositoryFormat):
 
1300
    """Bzr repository format 4.
 
1301
 
 
1302
    This repository format has:
 
1303
     - flat stores
 
1304
     - TextStores for texts, inventories,revisions.
 
1305
 
 
1306
    This format is deprecated: it indexes texts using a text id which is
 
1307
    removed in format 5; initialization and write support for this format
 
1308
    has been removed.
 
1309
    """
 
1310
 
 
1311
    def __init__(self):
 
1312
        super(RepositoryFormat4, self).__init__()
 
1313
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
 
1314
 
 
1315
    def get_format_description(self):
 
1316
        """See RepositoryFormat.get_format_description()."""
 
1317
        return "Repository format 4"
 
1318
 
 
1319
    def initialize(self, url, shared=False, _internal=False):
 
1320
        """Format 4 branches cannot be created."""
 
1321
        raise errors.UninitializableFormat(self)
 
1322
 
 
1323
    def is_supported(self):
 
1324
        """Format 4 is not supported.
 
1325
 
 
1326
        It is not supported because the model changed from 4 to 5 and the
 
1327
        conversion logic is expensive - so doing it on the fly was not 
 
1328
        feasible.
 
1329
        """
 
1330
        return False
 
1331
 
 
1332
    def _get_control_store(self, repo_transport, control_files):
 
1333
        """Format 4 repositories have no formal control store at this point.
 
1334
        
 
1335
        This will cause any control-file-needing apis to fail - this is desired.
 
1336
        """
 
1337
        return None
 
1338
    
 
1339
    def _get_revision_store(self, repo_transport, control_files):
 
1340
        """See RepositoryFormat._get_revision_store()."""
 
1341
        from bzrlib.xml4 import serializer_v4
 
1342
        return self._get_text_rev_store(repo_transport,
 
1343
                                        control_files,
 
1344
                                        'revision-store',
 
1345
                                        serializer=serializer_v4)
 
1346
 
 
1347
    def _get_text_store(self, transport, control_files):
 
1348
        """See RepositoryFormat._get_text_store()."""
 
1349
 
 
1350
 
 
1351
class RepositoryFormat5(PreSplitOutRepositoryFormat):
 
1352
    """Bzr control format 5.
 
1353
 
 
1354
    This repository format has:
 
1355
     - weaves for file texts and inventory
 
1356
     - flat stores
 
1357
     - TextStores for revisions and signatures.
 
1358
    """
 
1359
 
 
1360
    def __init__(self):
 
1361
        super(RepositoryFormat5, self).__init__()
 
1362
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
 
1363
 
 
1364
    def get_format_description(self):
 
1365
        """See RepositoryFormat.get_format_description()."""
 
1366
        return "Weave repository format 5"
 
1367
 
 
1368
    def _get_revision_store(self, repo_transport, control_files):
 
1369
        """See RepositoryFormat._get_revision_store()."""
 
1370
        """Return the revision store object for this a_bzrdir."""
 
1371
        return self._get_text_rev_store(repo_transport,
 
1372
                                        control_files,
 
1373
                                        'revision-store',
 
1374
                                        compressed=False)
 
1375
 
 
1376
    def _get_text_store(self, transport, control_files):
 
1377
        """See RepositoryFormat._get_text_store()."""
 
1378
        return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
 
1379
 
 
1380
 
 
1381
class RepositoryFormat6(PreSplitOutRepositoryFormat):
 
1382
    """Bzr control format 6.
 
1383
 
 
1384
    This repository format has:
 
1385
     - weaves for file texts and inventory
 
1386
     - hash subdirectory based stores.
 
1387
     - TextStores for revisions and signatures.
 
1388
    """
 
1389
 
 
1390
    def __init__(self):
 
1391
        super(RepositoryFormat6, self).__init__()
 
1392
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1393
 
 
1394
    def get_format_description(self):
 
1395
        """See RepositoryFormat.get_format_description()."""
 
1396
        return "Weave repository format 6"
 
1397
 
 
1398
    def _get_revision_store(self, repo_transport, control_files):
 
1399
        """See RepositoryFormat._get_revision_store()."""
 
1400
        return self._get_text_rev_store(repo_transport,
 
1401
                                        control_files,
 
1402
                                        'revision-store',
 
1403
                                        compressed=False,
 
1404
                                        prefixed=True)
 
1405
 
 
1406
    def _get_text_store(self, transport, control_files):
 
1407
        """See RepositoryFormat._get_text_store()."""
 
1408
        return self._get_versioned_file_store('weaves', transport, control_files)
 
1409
 
1286
1410
 
1287
1411
class MetaDirRepositoryFormat(RepositoryFormat):
1288
1412
    """Common base class for the new repositories using the metadir layout."""
1289
1413
 
1290
1414
    rich_root_data = False
1291
 
    supports_tree_reference = False
1292
 
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1293
1415
 
1294
1416
    def __init__(self):
1295
1417
        super(MetaDirRepositoryFormat, self).__init__()
 
1418
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1296
1419
 
1297
1420
    def _create_control_files(self, a_bzrdir):
1298
1421
        """Create the required files and the initial control_files object."""
1321
1444
            control_files.unlock()
1322
1445
 
1323
1446
 
 
1447
class RepositoryFormat7(MetaDirRepositoryFormat):
 
1448
    """Bzr repository 7.
 
1449
 
 
1450
    This repository format has:
 
1451
     - weaves for file texts and inventory
 
1452
     - hash subdirectory based stores.
 
1453
     - TextStores for revisions and signatures.
 
1454
     - a format marker of its own
 
1455
     - an optional 'shared-storage' flag
 
1456
     - an optional 'no-working-trees' flag
 
1457
    """
 
1458
 
 
1459
    def _get_control_store(self, repo_transport, control_files):
 
1460
        """Return the control store for this repository."""
 
1461
        return self._get_versioned_file_store('',
 
1462
                                              repo_transport,
 
1463
                                              control_files,
 
1464
                                              prefixed=False)
 
1465
 
 
1466
    def get_format_string(self):
 
1467
        """See RepositoryFormat.get_format_string()."""
 
1468
        return "Bazaar-NG Repository format 7"
 
1469
 
 
1470
    def get_format_description(self):
 
1471
        """See RepositoryFormat.get_format_description()."""
 
1472
        return "Weave repository format 7"
 
1473
 
 
1474
    def check_conversion_target(self, target_format):
 
1475
        pass
 
1476
 
 
1477
    def _get_revision_store(self, repo_transport, control_files):
 
1478
        """See RepositoryFormat._get_revision_store()."""
 
1479
        return self._get_text_rev_store(repo_transport,
 
1480
                                        control_files,
 
1481
                                        'revision-store',
 
1482
                                        compressed=False,
 
1483
                                        prefixed=True,
 
1484
                                        )
 
1485
 
 
1486
    def _get_text_store(self, transport, control_files):
 
1487
        """See RepositoryFormat._get_text_store()."""
 
1488
        return self._get_versioned_file_store('weaves',
 
1489
                                              transport,
 
1490
                                              control_files)
 
1491
 
 
1492
    def initialize(self, a_bzrdir, shared=False):
 
1493
        """Create a weave repository.
 
1494
 
 
1495
        :param shared: If true the repository will be initialized as a shared
 
1496
                       repository.
 
1497
        """
 
1498
        # Create an empty weave
 
1499
        sio = StringIO()
 
1500
        weavefile.write_weave_v5(weave.Weave(), sio)
 
1501
        empty_weave = sio.getvalue()
 
1502
 
 
1503
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1504
        dirs = ['revision-store', 'weaves']
 
1505
        files = [('inventory.weave', StringIO(empty_weave)), 
 
1506
                 ]
 
1507
        utf8_files = [('format', self.get_format_string())]
 
1508
 
 
1509
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1510
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1511
 
 
1512
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1513
        """See RepositoryFormat.open().
 
1514
        
 
1515
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1516
                                    repository at a slightly different url
 
1517
                                    than normal. I.e. during 'upgrade'.
 
1518
        """
 
1519
        if not _found:
 
1520
            format = RepositoryFormat.find_format(a_bzrdir)
 
1521
            assert format.__class__ ==  self.__class__
 
1522
        if _override_transport is not None:
 
1523
            repo_transport = _override_transport
 
1524
        else:
 
1525
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1526
        control_files = lockable_files.LockableFiles(repo_transport,
 
1527
                                'lock', lockdir.LockDir)
 
1528
        text_store = self._get_text_store(repo_transport, control_files)
 
1529
        control_store = self._get_control_store(repo_transport, control_files)
 
1530
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1531
        return MetaDirRepository(_format=self,
 
1532
                                 a_bzrdir=a_bzrdir,
 
1533
                                 control_files=control_files,
 
1534
                                 _revision_store=_revision_store,
 
1535
                                 control_store=control_store,
 
1536
                                 text_store=text_store)
 
1537
 
 
1538
 
 
1539
class RepositoryFormatKnit(MetaDirRepositoryFormat):
 
1540
    """Bzr repository knit format (generalized). 
 
1541
 
 
1542
    This repository format has:
 
1543
     - knits for file texts and inventory
 
1544
     - hash subdirectory based stores.
 
1545
     - knits for revisions and signatures
 
1546
     - TextStores for revisions and signatures.
 
1547
     - a format marker of its own
 
1548
     - an optional 'shared-storage' flag
 
1549
     - an optional 'no-working-trees' flag
 
1550
     - a LockDir lock
 
1551
    """
 
1552
 
 
1553
    def _get_control_store(self, repo_transport, control_files):
 
1554
        """Return the control store for this repository."""
 
1555
        return VersionedFileStore(
 
1556
            repo_transport,
 
1557
            prefixed=False,
 
1558
            file_mode=control_files._file_mode,
 
1559
            versionedfile_class=knit.KnitVersionedFile,
 
1560
            versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
 
1561
            )
 
1562
 
 
1563
    def _get_revision_store(self, repo_transport, control_files):
 
1564
        """See RepositoryFormat._get_revision_store()."""
 
1565
        from bzrlib.store.revision.knit import KnitRevisionStore
 
1566
        versioned_file_store = VersionedFileStore(
 
1567
            repo_transport,
 
1568
            file_mode=control_files._file_mode,
 
1569
            prefixed=False,
 
1570
            precious=True,
 
1571
            versionedfile_class=knit.KnitVersionedFile,
 
1572
            versionedfile_kwargs={'delta':False,
 
1573
                                  'factory':knit.KnitPlainFactory(),
 
1574
                                 },
 
1575
            escaped=True,
 
1576
            )
 
1577
        return KnitRevisionStore(versioned_file_store)
 
1578
 
 
1579
    def _get_text_store(self, transport, control_files):
 
1580
        """See RepositoryFormat._get_text_store()."""
 
1581
        return self._get_versioned_file_store('knits',
 
1582
                                  transport,
 
1583
                                  control_files,
 
1584
                                  versionedfile_class=knit.KnitVersionedFile,
 
1585
                                  versionedfile_kwargs={
 
1586
                                      'create_parent_dir':True,
 
1587
                                      'delay_create':True,
 
1588
                                      'dir_mode':control_files._dir_mode,
 
1589
                                  },
 
1590
                                  escaped=True)
 
1591
 
 
1592
    def initialize(self, a_bzrdir, shared=False):
 
1593
        """Create a knit format 1 repository.
 
1594
 
 
1595
        :param a_bzrdir: bzrdir to contain the new repository; must already
 
1596
            be initialized.
 
1597
        :param shared: If true the repository will be initialized as a shared
 
1598
                       repository.
 
1599
        """
 
1600
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1601
        dirs = ['revision-store', 'knits']
 
1602
        files = []
 
1603
        utf8_files = [('format', self.get_format_string())]
 
1604
        
 
1605
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1606
        repo_transport = a_bzrdir.get_repository_transport(None)
 
1607
        control_files = lockable_files.LockableFiles(repo_transport,
 
1608
                                'lock', lockdir.LockDir)
 
1609
        control_store = self._get_control_store(repo_transport, control_files)
 
1610
        transaction = transactions.WriteTransaction()
 
1611
        # trigger a write of the inventory store.
 
1612
        control_store.get_weave_or_empty('inventory', transaction)
 
1613
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1614
        _revision_store.has_revision_id('A', transaction)
 
1615
        _revision_store.get_signature_file(transaction)
 
1616
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1617
 
 
1618
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1619
        """See RepositoryFormat.open().
 
1620
        
 
1621
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1622
                                    repository at a slightly different url
 
1623
                                    than normal. I.e. during 'upgrade'.
 
1624
        """
 
1625
        if not _found:
 
1626
            format = RepositoryFormat.find_format(a_bzrdir)
 
1627
            assert format.__class__ ==  self.__class__
 
1628
        if _override_transport is not None:
 
1629
            repo_transport = _override_transport
 
1630
        else:
 
1631
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1632
        control_files = lockable_files.LockableFiles(repo_transport,
 
1633
                                'lock', lockdir.LockDir)
 
1634
        text_store = self._get_text_store(repo_transport, control_files)
 
1635
        control_store = self._get_control_store(repo_transport, control_files)
 
1636
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1637
        return KnitRepository(_format=self,
 
1638
                              a_bzrdir=a_bzrdir,
 
1639
                              control_files=control_files,
 
1640
                              _revision_store=_revision_store,
 
1641
                              control_store=control_store,
 
1642
                              text_store=text_store)
 
1643
 
 
1644
 
 
1645
class RepositoryFormatKnit1(RepositoryFormatKnit):
 
1646
    """Bzr repository knit format 1.
 
1647
 
 
1648
    This repository format has:
 
1649
     - knits for file texts and inventory
 
1650
     - hash subdirectory based stores.
 
1651
     - knits for revisions and signatures
 
1652
     - TextStores for revisions and signatures.
 
1653
     - a format marker of its own
 
1654
     - an optional 'shared-storage' flag
 
1655
     - an optional 'no-working-trees' flag
 
1656
     - a LockDir lock
 
1657
 
 
1658
    This format was introduced in bzr 0.8.
 
1659
    """
 
1660
    def get_format_string(self):
 
1661
        """See RepositoryFormat.get_format_string()."""
 
1662
        return "Bazaar-NG Knit Repository Format 1"
 
1663
 
 
1664
    def get_format_description(self):
 
1665
        """See RepositoryFormat.get_format_description()."""
 
1666
        return "Knit repository format 1"
 
1667
 
 
1668
    def check_conversion_target(self, target_format):
 
1669
        pass
 
1670
 
 
1671
 
 
1672
class RepositoryFormatKnit2(RepositoryFormatKnit):
 
1673
    """Bzr repository knit format 2.
 
1674
 
 
1675
    THIS FORMAT IS EXPERIMENTAL
 
1676
    This repository format has:
 
1677
     - knits for file texts and inventory
 
1678
     - hash subdirectory based stores.
 
1679
     - knits for revisions and signatures
 
1680
     - TextStores for revisions and signatures.
 
1681
     - a format marker of its own
 
1682
     - an optional 'shared-storage' flag
 
1683
     - an optional 'no-working-trees' flag
 
1684
     - a LockDir lock
 
1685
     - Support for recording full info about the tree root
 
1686
 
 
1687
    """
 
1688
    
 
1689
    rich_root_data = True
 
1690
 
 
1691
    def get_format_string(self):
 
1692
        """See RepositoryFormat.get_format_string()."""
 
1693
        return "Bazaar Knit Repository Format 2\n"
 
1694
 
 
1695
    def get_format_description(self):
 
1696
        """See RepositoryFormat.get_format_description()."""
 
1697
        return "Knit repository format 2"
 
1698
 
 
1699
    def check_conversion_target(self, target_format):
 
1700
        if not target_format.rich_root_data:
 
1701
            raise errors.BadConversionTarget(
 
1702
                'Does not support rich root data.', target_format)
 
1703
 
 
1704
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1705
        """See RepositoryFormat.open().
 
1706
        
 
1707
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1708
                                    repository at a slightly different url
 
1709
                                    than normal. I.e. during 'upgrade'.
 
1710
        """
 
1711
        if not _found:
 
1712
            format = RepositoryFormat.find_format(a_bzrdir)
 
1713
            assert format.__class__ ==  self.__class__
 
1714
        if _override_transport is not None:
 
1715
            repo_transport = _override_transport
 
1716
        else:
 
1717
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1718
        control_files = lockable_files.LockableFiles(repo_transport, 'lock',
 
1719
                                                     lockdir.LockDir)
 
1720
        text_store = self._get_text_store(repo_transport, control_files)
 
1721
        control_store = self._get_control_store(repo_transport, control_files)
 
1722
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1723
        return KnitRepository2(_format=self,
 
1724
                               a_bzrdir=a_bzrdir,
 
1725
                               control_files=control_files,
 
1726
                               _revision_store=_revision_store,
 
1727
                               control_store=control_store,
 
1728
                               text_store=text_store)
 
1729
 
 
1730
 
 
1731
 
1324
1732
# formats which have no format string are not discoverable
1325
 
# and not independently creatable, so are not registered.  They're 
1326
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
1327
 
# needed, it's constructed directly by the BzrDir.  Non-native formats where
1328
 
# the repository is not separately opened are similar.
1329
 
 
1330
 
format_registry.register_lazy(
1331
 
    'Bazaar-NG Repository format 7',
1332
 
    'bzrlib.repofmt.weaverepo',
1333
 
    'RepositoryFormat7'
1334
 
    )
1335
 
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1336
 
# default control directory format
1337
 
 
1338
 
format_registry.register_lazy(
1339
 
    'Bazaar-NG Knit Repository Format 1',
1340
 
    'bzrlib.repofmt.knitrepo',
1341
 
    'RepositoryFormatKnit1',
1342
 
    )
1343
 
format_registry.default_key = 'Bazaar-NG Knit Repository Format 1'
1344
 
 
1345
 
format_registry.register_lazy(
1346
 
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1347
 
    'bzrlib.repofmt.knitrepo',
1348
 
    'RepositoryFormatKnit3',
1349
 
    )
 
1733
# and not independently creatable, so are not registered.
 
1734
RepositoryFormat.register_format(RepositoryFormat7())
 
1735
_default_format = RepositoryFormatKnit1()
 
1736
RepositoryFormat.register_format(_default_format)
 
1737
RepositoryFormat.register_format(RepositoryFormatKnit2())
 
1738
RepositoryFormat.set_default_format(_default_format)
 
1739
_legacy_formats = [RepositoryFormat4(),
 
1740
                   RepositoryFormat5(),
 
1741
                   RepositoryFormat6()]
1350
1742
 
1351
1743
 
1352
1744
class InterRepository(InterObject):
1364
1756
    _optimisers = []
1365
1757
    """The available optimised InterRepository types."""
1366
1758
 
1367
 
    def copy_content(self, revision_id=None):
 
1759
    def copy_content(self, revision_id=None, basis=None):
1368
1760
        raise NotImplementedError(self.copy_content)
1369
1761
 
1370
1762
    def fetch(self, revision_id=None, pb=None):
1394
1786
        # generic, possibly worst case, slow code path.
1395
1787
        target_ids = set(self.target.all_revision_ids())
1396
1788
        if revision_id is not None:
1397
 
            # TODO: jam 20070210 InterRepository is internal enough that it
1398
 
            #       should assume revision_ids are already utf-8
1399
 
            revision_id = osutils.safe_revision_id(revision_id)
1400
1789
            source_ids = self.source.get_ancestry(revision_id)
1401
1790
            assert source_ids[0] is None
1402
1791
            source_ids.pop(0)
1415
1804
    Data format and model must match for this to work.
1416
1805
    """
1417
1806
 
1418
 
    @classmethod
1419
 
    def _get_repo_format_to_test(self):
1420
 
        """Repository format for testing with."""
1421
 
        return RepositoryFormat.get_default_format()
 
1807
    _matching_repo_format = RepositoryFormat4()
 
1808
    """Repository format for testing with."""
1422
1809
 
1423
1810
    @staticmethod
1424
1811
    def is_compatible(source, target):
1425
 
        if source.supports_rich_root() != target.supports_rich_root():
1426
 
            return False
1427
 
        if source._serializer != target._serializer:
1428
 
            return False
1429
 
        return True
 
1812
        if not isinstance(source, Repository):
 
1813
            return False
 
1814
        if not isinstance(target, Repository):
 
1815
            return False
 
1816
        if source._format.rich_root_data == target._format.rich_root_data:
 
1817
            return True
 
1818
        else:
 
1819
            return False
1430
1820
 
1431
1821
    @needs_write_lock
1432
 
    def copy_content(self, revision_id=None):
 
1822
    def copy_content(self, revision_id=None, basis=None):
1433
1823
        """Make a complete copy of the content in self into destination.
1434
 
 
1435
 
        This copies both the repository's revision data, and configuration information
1436
 
        such as the make_working_trees setting.
1437
1824
        
1438
1825
        This is a destructive operation! Do not use it on existing 
1439
1826
        repositories.
1440
1827
 
1441
1828
        :param revision_id: Only copy the content needed to construct
1442
1829
                            revision_id and its parents.
 
1830
        :param basis: Copy the needed data preferentially from basis.
1443
1831
        """
1444
1832
        try:
1445
1833
            self.target.set_make_working_trees(self.source.make_working_trees())
1446
1834
        except NotImplementedError:
1447
1835
            pass
1448
 
        # TODO: jam 20070210 This is fairly internal, so we should probably
1449
 
        #       just assert that revision_id is not unicode.
1450
 
        revision_id = osutils.safe_revision_id(revision_id)
 
1836
        # grab the basis available data
 
1837
        if basis is not None:
 
1838
            self.target.fetch(basis, revision_id=revision_id)
1451
1839
        # but don't bother fetching if we have the needed data now.
1452
1840
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
1453
1841
            self.target.has_revision(revision_id)):
1461
1849
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1462
1850
               self.source, self.source._format, self.target, 
1463
1851
               self.target._format)
1464
 
        # TODO: jam 20070210 This should be an assert, not a translate
1465
 
        revision_id = osutils.safe_revision_id(revision_id)
1466
1852
        f = GenericRepoFetcher(to_repository=self.target,
1467
1853
                               from_repository=self.source,
1468
1854
                               last_revision=revision_id,
1473
1859
class InterWeaveRepo(InterSameDataRepository):
1474
1860
    """Optimised code paths between Weave based repositories."""
1475
1861
 
1476
 
    @classmethod
1477
 
    def _get_repo_format_to_test(self):
1478
 
        from bzrlib.repofmt import weaverepo
1479
 
        return weaverepo.RepositoryFormat7()
 
1862
    _matching_repo_format = RepositoryFormat7()
 
1863
    """Repository format for testing with."""
1480
1864
 
1481
1865
    @staticmethod
1482
1866
    def is_compatible(source, target):
1486
1870
        could lead to confusing results, and there is no need to be 
1487
1871
        overly general.
1488
1872
        """
1489
 
        from bzrlib.repofmt.weaverepo import (
1490
 
                RepositoryFormat5,
1491
 
                RepositoryFormat6,
1492
 
                RepositoryFormat7,
1493
 
                )
1494
1873
        try:
1495
1874
            return (isinstance(source._format, (RepositoryFormat5,
1496
1875
                                                RepositoryFormat6,
1502
1881
            return False
1503
1882
    
1504
1883
    @needs_write_lock
1505
 
    def copy_content(self, revision_id=None):
 
1884
    def copy_content(self, revision_id=None, basis=None):
1506
1885
        """See InterRepository.copy_content()."""
1507
1886
        # weave specific optimised path:
1508
 
        # TODO: jam 20070210 Internal, should be an assert, not translate
1509
 
        revision_id = osutils.safe_revision_id(revision_id)
1510
 
        try:
1511
 
            self.target.set_make_working_trees(self.source.make_working_trees())
1512
 
        except NotImplementedError:
1513
 
            pass
1514
 
        # FIXME do not peek!
1515
 
        if self.source.control_files._transport.listable():
1516
 
            pb = ui.ui_factory.nested_progress_bar()
 
1887
        if basis is not None:
 
1888
            # copy the basis in, then fetch remaining data.
 
1889
            basis.copy_content_into(self.target, revision_id)
 
1890
            # the basis copy_content_into could miss-set this.
1517
1891
            try:
1518
 
                self.target.weave_store.copy_all_ids(
1519
 
                    self.source.weave_store,
1520
 
                    pb=pb,
1521
 
                    from_transaction=self.source.get_transaction(),
1522
 
                    to_transaction=self.target.get_transaction())
1523
 
                pb.update('copying inventory', 0, 1)
1524
 
                self.target.control_weaves.copy_multi(
1525
 
                    self.source.control_weaves, ['inventory'],
1526
 
                    from_transaction=self.source.get_transaction(),
1527
 
                    to_transaction=self.target.get_transaction())
1528
 
                self.target._revision_store.text_store.copy_all_ids(
1529
 
                    self.source._revision_store.text_store,
1530
 
                    pb=pb)
1531
 
            finally:
1532
 
                pb.finished()
1533
 
        else:
 
1892
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1893
            except NotImplementedError:
 
1894
                pass
1534
1895
            self.target.fetch(self.source, revision_id=revision_id)
 
1896
        else:
 
1897
            try:
 
1898
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1899
            except NotImplementedError:
 
1900
                pass
 
1901
            # FIXME do not peek!
 
1902
            if self.source.control_files._transport.listable():
 
1903
                pb = ui.ui_factory.nested_progress_bar()
 
1904
                try:
 
1905
                    self.target.weave_store.copy_all_ids(
 
1906
                        self.source.weave_store,
 
1907
                        pb=pb,
 
1908
                        from_transaction=self.source.get_transaction(),
 
1909
                        to_transaction=self.target.get_transaction())
 
1910
                    pb.update('copying inventory', 0, 1)
 
1911
                    self.target.control_weaves.copy_multi(
 
1912
                        self.source.control_weaves, ['inventory'],
 
1913
                        from_transaction=self.source.get_transaction(),
 
1914
                        to_transaction=self.target.get_transaction())
 
1915
                    self.target._revision_store.text_store.copy_all_ids(
 
1916
                        self.source._revision_store.text_store,
 
1917
                        pb=pb)
 
1918
                finally:
 
1919
                    pb.finished()
 
1920
            else:
 
1921
                self.target.fetch(self.source, revision_id=revision_id)
1535
1922
 
1536
1923
    @needs_write_lock
1537
1924
    def fetch(self, revision_id=None, pb=None):
1539
1926
        from bzrlib.fetch import GenericRepoFetcher
1540
1927
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1541
1928
               self.source, self.source._format, self.target, self.target._format)
1542
 
        # TODO: jam 20070210 This should be an assert, not a translate
1543
 
        revision_id = osutils.safe_revision_id(revision_id)
1544
1929
        f = GenericRepoFetcher(to_repository=self.target,
1545
1930
                               from_repository=self.source,
1546
1931
                               last_revision=revision_id,
1592
1977
class InterKnitRepo(InterSameDataRepository):
1593
1978
    """Optimised code paths between Knit based repositories."""
1594
1979
 
1595
 
    @classmethod
1596
 
    def _get_repo_format_to_test(self):
1597
 
        from bzrlib.repofmt import knitrepo
1598
 
        return knitrepo.RepositoryFormatKnit1()
 
1980
    _matching_repo_format = RepositoryFormatKnit1()
 
1981
    """Repository format for testing with."""
1599
1982
 
1600
1983
    @staticmethod
1601
1984
    def is_compatible(source, target):
1605
1988
        could lead to confusing results, and there is no need to be 
1606
1989
        overly general.
1607
1990
        """
1608
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
1609
1991
        try:
1610
1992
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
1611
1993
                    isinstance(target._format, (RepositoryFormatKnit1)))
1618
2000
        from bzrlib.fetch import KnitRepoFetcher
1619
2001
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1620
2002
               self.source, self.source._format, self.target, self.target._format)
1621
 
        # TODO: jam 20070210 This should be an assert, not a translate
1622
 
        revision_id = osutils.safe_revision_id(revision_id)
1623
2003
        f = KnitRepoFetcher(to_repository=self.target,
1624
2004
                            from_repository=self.source,
1625
2005
                            last_revision=revision_id,
1659
2039
 
1660
2040
class InterModel1and2(InterRepository):
1661
2041
 
1662
 
    @classmethod
1663
 
    def _get_repo_format_to_test(self):
1664
 
        return None
 
2042
    _matching_repo_format = None
1665
2043
 
1666
2044
    @staticmethod
1667
2045
    def is_compatible(source, target):
1668
 
        if not source.supports_rich_root() and target.supports_rich_root():
 
2046
        if not isinstance(source, Repository):
 
2047
            return False
 
2048
        if not isinstance(target, Repository):
 
2049
            return False
 
2050
        if not source._format.rich_root_data and target._format.rich_root_data:
1669
2051
            return True
1670
2052
        else:
1671
2053
            return False
1674
2056
    def fetch(self, revision_id=None, pb=None):
1675
2057
        """See InterRepository.fetch()."""
1676
2058
        from bzrlib.fetch import Model1toKnit2Fetcher
1677
 
        # TODO: jam 20070210 This should be an assert, not a translate
1678
 
        revision_id = osutils.safe_revision_id(revision_id)
1679
2059
        f = Model1toKnit2Fetcher(to_repository=self.target,
1680
2060
                                 from_repository=self.source,
1681
2061
                                 last_revision=revision_id,
1683
2063
        return f.count_copied, f.failed_revisions
1684
2064
 
1685
2065
    @needs_write_lock
1686
 
    def copy_content(self, revision_id=None):
 
2066
    def copy_content(self, revision_id=None, basis=None):
1687
2067
        """Make a complete copy of the content in self into destination.
1688
2068
        
1689
2069
        This is a destructive operation! Do not use it on existing 
1691
2071
 
1692
2072
        :param revision_id: Only copy the content needed to construct
1693
2073
                            revision_id and its parents.
 
2074
        :param basis: Copy the needed data preferentially from basis.
1694
2075
        """
1695
2076
        try:
1696
2077
            self.target.set_make_working_trees(self.source.make_working_trees())
1697
2078
        except NotImplementedError:
1698
2079
            pass
1699
 
        # TODO: jam 20070210 Internal, assert, don't translate
1700
 
        revision_id = osutils.safe_revision_id(revision_id)
 
2080
        # grab the basis available data
 
2081
        if basis is not None:
 
2082
            self.target.fetch(basis, revision_id=revision_id)
1701
2083
        # but don't bother fetching if we have the needed data now.
1702
2084
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
1703
2085
            self.target.has_revision(revision_id)):
1707
2089
 
1708
2090
class InterKnit1and2(InterKnitRepo):
1709
2091
 
1710
 
    @classmethod
1711
 
    def _get_repo_format_to_test(self):
1712
 
        return None
 
2092
    _matching_repo_format = None
1713
2093
 
1714
2094
    @staticmethod
1715
2095
    def is_compatible(source, target):
1716
 
        """Be compatible with Knit1 source and Knit3 target"""
1717
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
 
2096
        """Be compatible with Knit1 source and Knit2 target"""
1718
2097
        try:
1719
 
            from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
1720
 
                    RepositoryFormatKnit3
1721
2098
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
1722
 
                    isinstance(target._format, (RepositoryFormatKnit3)))
 
2099
                    isinstance(target._format, (RepositoryFormatKnit2)))
1723
2100
        except AttributeError:
1724
2101
            return False
1725
2102
 
1730
2107
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1731
2108
               self.source, self.source._format, self.target, 
1732
2109
               self.target._format)
1733
 
        # TODO: jam 20070210 This should be an assert, not a translate
1734
 
        revision_id = osutils.safe_revision_id(revision_id)
1735
2110
        f = Knit1to2Fetcher(to_repository=self.target,
1736
2111
                            from_repository=self.source,
1737
2112
                            last_revision=revision_id,
1739
2114
        return f.count_copied, f.failed_revisions
1740
2115
 
1741
2116
 
1742
 
class InterRemoteRepository(InterRepository):
1743
 
    """Code for converting between RemoteRepository objects.
1744
 
 
1745
 
    This just gets an non-remote repository from the RemoteRepository, and calls
1746
 
    InterRepository.get again.
1747
 
    """
1748
 
 
1749
 
    def __init__(self, source, target):
1750
 
        if isinstance(source, remote.RemoteRepository):
1751
 
            source._ensure_real()
1752
 
            real_source = source._real_repository
1753
 
        else:
1754
 
            real_source = source
1755
 
        if isinstance(target, remote.RemoteRepository):
1756
 
            target._ensure_real()
1757
 
            real_target = target._real_repository
1758
 
        else:
1759
 
            real_target = target
1760
 
        self.real_inter = InterRepository.get(real_source, real_target)
1761
 
 
1762
 
    @staticmethod
1763
 
    def is_compatible(source, target):
1764
 
        if isinstance(source, remote.RemoteRepository):
1765
 
            return True
1766
 
        if isinstance(target, remote.RemoteRepository):
1767
 
            return True
1768
 
        return False
1769
 
 
1770
 
    def copy_content(self, revision_id=None):
1771
 
        self.real_inter.copy_content(revision_id=revision_id)
1772
 
 
1773
 
    def fetch(self, revision_id=None, pb=None):
1774
 
        self.real_inter.fetch(revision_id=revision_id, pb=pb)
1775
 
 
1776
 
    @classmethod
1777
 
    def _get_repo_format_to_test(self):
1778
 
        return None
1779
 
 
1780
 
 
1781
2117
InterRepository.register_optimiser(InterSameDataRepository)
1782
2118
InterRepository.register_optimiser(InterWeaveRepo)
1783
2119
InterRepository.register_optimiser(InterKnitRepo)
1784
2120
InterRepository.register_optimiser(InterModel1and2)
1785
2121
InterRepository.register_optimiser(InterKnit1and2)
1786
 
InterRepository.register_optimiser(InterRemoteRepository)
 
2122
 
 
2123
 
 
2124
class RepositoryTestProviderAdapter(object):
 
2125
    """A tool to generate a suite testing multiple repository formats at once.
 
2126
 
 
2127
    This is done by copying the test once for each transport and injecting
 
2128
    the transport_server, transport_readonly_server, and bzrdir_format and
 
2129
    repository_format classes into each copy. Each copy is also given a new id()
 
2130
    to make it easy to identify.
 
2131
    """
 
2132
 
 
2133
    def __init__(self, transport_server, transport_readonly_server, formats):
 
2134
        self._transport_server = transport_server
 
2135
        self._transport_readonly_server = transport_readonly_server
 
2136
        self._formats = formats
 
2137
    
 
2138
    def adapt(self, test):
 
2139
        result = unittest.TestSuite()
 
2140
        for repository_format, bzrdir_format in self._formats:
 
2141
            new_test = deepcopy(test)
 
2142
            new_test.transport_server = self._transport_server
 
2143
            new_test.transport_readonly_server = self._transport_readonly_server
 
2144
            new_test.bzrdir_format = bzrdir_format
 
2145
            new_test.repository_format = repository_format
 
2146
            def make_new_test_id():
 
2147
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
 
2148
                return lambda: new_id
 
2149
            new_test.id = make_new_test_id()
 
2150
            result.addTest(new_test)
 
2151
        return result
 
2152
 
 
2153
 
 
2154
class InterRepositoryTestProviderAdapter(object):
 
2155
    """A tool to generate a suite testing multiple inter repository formats.
 
2156
 
 
2157
    This is done by copying the test once for each interrepo provider and injecting
 
2158
    the transport_server, transport_readonly_server, repository_format and 
 
2159
    repository_to_format classes into each copy.
 
2160
    Each copy is also given a new id() to make it easy to identify.
 
2161
    """
 
2162
 
 
2163
    def __init__(self, transport_server, transport_readonly_server, formats):
 
2164
        self._transport_server = transport_server
 
2165
        self._transport_readonly_server = transport_readonly_server
 
2166
        self._formats = formats
 
2167
    
 
2168
    def adapt(self, test):
 
2169
        result = unittest.TestSuite()
 
2170
        for interrepo_class, repository_format, repository_format_to in self._formats:
 
2171
            new_test = deepcopy(test)
 
2172
            new_test.transport_server = self._transport_server
 
2173
            new_test.transport_readonly_server = self._transport_readonly_server
 
2174
            new_test.interrepo_class = interrepo_class
 
2175
            new_test.repository_format = repository_format
 
2176
            new_test.repository_format_to = repository_format_to
 
2177
            def make_new_test_id():
 
2178
                new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
 
2179
                return lambda: new_id
 
2180
            new_test.id = make_new_test_id()
 
2181
            result.addTest(new_test)
 
2182
        return result
 
2183
 
 
2184
    @staticmethod
 
2185
    def default_test_list():
 
2186
        """Generate the default list of interrepo permutations to test."""
 
2187
        result = []
 
2188
        # test the default InterRepository between format 6 and the current 
 
2189
        # default format.
 
2190
        # XXX: robertc 20060220 reinstate this when there are two supported
 
2191
        # formats which do not have an optimal code path between them.
 
2192
        #result.append((InterRepository,
 
2193
        #               RepositoryFormat6(),
 
2194
        #               RepositoryFormatKnit1()))
 
2195
        for optimiser in InterRepository._optimisers:
 
2196
            if optimiser._matching_repo_format is not None:
 
2197
                result.append((optimiser,
 
2198
                               optimiser._matching_repo_format,
 
2199
                               optimiser._matching_repo_format
 
2200
                               ))
 
2201
        # if there are specific combinations we want to use, we can add them 
 
2202
        # here.
 
2203
        result.append((InterModel1and2, RepositoryFormat5(),
 
2204
                       RepositoryFormatKnit2()))
 
2205
        result.append((InterKnit1and2, RepositoryFormatKnit1(),
 
2206
                       RepositoryFormatKnit2()))
 
2207
        return result
1787
2208
 
1788
2209
 
1789
2210
class CopyConverter(object):
1869
2290
            self._committer = committer
1870
2291
 
1871
2292
        self.new_inventory = Inventory(None)
1872
 
        self._new_revision_id = osutils.safe_revision_id(revision_id)
 
2293
        self._new_revision_id = revision_id
1873
2294
        self.parents = parents
1874
2295
        self.repository = repository
1875
2296
 
1883
2304
        self._timestamp = round(timestamp, 3)
1884
2305
 
1885
2306
        if timezone is None:
1886
 
            self._timezone = osutils.local_time_offset()
 
2307
            self._timezone = local_time_offset()
1887
2308
        else:
1888
2309
            self._timezone = int(timezone)
1889
2310
 
1935
2356
 
1936
2357
    def _gen_revision_id(self):
1937
2358
        """Return new revision-id."""
1938
 
        return generate_ids.gen_revision_id(self._config.username(),
1939
 
                                            self._timestamp)
 
2359
        s = '%s-%s-' % (self._config.user_email(), 
 
2360
                        compact_date(self._timestamp))
 
2361
        s += hexlify(rand_bytes(8))
 
2362
        return s
1940
2363
 
1941
2364
    def _generate_revision_if_needed(self):
1942
2365
        """Create a revision id if None was supplied.
1943
2366
        
1944
2367
        If the repository can not support user-specified revision ids
1945
 
        they should override this function and raise CannotSetRevisionId
 
2368
        they should override this function and raise UnsupportedOperation
1946
2369
        if _new_revision_id is not None.
1947
2370
 
1948
 
        :raises: CannotSetRevisionId
 
2371
        :raises: UnsupportedOperation
1949
2372
        """
1950
2373
        if self._new_revision_id is None:
1951
2374
            self._new_revision_id = self._gen_revision_id()
1997
2420
        :param file_parents: The per-file parent revision ids.
1998
2421
        """
1999
2422
        self._add_text_to_weave(file_id, [], file_parents.keys())
2000
 
 
2001
 
    def modified_reference(self, file_id, file_parents):
2002
 
        """Record the modification of a reference.
2003
 
 
2004
 
        :param file_id: The file_id of the link to record.
2005
 
        :param file_parents: The per-file parent revision ids.
2006
 
        """
2007
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2008
2423
    
2009
2424
    def modified_file_text(self, file_id, file_parents,
2010
2425
                           get_content_byte_lines, text_sha1=None,
2109
2524
 
2110
2525
 
2111
2526
def _unescaper(match, _map=_unescape_map):
2112
 
    code = match.group(1)
2113
 
    try:
2114
 
        return _map[code]
2115
 
    except KeyError:
2116
 
        if not code.startswith('#'):
2117
 
            raise
2118
 
        return unichr(int(code[1:])).encode('utf8')
 
2527
    return _map[match.group(1)]
2119
2528
 
2120
2529
 
2121
2530
_unescape_re = None
2125
2534
    """Unescape predefined XML entities in a string of data."""
2126
2535
    global _unescape_re
2127
2536
    if _unescape_re is None:
2128
 
        _unescape_re = re.compile('\&([^;]*);')
 
2537
        _unescape_re = re.compile('\&([^;]*);')
2129
2538
    return _unescape_re.sub(_unescaper, data)