~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-10-10 07:23:07 UTC
  • mfrom: (2067.1.1 urandom-56883)
  • Revision ID: pqm@pqm.ubuntu.com-20061010072307-037a6f63da8a1bdd
(John Arbash Meinel) Handle exceptions while opening /dev/urandom

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