/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: John Arbash Meinel
  • Date: 2006-09-20 14:51:03 UTC
  • mfrom: (0.8.23 version_info)
  • mto: This revision was merged to the branch mainline in revision 2028.
  • Revision ID: john@arbash-meinel.com-20060920145103-02725c6d6c886040
[merge] version-info plugin, and cleanup for layout in bzr

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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
from binascii import hexlify
 
18
from copy import deepcopy
17
19
from cStringIO import StringIO
18
 
 
19
 
from bzrlib.lazy_import import lazy_import
20
 
lazy_import(globals(), """
21
20
import re
22
21
import time
23
 
import unittest
 
22
from unittest import TestSuite
24
23
 
25
24
from bzrlib import (
26
 
    bzrdir,
27
 
    check,
28
 
    errors,
29
 
    generate_ids,
30
 
    gpg,
31
 
    graph,
32
 
    lazy_regex,
33
 
    lockable_files,
34
 
    lockdir,
 
25
    bzrdir, 
 
26
    check, 
 
27
    delta, 
 
28
    gpg, 
 
29
    errors, 
35
30
    osutils,
36
 
    registry,
37
 
    revision as _mod_revision,
38
 
    symbol_versioning,
39
31
    transactions,
40
 
    ui,
 
32
    ui, 
 
33
    xml5, 
 
34
    xml6,
41
35
    )
 
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
37
from bzrlib.errors import InvalidRevisionId
 
38
from bzrlib.graph import Graph
 
39
from bzrlib.inter import InterObject
 
40
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
 
41
from bzrlib.knit import KnitVersionedFile, KnitPlainFactory
 
42
from bzrlib.lockable_files import LockableFiles, TransportLock
 
43
from bzrlib.lockdir import LockDir
 
44
from bzrlib.osutils import (safe_unicode, rand_bytes, compact_date, 
 
45
                            local_time_offset)
 
46
from bzrlib.revision import NULL_REVISION, Revision
42
47
from bzrlib.revisiontree import RevisionTree
43
 
from bzrlib.store.versioned import VersionedFileStore
 
48
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
44
49
from bzrlib.store.text import TextStore
 
50
from bzrlib import symbol_versioning
 
51
from bzrlib.symbol_versioning import (deprecated_method,
 
52
        zero_nine, 
 
53
        )
45
54
from bzrlib.testament import Testament
46
 
 
47
 
""")
48
 
 
49
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
50
 
from bzrlib.inter import InterObject
51
 
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
52
 
from bzrlib.symbol_versioning import (
53
 
        deprecated_method,
54
 
        zero_nine,
55
 
        )
56
55
from bzrlib.trace import mutter, note, warning
 
56
from bzrlib.tsort import topo_sort
 
57
from bzrlib.weave import WeaveFile
57
58
 
58
59
 
59
60
# Old formats display a warning, but only once
60
61
_deprecation_warning_done = False
61
62
 
62
63
 
63
 
######################################################################
64
 
# Repositories
65
 
 
66
64
class Repository(object):
67
65
    """Repository holding history for one or more branches.
68
66
 
75
73
    remote) disk.
76
74
    """
77
75
 
78
 
    _file_ids_altered_regex = lazy_regex.lazy_compile(
79
 
        r'file_id="(?P<file_id>[^"]+)"'
80
 
        r'.*revision="(?P<revision_id>[^"]+)"'
81
 
        )
82
 
 
83
76
    @needs_write_lock
84
 
    def add_inventory(self, revision_id, inv, parents):
85
 
        """Add the inventory inv to the repository as revision_id.
 
77
    def add_inventory(self, revid, inv, parents):
 
78
        """Add the inventory inv to the repository as revid.
86
79
        
87
 
        :param parents: The revision ids of the parents that revision_id
 
80
        :param parents: The revision ids of the parents that revid
88
81
                        is known to have and are in the repository already.
89
82
 
90
83
        returns the sha1 of the serialized inventory.
91
84
        """
92
 
        revision_id = osutils.safe_revision_id(revision_id)
93
 
        _mod_revision.check_not_reserved_id(revision_id)
94
 
        assert inv.revision_id is None or inv.revision_id == revision_id, \
 
85
        assert inv.revision_id is None or inv.revision_id == revid, \
95
86
            "Mismatch between inventory revision" \
96
 
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
 
87
            " id and insertion revid (%r, %r)" % (inv.revision_id, revid)
97
88
        assert inv.root is not None
98
89
        inv_text = self.serialise_inventory(inv)
99
90
        inv_sha1 = osutils.sha_string(inv_text)
100
91
        inv_vf = self.control_weaves.get_weave('inventory',
101
92
                                               self.get_transaction())
102
 
        self._inventory_add_lines(inv_vf, revision_id, parents,
103
 
                                  osutils.split_lines(inv_text))
 
93
        self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
104
94
        return inv_sha1
105
95
 
106
 
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines):
 
96
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
107
97
        final_parents = []
108
98
        for parent in parents:
109
99
            if parent in inv_vf:
110
100
                final_parents.append(parent)
111
101
 
112
 
        inv_vf.add_lines(revision_id, final_parents, lines)
 
102
        inv_vf.add_lines(revid, final_parents, lines)
113
103
 
114
104
    @needs_write_lock
115
 
    def add_revision(self, revision_id, rev, inv=None, config=None):
116
 
        """Add rev to the revision store as revision_id.
 
105
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
106
        """Add rev to the revision store as rev_id.
117
107
 
118
 
        :param revision_id: the revision id to use.
 
108
        :param rev_id: the revision id to use.
119
109
        :param rev: The revision object.
120
110
        :param inv: The inventory for the revision. if None, it will be looked
121
111
                    up in the inventory storer
123
113
                       If supplied its signature_needed method will be used
124
114
                       to determine if a signature should be made.
125
115
        """
126
 
        revision_id = osutils.safe_revision_id(revision_id)
127
 
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
128
 
        #       rev.parent_ids?
129
 
        _mod_revision.check_not_reserved_id(revision_id)
130
116
        if config is not None and config.signature_needed():
131
117
            if inv is None:
132
 
                inv = self.get_inventory(revision_id)
 
118
                inv = self.get_inventory(rev_id)
133
119
            plaintext = Testament(rev, inv).as_short_text()
134
120
            self.store_revision_signature(
135
 
                gpg.GPGStrategy(config), plaintext, revision_id)
136
 
        if not revision_id in self.get_inventory_weave():
 
121
                gpg.GPGStrategy(config), plaintext, rev_id)
 
122
        if not rev_id in self.get_inventory_weave():
137
123
            if inv is None:
138
 
                raise errors.WeaveRevisionNotPresent(revision_id,
 
124
                raise errors.WeaveRevisionNotPresent(rev_id,
139
125
                                                     self.get_inventory_weave())
140
126
            else:
141
127
                # yes, this is not suitable for adding with ghosts.
142
 
                self.add_inventory(revision_id, inv, rev.parent_ids)
 
128
                self.add_inventory(rev_id, inv, rev.parent_ids)
143
129
        self._revision_store.add_revision(rev, self.get_transaction())
144
130
 
145
131
    @needs_read_lock
167
153
        if self._revision_store.text_store.listable():
168
154
            return self._revision_store.all_revision_ids(self.get_transaction())
169
155
        result = self._all_possible_ids()
170
 
        # TODO: jam 20070210 Ensure that _all_possible_ids returns non-unicode
171
 
        #       ids. (It should, since _revision_store's API should change to
172
 
        #       return utf8 revision_ids)
173
156
        return self._eliminate_revisions_not_present(result)
174
157
 
175
158
    def break_lock(self):
223
206
        # TODO: make sure to construct the right store classes, etc, depending
224
207
        # on whether escaping is required.
225
208
        self._warn_if_deprecated()
 
209
        self._serializer = xml5.serializer_v5
226
210
 
227
211
    def __repr__(self):
228
212
        return '%s(%r)' % (self.__class__.__name__, 
231
215
    def is_locked(self):
232
216
        return self.control_files.is_locked()
233
217
 
234
 
    def lock_write(self, token=None):
235
 
        """Lock this repository for writing.
236
 
        
237
 
        :param token: if this is already locked, then lock_write will fail
238
 
            unless the token matches the existing lock.
239
 
        :returns: a token if this instance supports tokens, otherwise None.
240
 
        :raises TokenLockingNotSupported: when a token is given but this
241
 
            instance doesn't support using token locks.
242
 
        :raises MismatchedToken: if the specified token doesn't match the token
243
 
            of the existing lock.
244
 
 
245
 
        A token should be passed in if you know that you have locked the object
246
 
        some other way, and need to synchronise this object's state with that
247
 
        fact.
248
 
 
249
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
250
 
        """
251
 
        return self.control_files.lock_write(token=token)
 
218
    def lock_write(self):
 
219
        self.control_files.lock_write()
252
220
 
253
221
    def lock_read(self):
254
222
        self.control_files.lock_read()
256
224
    def get_physical_lock_status(self):
257
225
        return self.control_files.get_physical_lock_status()
258
226
 
259
 
    def leave_lock_in_place(self):
260
 
        """Tell this repository not to release the physical lock when this
261
 
        object is unlocked.
262
 
        
263
 
        If lock_write doesn't return a token, then this method is not supported.
264
 
        """
265
 
        self.control_files.leave_in_place()
266
 
 
267
 
    def dont_leave_lock_in_place(self):
268
 
        """Tell this repository to release the physical lock when this
269
 
        object is unlocked, even if it didn't originally acquire it.
270
 
 
271
 
        If lock_write doesn't return a token, then this method is not supported.
272
 
        """
273
 
        self.control_files.dont_leave_in_place()
274
 
 
275
 
    @needs_read_lock
276
 
    def gather_stats(self, revid=None, committers=None):
277
 
        """Gather statistics from a revision id.
278
 
 
279
 
        :param revid: The revision id to gather statistics from, if None, then
280
 
            no revision specific statistics are gathered.
281
 
        :param committers: Optional parameter controlling whether to grab
282
 
            a count of committers from the revision specific statistics.
283
 
        :return: A dictionary of statistics. Currently this contains:
284
 
            committers: The number of committers if requested.
285
 
            firstrev: A tuple with timestamp, timezone for the penultimate left
286
 
                most ancestor of revid, if revid is not the NULL_REVISION.
287
 
            latestrev: A tuple with timestamp, timezone for revid, if revid is
288
 
                not the NULL_REVISION.
289
 
            revisions: The total revision count in the repository.
290
 
            size: An estimate disk size of the repository in bytes.
291
 
        """
292
 
        result = {}
293
 
        if revid and committers:
294
 
            result['committers'] = 0
295
 
        if revid and revid != _mod_revision.NULL_REVISION:
296
 
            if committers:
297
 
                all_committers = set()
298
 
            revisions = self.get_ancestry(revid)
299
 
            # pop the leading None
300
 
            revisions.pop(0)
301
 
            first_revision = None
302
 
            if not committers:
303
 
                # ignore the revisions in the middle - just grab first and last
304
 
                revisions = revisions[0], revisions[-1]
305
 
            for revision in self.get_revisions(revisions):
306
 
                if not first_revision:
307
 
                    first_revision = revision
308
 
                if committers:
309
 
                    all_committers.add(revision.committer)
310
 
            last_revision = revision
311
 
            if committers:
312
 
                result['committers'] = len(all_committers)
313
 
            result['firstrev'] = (first_revision.timestamp,
314
 
                first_revision.timezone)
315
 
            result['latestrev'] = (last_revision.timestamp,
316
 
                last_revision.timezone)
317
 
 
318
 
        # now gather global repository information
319
 
        if self.bzrdir.root_transport.listable():
320
 
            c, t = self._revision_store.total_size(self.get_transaction())
321
 
            result['revisions'] = c
322
 
            result['size'] = t
323
 
        return result
324
 
 
325
227
    @needs_read_lock
326
228
    def missing_revision_ids(self, other, revision_id=None):
327
229
        """Return the revision ids that other has that this does not.
330
232
 
331
233
        revision_id: only return revision ids included by revision_id.
332
234
        """
333
 
        revision_id = osutils.safe_revision_id(revision_id)
334
235
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
335
236
 
336
237
    @staticmethod
343
244
        control = bzrdir.BzrDir.open(base)
344
245
        return control.open_repository()
345
246
 
346
 
    def copy_content_into(self, destination, revision_id=None):
 
247
    def copy_content_into(self, destination, revision_id=None, basis=None):
347
248
        """Make a complete copy of the content in self into destination.
348
249
        
349
250
        This is a destructive operation! Do not use it on existing 
350
251
        repositories.
351
252
        """
352
 
        revision_id = osutils.safe_revision_id(revision_id)
353
 
        return InterRepository.get(self, destination).copy_content(revision_id)
 
253
        return InterRepository.get(self, destination).copy_content(revision_id, basis)
354
254
 
355
255
    def fetch(self, source, revision_id=None, pb=None):
356
256
        """Fetch the content required to construct revision_id from source.
357
257
 
358
258
        If revision_id is None all content is copied.
359
259
        """
360
 
        revision_id = osutils.safe_revision_id(revision_id)
361
 
        inter = InterRepository.get(source, self)
362
 
        try:
363
 
            return inter.fetch(revision_id=revision_id, pb=pb)
364
 
        except NotImplementedError:
365
 
            raise errors.IncompatibleRepositories(source, self)
 
260
        return InterRepository.get(source, self).fetch(revision_id=revision_id,
 
261
                                                       pb=pb)
366
262
 
367
263
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
368
264
                           timezone=None, committer=None, revprops=None, 
378
274
        :param revprops: Optional dictionary of revision properties.
379
275
        :param revision_id: Optional revision id.
380
276
        """
381
 
        revision_id = osutils.safe_revision_id(revision_id)
382
277
        return _CommitBuilder(self, parents, config, timestamp, timezone,
383
278
                              committer, revprops, revision_id)
384
279
 
386
281
        self.control_files.unlock()
387
282
 
388
283
    @needs_read_lock
389
 
    def clone(self, a_bzrdir, revision_id=None):
 
284
    def clone(self, a_bzrdir, revision_id=None, basis=None):
390
285
        """Clone this repository into a_bzrdir using the current format.
391
286
 
392
287
        Currently no check is made that the format of this repository and
393
288
        the bzrdir format are compatible. FIXME RBC 20060201.
394
 
 
395
 
        :return: The newly created destination repository.
396
289
        """
397
290
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
398
291
            # use target default format.
399
 
            dest_repo = a_bzrdir.create_repository()
 
292
            result = a_bzrdir.create_repository()
 
293
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
 
294
        elif isinstance(a_bzrdir._format,
 
295
                      (bzrdir.BzrDirFormat4,
 
296
                       bzrdir.BzrDirFormat5,
 
297
                       bzrdir.BzrDirFormat6)):
 
298
            result = a_bzrdir.open_repository()
400
299
        else:
401
 
            # Most control formats need the repository to be specifically
402
 
            # created, but on some old all-in-one formats it's not needed
403
 
            try:
404
 
                dest_repo = self._format.initialize(a_bzrdir, shared=self.is_shared())
405
 
            except errors.UninitializableFormat:
406
 
                dest_repo = a_bzrdir.open_repository()
407
 
        self.copy_content_into(dest_repo, revision_id)
408
 
        return dest_repo
 
300
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
 
301
        self.copy_content_into(result, revision_id, basis)
 
302
        return result
409
303
 
410
304
    @needs_read_lock
411
305
    def has_revision(self, revision_id):
412
306
        """True if this repository has a copy of the revision."""
413
 
        revision_id = osutils.safe_revision_id(revision_id)
414
307
        return self._revision_store.has_revision_id(revision_id,
415
308
                                                    self.get_transaction())
416
309
 
424
317
        or testing the revision graph.
425
318
        """
426
319
        if not revision_id or not isinstance(revision_id, basestring):
427
 
            raise errors.InvalidRevisionId(revision_id=revision_id,
428
 
                                           branch=self)
429
 
        return self.get_revisions([revision_id])[0]
430
 
 
 
320
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
321
        return self._revision_store.get_revisions([revision_id],
 
322
                                                  self.get_transaction())[0]
431
323
    @needs_read_lock
432
324
    def get_revisions(self, revision_ids):
433
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
434
 
        revs = self._revision_store.get_revisions(revision_ids,
 
325
        return self._revision_store.get_revisions(revision_ids,
435
326
                                                  self.get_transaction())
436
 
        for rev in revs:
437
 
            assert not isinstance(rev.revision_id, unicode)
438
 
            for parent_id in rev.parent_ids:
439
 
                assert not isinstance(parent_id, unicode)
440
 
        return revs
441
327
 
442
328
    @needs_read_lock
443
329
    def get_revision_xml(self, revision_id):
444
 
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
445
 
        #       would have already do it.
446
 
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
447
 
        revision_id = osutils.safe_revision_id(revision_id)
448
 
        rev = self.get_revision(revision_id)
 
330
        rev = self.get_revision(revision_id) 
449
331
        rev_tmp = StringIO()
450
332
        # the current serializer..
451
333
        self._revision_store._serializer.write_revision(rev, rev_tmp)
455
337
    @needs_read_lock
456
338
    def get_revision(self, revision_id):
457
339
        """Return the Revision object for a named revision"""
458
 
        # TODO: jam 20070210 get_revision_reconcile should do this for us
459
 
        revision_id = osutils.safe_revision_id(revision_id)
460
340
        r = self.get_revision_reconcile(revision_id)
461
341
        # weave corruption can lead to absent revision markers that should be
462
342
        # present.
518
398
 
519
399
    @needs_write_lock
520
400
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
521
 
        revision_id = osutils.safe_revision_id(revision_id)
522
401
        signature = gpg_strategy.sign(plaintext)
523
402
        self._revision_store.add_revision_signature_text(revision_id,
524
403
                                                         signature,
535
414
        assert self._serializer.support_altered_by_hack, \
536
415
            ("fileids_altered_by_revision_ids only supported for branches " 
537
416
             "which store inventory as unnested xml, not on %r" % self)
538
 
        selected_revision_ids = set(osutils.safe_revision_id(r)
539
 
                                    for r in revision_ids)
 
417
        selected_revision_ids = set(revision_ids)
540
418
        w = self.get_inventory_weave()
541
419
        result = {}
542
420
 
548
426
        # revisions. We don't need to see all lines in the inventory because
549
427
        # only those added in an inventory in rev X can contain a revision=X
550
428
        # line.
551
 
        unescape_revid_cache = {}
552
 
        unescape_fileid_cache = {}
553
 
 
554
 
        # jam 20061218 In a big fetch, this handles hundreds of thousands
555
 
        # of lines, so it has had a lot of inlining and optimizing done.
556
 
        # Sorry that it is a little bit messy.
557
 
        # Move several functions to be local variables, since this is a long
558
 
        # running loop.
559
 
        search = self._file_ids_altered_regex.search
560
 
        unescape = _unescape_xml
561
 
        setdefault = result.setdefault
562
 
        pb = ui.ui_factory.nested_progress_bar()
563
 
        try:
564
 
            for line in w.iter_lines_added_or_present_in_versions(
565
 
                                        selected_revision_ids, pb=pb):
566
 
                match = search(line)
567
 
                if match is None:
568
 
                    continue
569
 
                # One call to match.group() returning multiple items is quite a
570
 
                # bit faster than 2 calls to match.group() each returning 1
571
 
                file_id, revision_id = match.group('file_id', 'revision_id')
572
 
 
573
 
                # Inlining the cache lookups helps a lot when you make 170,000
574
 
                # lines and 350k ids, versus 8.4 unique ids.
575
 
                # Using a cache helps in 2 ways:
576
 
                #   1) Avoids unnecessary decoding calls
577
 
                #   2) Re-uses cached strings, which helps in future set and
578
 
                #      equality checks.
579
 
                # (2) is enough that removing encoding entirely along with
580
 
                # the cache (so we are using plain strings) results in no
581
 
                # performance improvement.
582
 
                try:
583
 
                    revision_id = unescape_revid_cache[revision_id]
584
 
                except KeyError:
585
 
                    unescaped = unescape(revision_id)
586
 
                    unescape_revid_cache[revision_id] = unescaped
587
 
                    revision_id = unescaped
588
 
 
589
 
                if revision_id in selected_revision_ids:
590
 
                    try:
591
 
                        file_id = unescape_fileid_cache[file_id]
592
 
                    except KeyError:
593
 
                        unescaped = unescape(file_id)
594
 
                        unescape_fileid_cache[file_id] = unescaped
595
 
                        file_id = unescaped
596
 
                    setdefault(file_id, set()).add(revision_id)
597
 
        finally:
598
 
            pb.finished()
 
429
        for line in w.iter_lines_added_or_present_in_versions(selected_revision_ids):
 
430
            start = line.find('file_id="')+9
 
431
            if start < 9: continue
 
432
            end = line.find('"', start)
 
433
            assert end>= 0
 
434
            file_id = _unescape_xml(line[start:end])
 
435
 
 
436
            start = line.find('revision="')+10
 
437
            if start < 10: continue
 
438
            end = line.find('"', start)
 
439
            assert end>= 0
 
440
            revision_id = _unescape_xml(line[start:end])
 
441
            if revision_id in selected_revision_ids:
 
442
                result.setdefault(file_id, set()).add(revision_id)
599
443
        return result
600
444
 
601
445
    @needs_read_lock
606
450
    @needs_read_lock
607
451
    def get_inventory(self, revision_id):
608
452
        """Get Inventory object by hash."""
609
 
        # TODO: jam 20070210 Technically we don't need to sanitize, since all
610
 
        #       called functions must sanitize.
611
 
        revision_id = osutils.safe_revision_id(revision_id)
612
453
        return self.deserialise_inventory(
613
454
            revision_id, self.get_inventory_xml(revision_id))
614
455
 
618
459
        :param revision_id: The expected revision id of the inventory.
619
460
        :param xml: A serialised inventory.
620
461
        """
621
 
        revision_id = osutils.safe_revision_id(revision_id)
622
462
        result = self._serializer.read_inventory_from_string(xml)
623
463
        result.root.revision = revision_id
624
464
        return result
629
469
    @needs_read_lock
630
470
    def get_inventory_xml(self, revision_id):
631
471
        """Get inventory XML as a file object."""
632
 
        revision_id = osutils.safe_revision_id(revision_id)
633
472
        try:
634
 
            assert isinstance(revision_id, str), type(revision_id)
 
473
            assert isinstance(revision_id, basestring), type(revision_id)
635
474
            iw = self.get_inventory_weave()
636
475
            return iw.get_text(revision_id)
637
476
        except IndexError:
641
480
    def get_inventory_sha1(self, revision_id):
642
481
        """Return the sha1 hash of the inventory entry
643
482
        """
644
 
        # TODO: jam 20070210 Shouldn't this be deprecated / removed?
645
 
        revision_id = osutils.safe_revision_id(revision_id)
646
483
        return self.get_revision(revision_id).inventory_sha1
647
484
 
648
485
    @needs_read_lock
655
492
        :return: a dictionary of revision_id->revision_parents_list.
656
493
        """
657
494
        # special case NULL_REVISION
658
 
        if revision_id == _mod_revision.NULL_REVISION:
 
495
        if revision_id == NULL_REVISION:
659
496
            return {}
660
 
        revision_id = osutils.safe_revision_id(revision_id)
661
 
        a_weave = self.get_inventory_weave()
662
 
        all_revisions = self._eliminate_revisions_not_present(
663
 
                                a_weave.versions())
664
 
        entire_graph = dict([(node, a_weave.get_parents(node)) for 
 
497
        weave = self.get_inventory_weave()
 
498
        all_revisions = self._eliminate_revisions_not_present(weave.versions())
 
499
        entire_graph = dict([(node, weave.get_parents(node)) for 
665
500
                             node in all_revisions])
666
501
        if revision_id is None:
667
502
            return entire_graph
686
521
        :param revision_ids: an iterable of revisions to graph or None for all.
687
522
        :return: a Graph object with the graph reachable from revision_ids.
688
523
        """
689
 
        result = graph.Graph()
 
524
        result = Graph()
690
525
        if not revision_ids:
691
526
            pending = set(self.all_revision_ids())
692
527
            required = set([])
693
528
        else:
694
 
            pending = set(osutils.safe_revision_id(r) for r in revision_ids)
 
529
            pending = set(revision_ids)
695
530
            # special case NULL_REVISION
696
 
            if _mod_revision.NULL_REVISION in pending:
697
 
                pending.remove(_mod_revision.NULL_REVISION)
 
531
            if NULL_REVISION in pending:
 
532
                pending.remove(NULL_REVISION)
698
533
            required = set(pending)
699
534
        done = set([])
700
535
        while len(pending):
717
552
            done.add(revision_id)
718
553
        return result
719
554
 
720
 
    def _get_history_vf(self):
721
 
        """Get a versionedfile whose history graph reflects all revisions.
722
 
 
723
 
        For weave repositories, this is the inventory weave.
724
 
        """
725
 
        return self.get_inventory_weave()
726
 
 
727
 
    def iter_reverse_revision_history(self, revision_id):
728
 
        """Iterate backwards through revision ids in the lefthand history
729
 
 
730
 
        :param revision_id: The revision id to start with.  All its lefthand
731
 
            ancestors will be traversed.
732
 
        """
733
 
        revision_id = osutils.safe_revision_id(revision_id)
734
 
        if revision_id in (None, _mod_revision.NULL_REVISION):
735
 
            return
736
 
        next_id = revision_id
737
 
        versionedfile = self._get_history_vf()
738
 
        while True:
739
 
            yield next_id
740
 
            parents = versionedfile.get_parents(next_id)
741
 
            if len(parents) == 0:
742
 
                return
743
 
            else:
744
 
                next_id = parents[0]
745
 
 
746
555
    @needs_read_lock
747
556
    def get_revision_inventory(self, revision_id):
748
557
        """Return inventory of a past revision."""
780
589
        """
781
590
        # TODO: refactor this to use an existing revision object
782
591
        # so we don't need to read it in twice.
783
 
        if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
784
 
            return RevisionTree(self, Inventory(root_id=None), 
785
 
                                _mod_revision.NULL_REVISION)
 
592
        if revision_id is None or revision_id == NULL_REVISION:
 
593
            return RevisionTree(self, Inventory(), NULL_REVISION)
786
594
        else:
787
 
            revision_id = osutils.safe_revision_id(revision_id)
788
595
            inv = self.get_revision_inventory(revision_id)
789
596
            return RevisionTree(self, inv, revision_id)
790
597
 
794
601
 
795
602
        `revision_id` may not be None or 'null:'"""
796
603
        assert None not in revision_ids
797
 
        assert _mod_revision.NULL_REVISION not in revision_ids
 
604
        assert NULL_REVISION not in revision_ids
798
605
        texts = self.get_inventory_weave().get_texts(revision_ids)
799
606
        for text, revision_id in zip(texts, revision_ids):
800
607
            inv = self.deserialise_inventory(revision_id, text)
812
619
        """
813
620
        if revision_id is None:
814
621
            return [None]
815
 
        revision_id = osutils.safe_revision_id(revision_id)
816
622
        if not self.has_revision(revision_id):
817
623
            raise errors.NoSuchRevision(self, revision_id)
818
624
        w = self.get_inventory_weave()
827
633
        - it writes to stdout, it assumes that that is valid etc. Fix
828
634
        by creating a new more flexible convenience function.
829
635
        """
830
 
        revision_id = osutils.safe_revision_id(revision_id)
831
636
        tree = self.revision_tree(revision_id)
832
637
        # use inventory as it was in that revision
833
638
        file_id = tree.inventory.path2id(file)
841
646
    def get_transaction(self):
842
647
        return self.control_files.get_transaction()
843
648
 
844
 
    def revision_parents(self, revision_id):
845
 
        revision_id = osutils.safe_revision_id(revision_id)
846
 
        return self.get_inventory_weave().parent_names(revision_id)
 
649
    def revision_parents(self, revid):
 
650
        return self.get_inventory_weave().parent_names(revid)
847
651
 
848
652
    @needs_write_lock
849
653
    def set_make_working_trees(self, new_value):
863
667
 
864
668
    @needs_write_lock
865
669
    def sign_revision(self, revision_id, gpg_strategy):
866
 
        revision_id = osutils.safe_revision_id(revision_id)
867
670
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
868
671
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
869
672
 
870
673
    @needs_read_lock
871
674
    def has_signature_for_revision_id(self, revision_id):
872
675
        """Query for a revision signature for revision_id in the repository."""
873
 
        revision_id = osutils.safe_revision_id(revision_id)
874
676
        return self._revision_store.has_signature(revision_id,
875
677
                                                  self.get_transaction())
876
678
 
877
679
    @needs_read_lock
878
680
    def get_signature_text(self, revision_id):
879
681
        """Return the text for a signature."""
880
 
        revision_id = osutils.safe_revision_id(revision_id)
881
682
        return self._revision_store.get_signature_text(revision_id,
882
683
                                                       self.get_transaction())
883
684
 
893
694
        if not revision_ids:
894
695
            raise ValueError("revision_ids must be non-empty in %s.check" 
895
696
                    % (self,))
896
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
897
697
        return self._check(revision_ids)
898
698
 
899
699
    def _check(self, revision_ids):
909
709
        warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
910
710
                % (self._format, self.bzrdir.transport.base))
911
711
 
912
 
    def supports_rich_root(self):
913
 
        return self._format.rich_root_data
914
 
 
915
 
    def _check_ascii_revisionid(self, revision_id, method):
916
 
        """Private helper for ascii-only repositories."""
917
 
        # weave repositories refuse to store revisionids that are non-ascii.
918
 
        if revision_id is not None:
919
 
            # weaves require ascii revision ids.
920
 
            if isinstance(revision_id, unicode):
921
 
                try:
922
 
                    revision_id.encode('ascii')
923
 
                except UnicodeEncodeError:
924
 
                    raise errors.NonAsciiRevisionId(method, self)
925
 
            else:
926
 
                try:
927
 
                    revision_id.decode('ascii')
928
 
                except UnicodeDecodeError:
929
 
                    raise errors.NonAsciiRevisionId(method, self)
930
 
 
931
 
 
932
 
 
933
 
# remove these delegates a while after bzr 0.15
934
 
def __make_delegated(name, from_module):
935
 
    def _deprecated_repository_forwarder():
936
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
937
 
            % (name, from_module),
938
 
            DeprecationWarning,
939
 
            stacklevel=2)
940
 
        m = __import__(from_module, globals(), locals(), [name])
941
 
        try:
942
 
            return getattr(m, name)
943
 
        except AttributeError:
944
 
            raise AttributeError('module %s has no name %s'
945
 
                    % (m, name))
946
 
    globals()[name] = _deprecated_repository_forwarder
947
 
 
948
 
for _name in [
949
 
        'AllInOneRepository',
950
 
        'WeaveMetaDirRepository',
951
 
        'PreSplitOutRepositoryFormat',
952
 
        'RepositoryFormat4',
953
 
        'RepositoryFormat5',
954
 
        'RepositoryFormat6',
955
 
        'RepositoryFormat7',
956
 
        ]:
957
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
958
 
 
959
 
for _name in [
960
 
        'KnitRepository',
961
 
        'RepositoryFormatKnit',
962
 
        'RepositoryFormatKnit1',
963
 
        ]:
964
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
 
712
 
 
713
class AllInOneRepository(Repository):
 
714
    """Legacy support - the repository behaviour for all-in-one branches."""
 
715
 
 
716
    def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
 
717
        # we reuse one control files instance.
 
718
        dir_mode = a_bzrdir._control_files._dir_mode
 
719
        file_mode = a_bzrdir._control_files._file_mode
 
720
 
 
721
        def get_store(name, compressed=True, prefixed=False):
 
722
            # FIXME: This approach of assuming stores are all entirely compressed
 
723
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
724
            # some existing branches where there's a mixture; we probably 
 
725
            # still want the option to look for both.
 
726
            relpath = a_bzrdir._control_files._escape(name)
 
727
            store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
 
728
                              prefixed=prefixed, compressed=compressed,
 
729
                              dir_mode=dir_mode,
 
730
                              file_mode=file_mode)
 
731
            #if self._transport.should_cache():
 
732
            #    cache_path = os.path.join(self.cache_root, name)
 
733
            #    os.mkdir(cache_path)
 
734
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
735
            return store
 
736
 
 
737
        # not broken out yet because the controlweaves|inventory_store
 
738
        # and text_store | weave_store bits are still different.
 
739
        if isinstance(_format, RepositoryFormat4):
 
740
            # cannot remove these - there is still no consistent api 
 
741
            # which allows access to this old info.
 
742
            self.inventory_store = get_store('inventory-store')
 
743
            text_store = get_store('text-store')
 
744
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
 
745
 
 
746
    @needs_read_lock
 
747
    def is_shared(self):
 
748
        """AllInOne repositories cannot be shared."""
 
749
        return False
 
750
 
 
751
    @needs_write_lock
 
752
    def set_make_working_trees(self, new_value):
 
753
        """Set the policy flag for making working trees when creating branches.
 
754
 
 
755
        This only applies to branches that use this repository.
 
756
 
 
757
        The default is 'True'.
 
758
        :param new_value: True to restore the default, False to disable making
 
759
                          working trees.
 
760
        """
 
761
        raise NotImplementedError(self.set_make_working_trees)
 
762
    
 
763
    def make_working_trees(self):
 
764
        """Returns the policy for making working trees on new branches."""
 
765
        return True
965
766
 
966
767
 
967
768
def install_revision(repository, rev, revision_tree):
976
777
            parent_trees[p_id] = repository.revision_tree(None)
977
778
 
978
779
    inv = revision_tree.inventory
 
780
    
 
781
    # backwards compatability hack: skip the root id.
979
782
    entries = inv.iter_entries()
980
 
    # backwards compatability hack: skip the root id.
981
 
    if not repository.supports_rich_root():
982
 
        path, root = entries.next()
983
 
        if root.revision != rev.revision_id:
984
 
            raise errors.IncompatibleRevision(repr(repository))
 
783
    entries.next()
985
784
    # Add the texts that are not already present
986
785
    for path, ie in entries:
987
786
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
1053
852
        return not self.control_files._transport.has('no-working-trees')
1054
853
 
1055
854
 
1056
 
class RepositoryFormatRegistry(registry.Registry):
1057
 
    """Registry of RepositoryFormats.
1058
 
    """
1059
 
 
1060
 
    def get(self, format_string):
1061
 
        r = registry.Registry.get(self, format_string)
1062
 
        if callable(r):
1063
 
            r = r()
1064
 
        return r
 
855
class KnitRepository(MetaDirRepository):
 
856
    """Knit format repository."""
 
857
 
 
858
    def _warn_if_deprecated(self):
 
859
        # This class isn't deprecated
 
860
        pass
 
861
 
 
862
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
863
        inv_vf.add_lines_with_ghosts(revid, parents, lines)
 
864
 
 
865
    @needs_read_lock
 
866
    def _all_revision_ids(self):
 
867
        """See Repository.all_revision_ids()."""
 
868
        # Knits get the revision graph from the index of the revision knit, so
 
869
        # it's always possible even if they're on an unlistable transport.
 
870
        return self._revision_store.all_revision_ids(self.get_transaction())
 
871
 
 
872
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
873
        """Find file_id(s) which are involved in the changes between revisions.
 
874
 
 
875
        This determines the set of revisions which are involved, and then
 
876
        finds all file ids affected by those revisions.
 
877
        """
 
878
        vf = self._get_revision_vf()
 
879
        from_set = set(vf.get_ancestry(from_revid))
 
880
        to_set = set(vf.get_ancestry(to_revid))
 
881
        changed = to_set.difference(from_set)
 
882
        return self._fileid_involved_by_set(changed)
 
883
 
 
884
    def fileid_involved(self, last_revid=None):
 
885
        """Find all file_ids modified in the ancestry of last_revid.
 
886
 
 
887
        :param last_revid: If None, last_revision() will be used.
 
888
        """
 
889
        if not last_revid:
 
890
            changed = set(self.all_revision_ids())
 
891
        else:
 
892
            changed = set(self.get_ancestry(last_revid))
 
893
        if None in changed:
 
894
            changed.remove(None)
 
895
        return self._fileid_involved_by_set(changed)
 
896
 
 
897
    @needs_read_lock
 
898
    def get_ancestry(self, revision_id):
 
899
        """Return a list of revision-ids integrated by a revision.
 
900
        
 
901
        This is topologically sorted.
 
902
        """
 
903
        if revision_id is None:
 
904
            return [None]
 
905
        vf = self._get_revision_vf()
 
906
        try:
 
907
            return [None] + vf.get_ancestry(revision_id)
 
908
        except errors.RevisionNotPresent:
 
909
            raise errors.NoSuchRevision(self, revision_id)
 
910
 
 
911
    @needs_read_lock
 
912
    def get_revision(self, revision_id):
 
913
        """Return the Revision object for a named revision"""
 
914
        return self.get_revision_reconcile(revision_id)
 
915
 
 
916
    @needs_read_lock
 
917
    def get_revision_graph(self, revision_id=None):
 
918
        """Return a dictionary containing the revision graph.
 
919
 
 
920
        :param revision_id: The revision_id to get a graph from. If None, then
 
921
        the entire revision graph is returned. This is a deprecated mode of
 
922
        operation and will be removed in the future.
 
923
        :return: a dictionary of revision_id->revision_parents_list.
 
924
        """
 
925
        # special case NULL_REVISION
 
926
        if revision_id == NULL_REVISION:
 
927
            return {}
 
928
        weave = self._get_revision_vf()
 
929
        entire_graph = weave.get_graph()
 
930
        if revision_id is None:
 
931
            return weave.get_graph()
 
932
        elif revision_id not in weave:
 
933
            raise errors.NoSuchRevision(self, revision_id)
 
934
        else:
 
935
            # add what can be reached from revision_id
 
936
            result = {}
 
937
            pending = set([revision_id])
 
938
            while len(pending) > 0:
 
939
                node = pending.pop()
 
940
                result[node] = weave.get_parents(node)
 
941
                for revision_id in result[node]:
 
942
                    if revision_id not in result:
 
943
                        pending.add(revision_id)
 
944
            return result
 
945
 
 
946
    @needs_read_lock
 
947
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
948
        """Return a graph of the revisions with ghosts marked as applicable.
 
949
 
 
950
        :param revision_ids: an iterable of revisions to graph or None for all.
 
951
        :return: a Graph object with the graph reachable from revision_ids.
 
952
        """
 
953
        result = Graph()
 
954
        vf = self._get_revision_vf()
 
955
        versions = set(vf.versions())
 
956
        if not revision_ids:
 
957
            pending = set(self.all_revision_ids())
 
958
            required = set([])
 
959
        else:
 
960
            pending = set(revision_ids)
 
961
            # special case NULL_REVISION
 
962
            if NULL_REVISION in pending:
 
963
                pending.remove(NULL_REVISION)
 
964
            required = set(pending)
 
965
        done = set([])
 
966
        while len(pending):
 
967
            revision_id = pending.pop()
 
968
            if not revision_id in versions:
 
969
                if revision_id in required:
 
970
                    raise errors.NoSuchRevision(self, revision_id)
 
971
                # a ghost
 
972
                result.add_ghost(revision_id)
 
973
                # mark it as done so we don't try for it again.
 
974
                done.add(revision_id)
 
975
                continue
 
976
            parent_ids = vf.get_parents_with_ghosts(revision_id)
 
977
            for parent_id in parent_ids:
 
978
                # is this queued or done ?
 
979
                if (parent_id not in pending and
 
980
                    parent_id not in done):
 
981
                    # no, queue it.
 
982
                    pending.add(parent_id)
 
983
            result.add_node(revision_id, parent_ids)
 
984
            done.add(revision_id)
 
985
        return result
 
986
 
 
987
    def _get_revision_vf(self):
 
988
        """:return: a versioned file containing the revisions."""
 
989
        vf = self._revision_store.get_revision_file(self.get_transaction())
 
990
        return vf
 
991
 
 
992
    @needs_write_lock
 
993
    def reconcile(self, other=None, thorough=False):
 
994
        """Reconcile this repository."""
 
995
        from bzrlib.reconcile import KnitReconciler
 
996
        reconciler = KnitReconciler(self, thorough=thorough)
 
997
        reconciler.reconcile()
 
998
        return reconciler
1065
999
    
1066
 
 
1067
 
format_registry = RepositoryFormatRegistry()
1068
 
"""Registry of formats, indexed by their identifying format string.
1069
 
 
1070
 
This can contain either format instances themselves, or classes/factories that
1071
 
can be called to obtain one.
1072
 
"""
1073
 
 
1074
 
 
1075
 
#####################################################################
1076
 
# Repository Formats
 
1000
    def revision_parents(self, revision_id):
 
1001
        return self._get_revision_vf().get_parents(revision_id)
 
1002
 
 
1003
 
 
1004
class KnitRepository2(KnitRepository):
 
1005
    """"""
 
1006
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
 
1007
                 control_store, text_store):
 
1008
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
1009
                              _revision_store, control_store, text_store)
 
1010
        self._serializer = xml6.serializer_v6
 
1011
 
 
1012
    def deserialise_inventory(self, revision_id, xml):
 
1013
        """Transform the xml into an inventory object. 
 
1014
 
 
1015
        :param revision_id: The expected revision id of the inventory.
 
1016
        :param xml: A serialised inventory.
 
1017
        """
 
1018
        result = self._serializer.read_inventory_from_string(xml)
 
1019
        assert result.root.revision is not None
 
1020
        return result
 
1021
 
 
1022
    def serialise_inventory(self, inv):
 
1023
        """Transform the inventory object into XML text.
 
1024
 
 
1025
        :param revision_id: The expected revision id of the inventory.
 
1026
        :param xml: A serialised inventory.
 
1027
        """
 
1028
        assert inv.revision_id is not None
 
1029
        assert inv.root.revision is not None
 
1030
        return KnitRepository.serialise_inventory(self, inv)
 
1031
 
 
1032
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
 
1033
                           timezone=None, committer=None, revprops=None, 
 
1034
                           revision_id=None):
 
1035
        """Obtain a CommitBuilder for this repository.
 
1036
        
 
1037
        :param branch: Branch to commit to.
 
1038
        :param parents: Revision ids of the parents of the new revision.
 
1039
        :param config: Configuration to use.
 
1040
        :param timestamp: Optional timestamp recorded for commit.
 
1041
        :param timezone: Optional timezone for timestamp.
 
1042
        :param committer: Optional committer to set for commit.
 
1043
        :param revprops: Optional dictionary of revision properties.
 
1044
        :param revision_id: Optional revision id.
 
1045
        """
 
1046
        return RootCommitBuilder(self, parents, config, timestamp, timezone,
 
1047
                                 committer, revprops, revision_id)
 
1048
 
1077
1049
 
1078
1050
class RepositoryFormat(object):
1079
1051
    """A repository format.
1099
1071
    parameterisation.
1100
1072
    """
1101
1073
 
 
1074
    _default_format = None
 
1075
    """The default format used for new repositories."""
 
1076
 
 
1077
    _formats = {}
 
1078
    """The known formats."""
 
1079
 
1102
1080
    def __str__(self):
1103
1081
        return "<%s>" % self.__class__.__name__
1104
1082
 
1105
 
    def __eq__(self, other):
1106
 
        # format objects are generally stateless
1107
 
        return isinstance(other, self.__class__)
1108
 
 
1109
 
    def __ne__(self, other):
1110
 
        return not self == other
1111
 
 
1112
1083
    @classmethod
1113
1084
    def find_format(klass, a_bzrdir):
1114
 
        """Return the format for the repository object in a_bzrdir.
1115
 
        
1116
 
        This is used by bzr native formats that have a "format" file in
1117
 
        the repository.  Other methods may be used by different types of 
1118
 
        control directory.
1119
 
        """
 
1085
        """Return the format for the repository object in a_bzrdir."""
1120
1086
        try:
1121
1087
            transport = a_bzrdir.get_repository_transport(None)
1122
1088
            format_string = transport.get("format").read()
1123
 
            return format_registry.get(format_string)
 
1089
            return klass._formats[format_string]
1124
1090
        except errors.NoSuchFile:
1125
1091
            raise errors.NoRepositoryPresent(a_bzrdir)
1126
1092
        except KeyError:
1127
1093
            raise errors.UnknownFormatError(format=format_string)
1128
1094
 
1129
 
    @classmethod
1130
 
    def register_format(klass, format):
1131
 
        format_registry.register(format.get_format_string(), format)
1132
 
 
1133
 
    @classmethod
1134
 
    def unregister_format(klass, format):
1135
 
        format_registry.remove(format.get_format_string())
 
1095
    def _get_control_store(self, repo_transport, control_files):
 
1096
        """Return the control store for this repository."""
 
1097
        raise NotImplementedError(self._get_control_store)
1136
1098
    
1137
1099
    @classmethod
1138
1100
    def get_default_format(klass):
1139
1101
        """Return the current default format."""
1140
 
        from bzrlib import bzrdir
1141
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
1142
 
 
1143
 
    def _get_control_store(self, repo_transport, control_files):
1144
 
        """Return the control store for this repository."""
1145
 
        raise NotImplementedError(self._get_control_store)
 
1102
        return klass._default_format
1146
1103
 
1147
1104
    def get_format_string(self):
1148
1105
        """Return the ASCII format string that identifies this format.
1175
1132
        from bzrlib.store.revision.text import TextRevisionStore
1176
1133
        dir_mode = control_files._dir_mode
1177
1134
        file_mode = control_files._file_mode
1178
 
        text_store = TextStore(transport.clone(name),
 
1135
        text_store =TextStore(transport.clone(name),
1179
1136
                              prefixed=prefixed,
1180
1137
                              compressed=compressed,
1181
1138
                              dir_mode=dir_mode,
1183
1140
        _revision_store = TextRevisionStore(text_store, serializer)
1184
1141
        return _revision_store
1185
1142
 
1186
 
    # TODO: this shouldn't be in the base class, it's specific to things that
1187
 
    # use weaves or knits -- mbp 20070207
1188
1143
    def _get_versioned_file_store(self,
1189
1144
                                  name,
1190
1145
                                  transport,
1191
1146
                                  control_files,
1192
1147
                                  prefixed=True,
1193
 
                                  versionedfile_class=None,
 
1148
                                  versionedfile_class=WeaveFile,
1194
1149
                                  versionedfile_kwargs={},
1195
1150
                                  escaped=False):
1196
 
        if versionedfile_class is None:
1197
 
            versionedfile_class = self._versionedfile_class
1198
1151
        weave_transport = control_files._transport.clone(name)
1199
1152
        dir_mode = control_files._dir_mode
1200
1153
        file_mode = control_files._file_mode
1234
1187
        """
1235
1188
        raise NotImplementedError(self.open)
1236
1189
 
 
1190
    @classmethod
 
1191
    def register_format(klass, format):
 
1192
        klass._formats[format.get_format_string()] = format
 
1193
 
 
1194
    @classmethod
 
1195
    def set_default_format(klass, format):
 
1196
        klass._default_format = format
 
1197
 
 
1198
    @classmethod
 
1199
    def unregister_format(klass, format):
 
1200
        assert klass._formats[format.get_format_string()] is format
 
1201
        del klass._formats[format.get_format_string()]
 
1202
 
 
1203
 
 
1204
class PreSplitOutRepositoryFormat(RepositoryFormat):
 
1205
    """Base class for the pre split out repository formats."""
 
1206
 
 
1207
    rich_root_data = False
 
1208
 
 
1209
    def initialize(self, a_bzrdir, shared=False, _internal=False):
 
1210
        """Create a weave repository.
 
1211
        
 
1212
        TODO: when creating split out bzr branch formats, move this to a common
 
1213
        base for Format5, Format6. or something like that.
 
1214
        """
 
1215
        from bzrlib.weavefile import write_weave_v5
 
1216
        from bzrlib.weave import Weave
 
1217
 
 
1218
        if shared:
 
1219
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
1220
 
 
1221
        if not _internal:
 
1222
            # always initialized when the bzrdir is.
 
1223
            return self.open(a_bzrdir, _found=True)
 
1224
        
 
1225
        # Create an empty weave
 
1226
        sio = StringIO()
 
1227
        write_weave_v5(Weave(), sio)
 
1228
        empty_weave = sio.getvalue()
 
1229
 
 
1230
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1231
        dirs = ['revision-store', 'weaves']
 
1232
        files = [('inventory.weave', StringIO(empty_weave)),
 
1233
                 ]
 
1234
        
 
1235
        # FIXME: RBC 20060125 don't peek under the covers
 
1236
        # NB: no need to escape relative paths that are url safe.
 
1237
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
 
1238
                                      TransportLock)
 
1239
        control_files.create_lock()
 
1240
        control_files.lock_write()
 
1241
        control_files._transport.mkdir_multi(dirs,
 
1242
                mode=control_files._dir_mode)
 
1243
        try:
 
1244
            for file, content in files:
 
1245
                control_files.put(file, content)
 
1246
        finally:
 
1247
            control_files.unlock()
 
1248
        return self.open(a_bzrdir, _found=True)
 
1249
 
 
1250
    def _get_control_store(self, repo_transport, control_files):
 
1251
        """Return the control store for this repository."""
 
1252
        return self._get_versioned_file_store('',
 
1253
                                              repo_transport,
 
1254
                                              control_files,
 
1255
                                              prefixed=False)
 
1256
 
 
1257
    def _get_text_store(self, transport, control_files):
 
1258
        """Get a store for file texts for this format."""
 
1259
        raise NotImplementedError(self._get_text_store)
 
1260
 
 
1261
    def open(self, a_bzrdir, _found=False):
 
1262
        """See RepositoryFormat.open()."""
 
1263
        if not _found:
 
1264
            # we are being called directly and must probe.
 
1265
            raise NotImplementedError
 
1266
 
 
1267
        repo_transport = a_bzrdir.get_repository_transport(None)
 
1268
        control_files = a_bzrdir._control_files
 
1269
        text_store = self._get_text_store(repo_transport, control_files)
 
1270
        control_store = self._get_control_store(repo_transport, control_files)
 
1271
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1272
        return AllInOneRepository(_format=self,
 
1273
                                  a_bzrdir=a_bzrdir,
 
1274
                                  _revision_store=_revision_store,
 
1275
                                  control_store=control_store,
 
1276
                                  text_store=text_store)
 
1277
 
 
1278
    def check_conversion_target(self, target_format):
 
1279
        pass
 
1280
 
 
1281
 
 
1282
class RepositoryFormat4(PreSplitOutRepositoryFormat):
 
1283
    """Bzr repository format 4.
 
1284
 
 
1285
    This repository format has:
 
1286
     - flat stores
 
1287
     - TextStores for texts, inventories,revisions.
 
1288
 
 
1289
    This format is deprecated: it indexes texts using a text id which is
 
1290
    removed in format 5; initialization and write support for this format
 
1291
    has been removed.
 
1292
    """
 
1293
 
 
1294
    def __init__(self):
 
1295
        super(RepositoryFormat4, self).__init__()
 
1296
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
 
1297
 
 
1298
    def get_format_description(self):
 
1299
        """See RepositoryFormat.get_format_description()."""
 
1300
        return "Repository format 4"
 
1301
 
 
1302
    def initialize(self, url, shared=False, _internal=False):
 
1303
        """Format 4 branches cannot be created."""
 
1304
        raise errors.UninitializableFormat(self)
 
1305
 
 
1306
    def is_supported(self):
 
1307
        """Format 4 is not supported.
 
1308
 
 
1309
        It is not supported because the model changed from 4 to 5 and the
 
1310
        conversion logic is expensive - so doing it on the fly was not 
 
1311
        feasible.
 
1312
        """
 
1313
        return False
 
1314
 
 
1315
    def _get_control_store(self, repo_transport, control_files):
 
1316
        """Format 4 repositories have no formal control store at this point.
 
1317
        
 
1318
        This will cause any control-file-needing apis to fail - this is desired.
 
1319
        """
 
1320
        return None
 
1321
    
 
1322
    def _get_revision_store(self, repo_transport, control_files):
 
1323
        """See RepositoryFormat._get_revision_store()."""
 
1324
        from bzrlib.xml4 import serializer_v4
 
1325
        return self._get_text_rev_store(repo_transport,
 
1326
                                        control_files,
 
1327
                                        'revision-store',
 
1328
                                        serializer=serializer_v4)
 
1329
 
 
1330
    def _get_text_store(self, transport, control_files):
 
1331
        """See RepositoryFormat._get_text_store()."""
 
1332
 
 
1333
 
 
1334
class RepositoryFormat5(PreSplitOutRepositoryFormat):
 
1335
    """Bzr control format 5.
 
1336
 
 
1337
    This repository format has:
 
1338
     - weaves for file texts and inventory
 
1339
     - flat stores
 
1340
     - TextStores for revisions and signatures.
 
1341
    """
 
1342
 
 
1343
    def __init__(self):
 
1344
        super(RepositoryFormat5, self).__init__()
 
1345
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
 
1346
 
 
1347
    def get_format_description(self):
 
1348
        """See RepositoryFormat.get_format_description()."""
 
1349
        return "Weave repository format 5"
 
1350
 
 
1351
    def _get_revision_store(self, repo_transport, control_files):
 
1352
        """See RepositoryFormat._get_revision_store()."""
 
1353
        """Return the revision store object for this a_bzrdir."""
 
1354
        return self._get_text_rev_store(repo_transport,
 
1355
                                        control_files,
 
1356
                                        'revision-store',
 
1357
                                        compressed=False)
 
1358
 
 
1359
    def _get_text_store(self, transport, control_files):
 
1360
        """See RepositoryFormat._get_text_store()."""
 
1361
        return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
 
1362
 
 
1363
 
 
1364
class RepositoryFormat6(PreSplitOutRepositoryFormat):
 
1365
    """Bzr control format 6.
 
1366
 
 
1367
    This repository format has:
 
1368
     - weaves for file texts and inventory
 
1369
     - hash subdirectory based stores.
 
1370
     - TextStores for revisions and signatures.
 
1371
    """
 
1372
 
 
1373
    def __init__(self):
 
1374
        super(RepositoryFormat6, self).__init__()
 
1375
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1376
 
 
1377
    def get_format_description(self):
 
1378
        """See RepositoryFormat.get_format_description()."""
 
1379
        return "Weave repository format 6"
 
1380
 
 
1381
    def _get_revision_store(self, repo_transport, control_files):
 
1382
        """See RepositoryFormat._get_revision_store()."""
 
1383
        return self._get_text_rev_store(repo_transport,
 
1384
                                        control_files,
 
1385
                                        'revision-store',
 
1386
                                        compressed=False,
 
1387
                                        prefixed=True)
 
1388
 
 
1389
    def _get_text_store(self, transport, control_files):
 
1390
        """See RepositoryFormat._get_text_store()."""
 
1391
        return self._get_versioned_file_store('weaves', transport, control_files)
 
1392
 
1237
1393
 
1238
1394
class MetaDirRepositoryFormat(RepositoryFormat):
1239
1395
    """Common base class for the new repositories using the metadir layout."""
1240
1396
 
1241
1397
    rich_root_data = False
1242
 
    supports_tree_reference = False
1243
 
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1244
1398
 
1245
1399
    def __init__(self):
1246
1400
        super(MetaDirRepositoryFormat, self).__init__()
 
1401
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1247
1402
 
1248
1403
    def _create_control_files(self, a_bzrdir):
1249
1404
        """Create the required files and the initial control_files object."""
1250
1405
        # FIXME: RBC 20060125 don't peek under the covers
1251
1406
        # NB: no need to escape relative paths that are url safe.
1252
1407
        repository_transport = a_bzrdir.get_repository_transport(self)
1253
 
        control_files = lockable_files.LockableFiles(repository_transport,
1254
 
                                'lock', lockdir.LockDir)
 
1408
        control_files = LockableFiles(repository_transport, 'lock', LockDir)
1255
1409
        control_files.create_lock()
1256
1410
        return control_files
1257
1411
 
1272
1426
            control_files.unlock()
1273
1427
 
1274
1428
 
 
1429
class RepositoryFormat7(MetaDirRepositoryFormat):
 
1430
    """Bzr repository 7.
 
1431
 
 
1432
    This repository format has:
 
1433
     - weaves for file texts and inventory
 
1434
     - hash subdirectory based stores.
 
1435
     - TextStores for revisions and signatures.
 
1436
     - a format marker of its own
 
1437
     - an optional 'shared-storage' flag
 
1438
     - an optional 'no-working-trees' flag
 
1439
    """
 
1440
 
 
1441
    def _get_control_store(self, repo_transport, control_files):
 
1442
        """Return the control store for this repository."""
 
1443
        return self._get_versioned_file_store('',
 
1444
                                              repo_transport,
 
1445
                                              control_files,
 
1446
                                              prefixed=False)
 
1447
 
 
1448
    def get_format_string(self):
 
1449
        """See RepositoryFormat.get_format_string()."""
 
1450
        return "Bazaar-NG Repository format 7"
 
1451
 
 
1452
    def get_format_description(self):
 
1453
        """See RepositoryFormat.get_format_description()."""
 
1454
        return "Weave repository format 7"
 
1455
 
 
1456
    def check_conversion_target(self, target_format):
 
1457
        pass
 
1458
 
 
1459
    def _get_revision_store(self, repo_transport, control_files):
 
1460
        """See RepositoryFormat._get_revision_store()."""
 
1461
        return self._get_text_rev_store(repo_transport,
 
1462
                                        control_files,
 
1463
                                        'revision-store',
 
1464
                                        compressed=False,
 
1465
                                        prefixed=True,
 
1466
                                        )
 
1467
 
 
1468
    def _get_text_store(self, transport, control_files):
 
1469
        """See RepositoryFormat._get_text_store()."""
 
1470
        return self._get_versioned_file_store('weaves',
 
1471
                                              transport,
 
1472
                                              control_files)
 
1473
 
 
1474
    def initialize(self, a_bzrdir, shared=False):
 
1475
        """Create a weave repository.
 
1476
 
 
1477
        :param shared: If true the repository will be initialized as a shared
 
1478
                       repository.
 
1479
        """
 
1480
        from bzrlib.weavefile import write_weave_v5
 
1481
        from bzrlib.weave import Weave
 
1482
 
 
1483
        # Create an empty weave
 
1484
        sio = StringIO()
 
1485
        write_weave_v5(Weave(), sio)
 
1486
        empty_weave = sio.getvalue()
 
1487
 
 
1488
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1489
        dirs = ['revision-store', 'weaves']
 
1490
        files = [('inventory.weave', StringIO(empty_weave)), 
 
1491
                 ]
 
1492
        utf8_files = [('format', self.get_format_string())]
 
1493
 
 
1494
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1495
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1496
 
 
1497
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1498
        """See RepositoryFormat.open().
 
1499
        
 
1500
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1501
                                    repository at a slightly different url
 
1502
                                    than normal. I.e. during 'upgrade'.
 
1503
        """
 
1504
        if not _found:
 
1505
            format = RepositoryFormat.find_format(a_bzrdir)
 
1506
            assert format.__class__ ==  self.__class__
 
1507
        if _override_transport is not None:
 
1508
            repo_transport = _override_transport
 
1509
        else:
 
1510
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1511
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
1512
        text_store = self._get_text_store(repo_transport, control_files)
 
1513
        control_store = self._get_control_store(repo_transport, control_files)
 
1514
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1515
        return MetaDirRepository(_format=self,
 
1516
                                 a_bzrdir=a_bzrdir,
 
1517
                                 control_files=control_files,
 
1518
                                 _revision_store=_revision_store,
 
1519
                                 control_store=control_store,
 
1520
                                 text_store=text_store)
 
1521
 
 
1522
 
 
1523
class RepositoryFormatKnit(MetaDirRepositoryFormat):
 
1524
    """Bzr repository knit format (generalized). 
 
1525
 
 
1526
    This repository format has:
 
1527
     - knits for file texts and inventory
 
1528
     - hash subdirectory based stores.
 
1529
     - knits for revisions and signatures
 
1530
     - TextStores for revisions and signatures.
 
1531
     - a format marker of its own
 
1532
     - an optional 'shared-storage' flag
 
1533
     - an optional 'no-working-trees' flag
 
1534
     - a LockDir lock
 
1535
    """
 
1536
 
 
1537
    def _get_control_store(self, repo_transport, control_files):
 
1538
        """Return the control store for this repository."""
 
1539
        return VersionedFileStore(
 
1540
            repo_transport,
 
1541
            prefixed=False,
 
1542
            file_mode=control_files._file_mode,
 
1543
            versionedfile_class=KnitVersionedFile,
 
1544
            versionedfile_kwargs={'factory':KnitPlainFactory()},
 
1545
            )
 
1546
 
 
1547
    def _get_revision_store(self, repo_transport, control_files):
 
1548
        """See RepositoryFormat._get_revision_store()."""
 
1549
        from bzrlib.store.revision.knit import KnitRevisionStore
 
1550
        versioned_file_store = VersionedFileStore(
 
1551
            repo_transport,
 
1552
            file_mode=control_files._file_mode,
 
1553
            prefixed=False,
 
1554
            precious=True,
 
1555
            versionedfile_class=KnitVersionedFile,
 
1556
            versionedfile_kwargs={'delta':False, 'factory':KnitPlainFactory(),},
 
1557
            escaped=True,
 
1558
            )
 
1559
        return KnitRevisionStore(versioned_file_store)
 
1560
 
 
1561
    def _get_text_store(self, transport, control_files):
 
1562
        """See RepositoryFormat._get_text_store()."""
 
1563
        return self._get_versioned_file_store('knits',
 
1564
                                              transport,
 
1565
                                              control_files,
 
1566
                                              versionedfile_class=KnitVersionedFile,
 
1567
                                              versionedfile_kwargs={
 
1568
                                                  'create_parent_dir':True,
 
1569
                                                  'delay_create':True,
 
1570
                                                  'dir_mode':control_files._dir_mode,
 
1571
                                              },
 
1572
                                              escaped=True)
 
1573
 
 
1574
    def initialize(self, a_bzrdir, shared=False):
 
1575
        """Create a knit format 1 repository.
 
1576
 
 
1577
        :param a_bzrdir: bzrdir to contain the new repository; must already
 
1578
            be initialized.
 
1579
        :param shared: If true the repository will be initialized as a shared
 
1580
                       repository.
 
1581
        """
 
1582
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1583
        dirs = ['revision-store', 'knits']
 
1584
        files = []
 
1585
        utf8_files = [('format', self.get_format_string())]
 
1586
        
 
1587
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1588
        repo_transport = a_bzrdir.get_repository_transport(None)
 
1589
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
1590
        control_store = self._get_control_store(repo_transport, control_files)
 
1591
        transaction = transactions.WriteTransaction()
 
1592
        # trigger a write of the inventory store.
 
1593
        control_store.get_weave_or_empty('inventory', transaction)
 
1594
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1595
        _revision_store.has_revision_id('A', transaction)
 
1596
        _revision_store.get_signature_file(transaction)
 
1597
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1598
 
 
1599
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1600
        """See RepositoryFormat.open().
 
1601
        
 
1602
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1603
                                    repository at a slightly different url
 
1604
                                    than normal. I.e. during 'upgrade'.
 
1605
        """
 
1606
        if not _found:
 
1607
            format = RepositoryFormat.find_format(a_bzrdir)
 
1608
            assert format.__class__ ==  self.__class__
 
1609
        if _override_transport is not None:
 
1610
            repo_transport = _override_transport
 
1611
        else:
 
1612
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1613
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
1614
        text_store = self._get_text_store(repo_transport, control_files)
 
1615
        control_store = self._get_control_store(repo_transport, control_files)
 
1616
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1617
        return KnitRepository(_format=self,
 
1618
                              a_bzrdir=a_bzrdir,
 
1619
                              control_files=control_files,
 
1620
                              _revision_store=_revision_store,
 
1621
                              control_store=control_store,
 
1622
                              text_store=text_store)
 
1623
 
 
1624
 
 
1625
class RepositoryFormatKnit1(RepositoryFormatKnit):
 
1626
    """Bzr repository knit format 1.
 
1627
 
 
1628
    This repository format has:
 
1629
     - knits for file texts and inventory
 
1630
     - hash subdirectory based stores.
 
1631
     - knits for revisions and signatures
 
1632
     - TextStores for revisions and signatures.
 
1633
     - a format marker of its own
 
1634
     - an optional 'shared-storage' flag
 
1635
     - an optional 'no-working-trees' flag
 
1636
     - a LockDir lock
 
1637
 
 
1638
    This format was introduced in bzr 0.8.
 
1639
    """
 
1640
    def get_format_string(self):
 
1641
        """See RepositoryFormat.get_format_string()."""
 
1642
        return "Bazaar-NG Knit Repository Format 1"
 
1643
 
 
1644
    def get_format_description(self):
 
1645
        """See RepositoryFormat.get_format_description()."""
 
1646
        return "Knit repository format 1"
 
1647
 
 
1648
    def check_conversion_target(self, target_format):
 
1649
        pass
 
1650
 
 
1651
 
 
1652
class RepositoryFormatKnit2(RepositoryFormatKnit):
 
1653
    """Bzr repository knit format 2.
 
1654
 
 
1655
    THIS FORMAT IS EXPERIMENTAL
 
1656
    This repository format has:
 
1657
     - knits for file texts and inventory
 
1658
     - hash subdirectory based stores.
 
1659
     - knits for revisions and signatures
 
1660
     - TextStores for revisions and signatures.
 
1661
     - a format marker of its own
 
1662
     - an optional 'shared-storage' flag
 
1663
     - an optional 'no-working-trees' flag
 
1664
     - a LockDir lock
 
1665
     - Support for recording full info about the tree root
 
1666
 
 
1667
    """
 
1668
    
 
1669
    rich_root_data = True
 
1670
 
 
1671
    def get_format_string(self):
 
1672
        """See RepositoryFormat.get_format_string()."""
 
1673
        return "Bazaar Knit Repository Format 2\n"
 
1674
 
 
1675
    def get_format_description(self):
 
1676
        """See RepositoryFormat.get_format_description()."""
 
1677
        return "Knit repository format 2"
 
1678
 
 
1679
    def check_conversion_target(self, target_format):
 
1680
        if not target_format.rich_root_data:
 
1681
            raise errors.BadConversionTarget(
 
1682
                'Does not support rich root data.', target_format)
 
1683
 
 
1684
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1685
        """See RepositoryFormat.open().
 
1686
        
 
1687
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1688
                                    repository at a slightly different url
 
1689
                                    than normal. I.e. during 'upgrade'.
 
1690
        """
 
1691
        if not _found:
 
1692
            format = RepositoryFormat.find_format(a_bzrdir)
 
1693
            assert format.__class__ ==  self.__class__
 
1694
        if _override_transport is not None:
 
1695
            repo_transport = _override_transport
 
1696
        else:
 
1697
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1698
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
1699
        text_store = self._get_text_store(repo_transport, control_files)
 
1700
        control_store = self._get_control_store(repo_transport, control_files)
 
1701
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1702
        return KnitRepository2(_format=self,
 
1703
                               a_bzrdir=a_bzrdir,
 
1704
                               control_files=control_files,
 
1705
                               _revision_store=_revision_store,
 
1706
                               control_store=control_store,
 
1707
                               text_store=text_store)
 
1708
 
 
1709
 
 
1710
 
1275
1711
# formats which have no format string are not discoverable
1276
 
# and not independently creatable, so are not registered.  They're 
1277
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
1278
 
# needed, it's constructed directly by the BzrDir.  Non-native formats where
1279
 
# the repository is not separately opened are similar.
1280
 
 
1281
 
format_registry.register_lazy(
1282
 
    'Bazaar-NG Repository format 7',
1283
 
    'bzrlib.repofmt.weaverepo',
1284
 
    'RepositoryFormat7'
1285
 
    )
1286
 
# KEEP in sync with bzrdir.format_registry default, which controls the overall
1287
 
# default control directory format
1288
 
 
1289
 
format_registry.register_lazy(
1290
 
    'Bazaar-NG Knit Repository Format 1',
1291
 
    'bzrlib.repofmt.knitrepo',
1292
 
    'RepositoryFormatKnit1',
1293
 
    )
1294
 
format_registry.default_key = 'Bazaar-NG Knit Repository Format 1'
1295
 
 
1296
 
format_registry.register_lazy(
1297
 
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1298
 
    'bzrlib.repofmt.knitrepo',
1299
 
    'RepositoryFormatKnit3',
1300
 
    )
 
1712
# and not independently creatable, so are not registered.
 
1713
RepositoryFormat.register_format(RepositoryFormat7())
 
1714
_default_format = RepositoryFormatKnit1()
 
1715
RepositoryFormat.register_format(_default_format)
 
1716
RepositoryFormat.register_format(RepositoryFormatKnit2())
 
1717
RepositoryFormat.set_default_format(_default_format)
 
1718
_legacy_formats = [RepositoryFormat4(),
 
1719
                   RepositoryFormat5(),
 
1720
                   RepositoryFormat6()]
1301
1721
 
1302
1722
 
1303
1723
class InterRepository(InterObject):
1315
1735
    _optimisers = []
1316
1736
    """The available optimised InterRepository types."""
1317
1737
 
1318
 
    def copy_content(self, revision_id=None):
 
1738
    def copy_content(self, revision_id=None, basis=None):
1319
1739
        raise NotImplementedError(self.copy_content)
1320
1740
 
1321
1741
    def fetch(self, revision_id=None, pb=None):
1345
1765
        # generic, possibly worst case, slow code path.
1346
1766
        target_ids = set(self.target.all_revision_ids())
1347
1767
        if revision_id is not None:
1348
 
            # TODO: jam 20070210 InterRepository is internal enough that it
1349
 
            #       should assume revision_ids are already utf-8
1350
 
            revision_id = osutils.safe_revision_id(revision_id)
1351
1768
            source_ids = self.source.get_ancestry(revision_id)
1352
1769
            assert source_ids[0] is None
1353
1770
            source_ids.pop(0)
1366
1783
    Data format and model must match for this to work.
1367
1784
    """
1368
1785
 
1369
 
    @classmethod
1370
 
    def _get_repo_format_to_test(self):
1371
 
        """Repository format for testing with."""
1372
 
        return RepositoryFormat.get_default_format()
 
1786
    _matching_repo_format = RepositoryFormat4()
 
1787
    """Repository format for testing with."""
1373
1788
 
1374
1789
    @staticmethod
1375
1790
    def is_compatible(source, target):
1376
 
        if source.supports_rich_root() != target.supports_rich_root():
1377
 
            return False
1378
 
        if source._serializer != target._serializer:
1379
 
            return False
1380
 
        return True
 
1791
        if not isinstance(source, Repository):
 
1792
            return False
 
1793
        if not isinstance(target, Repository):
 
1794
            return False
 
1795
        if source._format.rich_root_data == target._format.rich_root_data:
 
1796
            return True
 
1797
        else:
 
1798
            return False
1381
1799
 
1382
1800
    @needs_write_lock
1383
 
    def copy_content(self, revision_id=None):
 
1801
    def copy_content(self, revision_id=None, basis=None):
1384
1802
        """Make a complete copy of the content in self into destination.
1385
1803
        
1386
1804
        This is a destructive operation! Do not use it on existing 
1388
1806
 
1389
1807
        :param revision_id: Only copy the content needed to construct
1390
1808
                            revision_id and its parents.
 
1809
        :param basis: Copy the needed data preferentially from basis.
1391
1810
        """
1392
1811
        try:
1393
1812
            self.target.set_make_working_trees(self.source.make_working_trees())
1394
1813
        except NotImplementedError:
1395
1814
            pass
1396
 
        # TODO: jam 20070210 This is fairly internal, so we should probably
1397
 
        #       just assert that revision_id is not unicode.
1398
 
        revision_id = osutils.safe_revision_id(revision_id)
 
1815
        # grab the basis available data
 
1816
        if basis is not None:
 
1817
            self.target.fetch(basis, revision_id=revision_id)
1399
1818
        # but don't bother fetching if we have the needed data now.
1400
 
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
1819
        if (revision_id not in (None, NULL_REVISION) and 
1401
1820
            self.target.has_revision(revision_id)):
1402
1821
            return
1403
1822
        self.target.fetch(self.source, revision_id=revision_id)
1409
1828
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1410
1829
               self.source, self.source._format, self.target, 
1411
1830
               self.target._format)
1412
 
        # TODO: jam 20070210 This should be an assert, not a translate
1413
 
        revision_id = osutils.safe_revision_id(revision_id)
1414
1831
        f = GenericRepoFetcher(to_repository=self.target,
1415
1832
                               from_repository=self.source,
1416
1833
                               last_revision=revision_id,
1421
1838
class InterWeaveRepo(InterSameDataRepository):
1422
1839
    """Optimised code paths between Weave based repositories."""
1423
1840
 
1424
 
    @classmethod
1425
 
    def _get_repo_format_to_test(self):
1426
 
        from bzrlib.repofmt import weaverepo
1427
 
        return weaverepo.RepositoryFormat7()
 
1841
    _matching_repo_format = RepositoryFormat7()
 
1842
    """Repository format for testing with."""
1428
1843
 
1429
1844
    @staticmethod
1430
1845
    def is_compatible(source, target):
1434
1849
        could lead to confusing results, and there is no need to be 
1435
1850
        overly general.
1436
1851
        """
1437
 
        from bzrlib.repofmt.weaverepo import (
1438
 
                RepositoryFormat5,
1439
 
                RepositoryFormat6,
1440
 
                RepositoryFormat7,
1441
 
                )
1442
1852
        try:
1443
1853
            return (isinstance(source._format, (RepositoryFormat5,
1444
1854
                                                RepositoryFormat6,
1450
1860
            return False
1451
1861
    
1452
1862
    @needs_write_lock
1453
 
    def copy_content(self, revision_id=None):
 
1863
    def copy_content(self, revision_id=None, basis=None):
1454
1864
        """See InterRepository.copy_content()."""
1455
1865
        # weave specific optimised path:
1456
 
        # TODO: jam 20070210 Internal, should be an assert, not translate
1457
 
        revision_id = osutils.safe_revision_id(revision_id)
1458
 
        try:
1459
 
            self.target.set_make_working_trees(self.source.make_working_trees())
1460
 
        except NotImplementedError:
1461
 
            pass
1462
 
        # FIXME do not peek!
1463
 
        if self.source.control_files._transport.listable():
1464
 
            pb = ui.ui_factory.nested_progress_bar()
 
1866
        if basis is not None:
 
1867
            # copy the basis in, then fetch remaining data.
 
1868
            basis.copy_content_into(self.target, revision_id)
 
1869
            # the basis copy_content_into could miss-set this.
1465
1870
            try:
1466
 
                self.target.weave_store.copy_all_ids(
1467
 
                    self.source.weave_store,
1468
 
                    pb=pb,
1469
 
                    from_transaction=self.source.get_transaction(),
1470
 
                    to_transaction=self.target.get_transaction())
1471
 
                pb.update('copying inventory', 0, 1)
1472
 
                self.target.control_weaves.copy_multi(
1473
 
                    self.source.control_weaves, ['inventory'],
1474
 
                    from_transaction=self.source.get_transaction(),
1475
 
                    to_transaction=self.target.get_transaction())
1476
 
                self.target._revision_store.text_store.copy_all_ids(
1477
 
                    self.source._revision_store.text_store,
1478
 
                    pb=pb)
1479
 
            finally:
1480
 
                pb.finished()
1481
 
        else:
 
1871
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1872
            except NotImplementedError:
 
1873
                pass
1482
1874
            self.target.fetch(self.source, revision_id=revision_id)
 
1875
        else:
 
1876
            try:
 
1877
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1878
            except NotImplementedError:
 
1879
                pass
 
1880
            # FIXME do not peek!
 
1881
            if self.source.control_files._transport.listable():
 
1882
                pb = ui.ui_factory.nested_progress_bar()
 
1883
                try:
 
1884
                    self.target.weave_store.copy_all_ids(
 
1885
                        self.source.weave_store,
 
1886
                        pb=pb,
 
1887
                        from_transaction=self.source.get_transaction(),
 
1888
                        to_transaction=self.target.get_transaction())
 
1889
                    pb.update('copying inventory', 0, 1)
 
1890
                    self.target.control_weaves.copy_multi(
 
1891
                        self.source.control_weaves, ['inventory'],
 
1892
                        from_transaction=self.source.get_transaction(),
 
1893
                        to_transaction=self.target.get_transaction())
 
1894
                    self.target._revision_store.text_store.copy_all_ids(
 
1895
                        self.source._revision_store.text_store,
 
1896
                        pb=pb)
 
1897
                finally:
 
1898
                    pb.finished()
 
1899
            else:
 
1900
                self.target.fetch(self.source, revision_id=revision_id)
1483
1901
 
1484
1902
    @needs_write_lock
1485
1903
    def fetch(self, revision_id=None, pb=None):
1487
1905
        from bzrlib.fetch import GenericRepoFetcher
1488
1906
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1489
1907
               self.source, self.source._format, self.target, self.target._format)
1490
 
        # TODO: jam 20070210 This should be an assert, not a translate
1491
 
        revision_id = osutils.safe_revision_id(revision_id)
1492
1908
        f = GenericRepoFetcher(to_repository=self.target,
1493
1909
                               from_repository=self.source,
1494
1910
                               last_revision=revision_id,
1540
1956
class InterKnitRepo(InterSameDataRepository):
1541
1957
    """Optimised code paths between Knit based repositories."""
1542
1958
 
1543
 
    @classmethod
1544
 
    def _get_repo_format_to_test(self):
1545
 
        from bzrlib.repofmt import knitrepo
1546
 
        return knitrepo.RepositoryFormatKnit1()
 
1959
    _matching_repo_format = RepositoryFormatKnit1()
 
1960
    """Repository format for testing with."""
1547
1961
 
1548
1962
    @staticmethod
1549
1963
    def is_compatible(source, target):
1553
1967
        could lead to confusing results, and there is no need to be 
1554
1968
        overly general.
1555
1969
        """
1556
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
1557
1970
        try:
1558
1971
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
1559
1972
                    isinstance(target._format, (RepositoryFormatKnit1)))
1566
1979
        from bzrlib.fetch import KnitRepoFetcher
1567
1980
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1568
1981
               self.source, self.source._format, self.target, self.target._format)
1569
 
        # TODO: jam 20070210 This should be an assert, not a translate
1570
 
        revision_id = osutils.safe_revision_id(revision_id)
1571
1982
        f = KnitRepoFetcher(to_repository=self.target,
1572
1983
                            from_repository=self.source,
1573
1984
                            last_revision=revision_id,
1607
2018
 
1608
2019
class InterModel1and2(InterRepository):
1609
2020
 
1610
 
    @classmethod
1611
 
    def _get_repo_format_to_test(self):
1612
 
        return None
 
2021
    _matching_repo_format = None
1613
2022
 
1614
2023
    @staticmethod
1615
2024
    def is_compatible(source, target):
1616
 
        if not source.supports_rich_root() and target.supports_rich_root():
 
2025
        if not isinstance(source, Repository):
 
2026
            return False
 
2027
        if not isinstance(target, Repository):
 
2028
            return False
 
2029
        if not source._format.rich_root_data and target._format.rich_root_data:
1617
2030
            return True
1618
2031
        else:
1619
2032
            return False
1622
2035
    def fetch(self, revision_id=None, pb=None):
1623
2036
        """See InterRepository.fetch()."""
1624
2037
        from bzrlib.fetch import Model1toKnit2Fetcher
1625
 
        # TODO: jam 20070210 This should be an assert, not a translate
1626
 
        revision_id = osutils.safe_revision_id(revision_id)
1627
2038
        f = Model1toKnit2Fetcher(to_repository=self.target,
1628
2039
                                 from_repository=self.source,
1629
2040
                                 last_revision=revision_id,
1631
2042
        return f.count_copied, f.failed_revisions
1632
2043
 
1633
2044
    @needs_write_lock
1634
 
    def copy_content(self, revision_id=None):
 
2045
    def copy_content(self, revision_id=None, basis=None):
1635
2046
        """Make a complete copy of the content in self into destination.
1636
2047
        
1637
2048
        This is a destructive operation! Do not use it on existing 
1639
2050
 
1640
2051
        :param revision_id: Only copy the content needed to construct
1641
2052
                            revision_id and its parents.
 
2053
        :param basis: Copy the needed data preferentially from basis.
1642
2054
        """
1643
2055
        try:
1644
2056
            self.target.set_make_working_trees(self.source.make_working_trees())
1645
2057
        except NotImplementedError:
1646
2058
            pass
1647
 
        # TODO: jam 20070210 Internal, assert, don't translate
1648
 
        revision_id = osutils.safe_revision_id(revision_id)
 
2059
        # grab the basis available data
 
2060
        if basis is not None:
 
2061
            self.target.fetch(basis, revision_id=revision_id)
1649
2062
        # but don't bother fetching if we have the needed data now.
1650
 
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
2063
        if (revision_id not in (None, NULL_REVISION) and 
1651
2064
            self.target.has_revision(revision_id)):
1652
2065
            return
1653
2066
        self.target.fetch(self.source, revision_id=revision_id)
1655
2068
 
1656
2069
class InterKnit1and2(InterKnitRepo):
1657
2070
 
1658
 
    @classmethod
1659
 
    def _get_repo_format_to_test(self):
1660
 
        return None
 
2071
    _matching_repo_format = None
1661
2072
 
1662
2073
    @staticmethod
1663
2074
    def is_compatible(source, target):
1664
 
        """Be compatible with Knit1 source and Knit3 target"""
1665
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
 
2075
        """Be compatible with Knit1 source and Knit2 target"""
1666
2076
        try:
1667
 
            from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
1668
 
                    RepositoryFormatKnit3
1669
2077
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
1670
 
                    isinstance(target._format, (RepositoryFormatKnit3)))
 
2078
                    isinstance(target._format, (RepositoryFormatKnit2)))
1671
2079
        except AttributeError:
1672
2080
            return False
1673
2081
 
1678
2086
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1679
2087
               self.source, self.source._format, self.target, 
1680
2088
               self.target._format)
1681
 
        # TODO: jam 20070210 This should be an assert, not a translate
1682
 
        revision_id = osutils.safe_revision_id(revision_id)
1683
2089
        f = Knit1to2Fetcher(to_repository=self.target,
1684
2090
                            from_repository=self.source,
1685
2091
                            last_revision=revision_id,
1709
2115
        self._formats = formats
1710
2116
    
1711
2117
    def adapt(self, test):
1712
 
        result = unittest.TestSuite()
 
2118
        result = TestSuite()
1713
2119
        for repository_format, bzrdir_format in self._formats:
1714
 
            from copy import deepcopy
1715
2120
            new_test = deepcopy(test)
1716
2121
            new_test.transport_server = self._transport_server
1717
2122
            new_test.transport_readonly_server = self._transport_readonly_server
1740
2145
        self._formats = formats
1741
2146
    
1742
2147
    def adapt(self, test):
1743
 
        result = unittest.TestSuite()
 
2148
        result = TestSuite()
1744
2149
        for interrepo_class, repository_format, repository_format_to in self._formats:
1745
 
            from copy import deepcopy
1746
2150
            new_test = deepcopy(test)
1747
2151
            new_test.transport_server = self._transport_server
1748
2152
            new_test.transport_readonly_server = self._transport_readonly_server
1759
2163
    @staticmethod
1760
2164
    def default_test_list():
1761
2165
        """Generate the default list of interrepo permutations to test."""
1762
 
        from bzrlib.repofmt import knitrepo, weaverepo
1763
2166
        result = []
1764
2167
        # test the default InterRepository between format 6 and the current 
1765
2168
        # default format.
1768
2171
        #result.append((InterRepository,
1769
2172
        #               RepositoryFormat6(),
1770
2173
        #               RepositoryFormatKnit1()))
1771
 
        for optimiser_class in InterRepository._optimisers:
1772
 
            format_to_test = optimiser_class._get_repo_format_to_test()
1773
 
            if format_to_test is not None:
1774
 
                result.append((optimiser_class,
1775
 
                               format_to_test, format_to_test))
 
2174
        for optimiser in InterRepository._optimisers:
 
2175
            if optimiser._matching_repo_format is not None:
 
2176
                result.append((optimiser,
 
2177
                               optimiser._matching_repo_format,
 
2178
                               optimiser._matching_repo_format
 
2179
                               ))
1776
2180
        # if there are specific combinations we want to use, we can add them 
1777
2181
        # here.
1778
 
        result.append((InterModel1and2,
1779
 
                       weaverepo.RepositoryFormat5(),
1780
 
                       knitrepo.RepositoryFormatKnit3()))
1781
 
        result.append((InterKnit1and2,
1782
 
                       knitrepo.RepositoryFormatKnit1(),
1783
 
                       knitrepo.RepositoryFormatKnit3()))
 
2182
        result.append((InterModel1and2, RepositoryFormat5(),
 
2183
                       RepositoryFormatKnit2()))
 
2184
        result.append((InterKnit1and2, RepositoryFormatKnit1(),
 
2185
                       RepositoryFormatKnit2()))
1784
2186
        return result
1785
2187
 
1786
2188
 
1867
2269
            self._committer = committer
1868
2270
 
1869
2271
        self.new_inventory = Inventory(None)
1870
 
        self._new_revision_id = osutils.safe_revision_id(revision_id)
 
2272
        self._new_revision_id = revision_id
1871
2273
        self.parents = parents
1872
2274
        self.repository = repository
1873
2275
 
1881
2283
        self._timestamp = round(timestamp, 3)
1882
2284
 
1883
2285
        if timezone is None:
1884
 
            self._timezone = osutils.local_time_offset()
 
2286
            self._timezone = local_time_offset()
1885
2287
        else:
1886
2288
            self._timezone = int(timezone)
1887
2289
 
1892
2294
 
1893
2295
        :return: The revision id of the recorded revision.
1894
2296
        """
1895
 
        rev = _mod_revision.Revision(
1896
 
                       timestamp=self._timestamp,
 
2297
        rev = Revision(timestamp=self._timestamp,
1897
2298
                       timezone=self._timezone,
1898
2299
                       committer=self._committer,
1899
2300
                       message=message,
1905
2306
            self.new_inventory, self._config)
1906
2307
        return self._new_revision_id
1907
2308
 
1908
 
    def revision_tree(self):
1909
 
        """Return the tree that was just committed.
1910
 
 
1911
 
        After calling commit() this can be called to get a RevisionTree
1912
 
        representing the newly committed tree. This is preferred to
1913
 
        calling Repository.revision_tree() because that may require
1914
 
        deserializing the inventory, while we already have a copy in
1915
 
        memory.
1916
 
        """
1917
 
        return RevisionTree(self.repository, self.new_inventory,
1918
 
                            self._new_revision_id)
1919
 
 
1920
2309
    def finish_inventory(self):
1921
2310
        """Tell the builder that the inventory is finished."""
1922
2311
        if self.new_inventory.root is None:
1933
2322
 
1934
2323
    def _gen_revision_id(self):
1935
2324
        """Return new revision-id."""
1936
 
        return generate_ids.gen_revision_id(self._config.username(),
1937
 
                                            self._timestamp)
 
2325
        s = '%s-%s-' % (self._config.user_email(), 
 
2326
                        compact_date(self._timestamp))
 
2327
        s += hexlify(rand_bytes(8))
 
2328
        return s
1938
2329
 
1939
2330
    def _generate_revision_if_needed(self):
1940
2331
        """Create a revision id if None was supplied.
1941
2332
        
1942
2333
        If the repository can not support user-specified revision ids
1943
 
        they should override this function and raise CannotSetRevisionId
 
2334
        they should override this function and raise UnsupportedOperation
1944
2335
        if _new_revision_id is not None.
1945
2336
 
1946
 
        :raises: CannotSetRevisionId
 
2337
        :raises: UnsupportedOperation
1947
2338
        """
1948
2339
        if self._new_revision_id is None:
1949
2340
            self._new_revision_id = self._gen_revision_id()
1976
2367
 
1977
2368
        # In this revision format, root entries have no knit or weave
1978
2369
        if ie is self.new_inventory.root:
1979
 
            # When serializing out to disk and back in
1980
 
            # root.revision is always _new_revision_id
1981
 
            ie.revision = self._new_revision_id
 
2370
            if len(parent_invs):
 
2371
                ie.revision = parent_invs[0].root.revision
 
2372
            else:
 
2373
                ie.revision = None
1982
2374
            return
1983
2375
        previous_entries = ie.find_previous_heads(
1984
2376
            parent_invs,
1995
2387
        :param file_parents: The per-file parent revision ids.
1996
2388
        """
1997
2389
        self._add_text_to_weave(file_id, [], file_parents.keys())
1998
 
 
1999
 
    def modified_reference(self, file_id, file_parents):
2000
 
        """Record the modification of a reference.
2001
 
 
2002
 
        :param file_id: The file_id of the link to record.
2003
 
        :param file_parents: The per-file parent revision ids.
2004
 
        """
2005
 
        self._add_text_to_weave(file_id, [], file_parents.keys())
2006
2390
    
2007
2391
    def modified_file_text(self, file_id, file_parents,
2008
2392
                           get_content_byte_lines, text_sha1=None,
2107
2491
 
2108
2492
 
2109
2493
def _unescaper(match, _map=_unescape_map):
2110
 
    code = match.group(1)
2111
 
    try:
2112
 
        return _map[code]
2113
 
    except KeyError:
2114
 
        if not code.startswith('#'):
2115
 
            raise
2116
 
        return unichr(int(code[1:])).encode('utf8')
 
2494
    return _map[match.group(1)]
2117
2495
 
2118
2496
 
2119
2497
_unescape_re = None
2123
2501
    """Unescape predefined XML entities in a string of data."""
2124
2502
    global _unescape_re
2125
2503
    if _unescape_re is None:
2126
 
        _unescape_re = re.compile('\&([^;]*);')
 
2504
        _unescape_re = re.compile('\&([^;]*);')
2127
2505
    return _unescape_re.sub(_unescaper, data)