~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

Cleaned up some long lines

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from bzrlib.lazy_import import lazy_import
18
 
lazy_import(globals(), """
19
 
import itertools
20
 
import time
21
 
 
22
 
from bzrlib import (
23
 
    bzrdir,
24
 
    config,
25
 
    controldir,
26
 
    debug,
27
 
    generate_ids,
28
 
    graph,
29
 
    lockable_files,
30
 
    lockdir,
31
 
    osutils,
32
 
    revision as _mod_revision,
33
 
    testament as _mod_testament,
34
 
    tsort,
35
 
    gpg,
36
 
    )
37
 
from bzrlib.bundle import serializer
38
 
from bzrlib.i18n import gettext
39
 
""")
40
 
 
41
 
from bzrlib import (
42
 
    errors,
43
 
    registry,
44
 
    symbol_versioning,
45
 
    ui,
46
 
    )
47
 
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
48
 
from bzrlib.inter import InterObject
49
 
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
50
 
from bzrlib.trace import (
51
 
    log_exception_quietly, note, mutter, mutter_callsite, warning)
52
 
 
53
 
 
54
 
# Old formats display a warning, but only once
55
 
_deprecation_warning_done = False
56
 
 
57
 
 
58
 
class IsInWriteGroupError(errors.InternalBzrError):
59
 
 
60
 
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
61
 
 
62
 
    def __init__(self, repo):
63
 
        errors.InternalBzrError.__init__(self, repo=repo)
64
 
 
65
 
 
66
 
class CommitBuilder(object):
67
 
    """Provides an interface to build up a commit.
68
 
 
69
 
    This allows describing a tree to be committed without needing to
70
 
    know the internals of the format of the repository.
71
 
    """
72
 
 
73
 
    # all clients should supply tree roots.
74
 
    record_root_entry = True
75
 
    # whether this commit builder supports the record_entry_contents interface
76
 
    supports_record_entry_contents = False
77
 
 
78
 
    def __init__(self, repository, parents, config, timestamp=None,
79
 
                 timezone=None, committer=None, revprops=None,
80
 
                 revision_id=None, lossy=False):
81
 
        """Initiate a CommitBuilder.
82
 
 
83
 
        :param repository: Repository to commit to.
84
 
        :param parents: Revision ids of the parents of the new revision.
85
 
        :param timestamp: Optional timestamp recorded for commit.
86
 
        :param timezone: Optional timezone for timestamp.
87
 
        :param committer: Optional committer to set for commit.
88
 
        :param revprops: Optional dictionary of revision properties.
89
 
        :param revision_id: Optional revision id.
90
 
        :param lossy: Whether to discard data that can not be natively
91
 
            represented, when pushing to a foreign VCS 
92
 
        """
93
 
        self._config = config
94
 
        self._lossy = lossy
95
 
 
96
 
        if committer is None:
97
 
            self._committer = self._config.username()
98
 
        elif not isinstance(committer, unicode):
99
 
            self._committer = committer.decode() # throw if non-ascii
100
 
        else:
101
 
            self._committer = committer
102
 
 
103
 
        self._new_revision_id = revision_id
104
 
        self.parents = parents
105
 
        self.repository = repository
106
 
 
107
 
        self._revprops = {}
108
 
        if revprops is not None:
109
 
            self._validate_revprops(revprops)
110
 
            self._revprops.update(revprops)
111
 
 
112
 
        if timestamp is None:
113
 
            timestamp = time.time()
114
 
        # Restrict resolution to 1ms
115
 
        self._timestamp = round(timestamp, 3)
116
 
 
117
 
        if timezone is None:
118
 
            self._timezone = osutils.local_time_offset()
119
 
        else:
120
 
            self._timezone = int(timezone)
121
 
 
122
 
        self._generate_revision_if_needed()
123
 
 
124
 
    def any_changes(self):
125
 
        """Return True if any entries were changed.
126
 
 
127
 
        This includes merge-only changes. It is the core for the --unchanged
128
 
        detection in commit.
129
 
 
130
 
        :return: True if any changes have occured.
131
 
        """
132
 
        raise NotImplementedError(self.any_changes)
133
 
 
134
 
    def _validate_unicode_text(self, text, context):
135
 
        """Verify things like commit messages don't have bogus characters."""
136
 
        if '\r' in text:
137
 
            raise ValueError('Invalid value for %s: %r' % (context, text))
138
 
 
139
 
    def _validate_revprops(self, revprops):
140
 
        for key, value in revprops.iteritems():
141
 
            # We know that the XML serializers do not round trip '\r'
142
 
            # correctly, so refuse to accept them
143
 
            if not isinstance(value, basestring):
144
 
                raise ValueError('revision property (%s) is not a valid'
145
 
                                 ' (unicode) string: %r' % (key, value))
146
 
            self._validate_unicode_text(value,
147
 
                                        'revision property (%s)' % (key,))
148
 
 
149
 
    def commit(self, message):
150
 
        """Make the actual commit.
151
 
 
152
 
        :return: The revision id of the recorded revision.
153
 
        """
154
 
        raise NotImplementedError(self.commit)
155
 
 
156
 
    def abort(self):
157
 
        """Abort the commit that is being built.
158
 
        """
159
 
        raise NotImplementedError(self.abort)
160
 
 
161
 
    def revision_tree(self):
162
 
        """Return the tree that was just committed.
163
 
 
164
 
        After calling commit() this can be called to get a
165
 
        RevisionTree representing the newly committed tree. This is
166
 
        preferred to calling Repository.revision_tree() because that may
167
 
        require deserializing the inventory, while we already have a copy in
168
 
        memory.
169
 
        """
170
 
        raise NotImplementedError(self.revision_tree)
171
 
 
172
 
    def finish_inventory(self):
173
 
        """Tell the builder that the inventory is finished.
174
 
 
175
 
        :return: The inventory id in the repository, which can be used with
176
 
            repository.get_inventory.
177
 
        """
178
 
        raise NotImplementedError(self.finish_inventory)
179
 
 
180
 
    def _gen_revision_id(self):
181
 
        """Return new revision-id."""
182
 
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
183
 
 
184
 
    def _generate_revision_if_needed(self):
185
 
        """Create a revision id if None was supplied.
186
 
 
187
 
        If the repository can not support user-specified revision ids
188
 
        they should override this function and raise CannotSetRevisionId
189
 
        if _new_revision_id is not None.
190
 
 
191
 
        :raises: CannotSetRevisionId
192
 
        """
193
 
        if self._new_revision_id is None:
194
 
            self._new_revision_id = self._gen_revision_id()
195
 
            self.random_revid = True
196
 
        else:
197
 
            self.random_revid = False
198
 
 
199
 
    def will_record_deletes(self):
200
 
        """Tell the commit builder that deletes are being notified.
201
 
 
202
 
        This enables the accumulation of an inventory delta; for the resulting
203
 
        commit to be valid, deletes against the basis MUST be recorded via
204
 
        builder.record_delete().
205
 
        """
206
 
        raise NotImplementedError(self.will_record_deletes)
207
 
 
208
 
    def record_iter_changes(self, tree, basis_revision_id, iter_changes):
209
 
        """Record a new tree via iter_changes.
210
 
 
211
 
        :param tree: The tree to obtain text contents from for changed objects.
212
 
        :param basis_revision_id: The revision id of the tree the iter_changes
213
 
            has been generated against. Currently assumed to be the same
214
 
            as self.parents[0] - if it is not, errors may occur.
215
 
        :param iter_changes: An iter_changes iterator with the changes to apply
216
 
            to basis_revision_id. The iterator must not include any items with
217
 
            a current kind of None - missing items must be either filtered out
218
 
            or errored-on beefore record_iter_changes sees the item.
219
 
        :return: A generator of (file_id, relpath, fs_hash) tuples for use with
220
 
            tree._observed_sha1.
221
 
        """
222
 
        raise NotImplementedError(self.record_iter_changes)
223
 
 
224
 
 
225
 
class RepositoryWriteLockResult(LogicalLockResult):
226
 
    """The result of write locking a repository.
227
 
 
228
 
    :ivar repository_token: The token obtained from the underlying lock, or
229
 
        None.
230
 
    :ivar unlock: A callable which will unlock the lock.
231
 
    """
232
 
 
233
 
    def __init__(self, unlock, repository_token):
234
 
        LogicalLockResult.__init__(self, unlock)
235
 
        self.repository_token = repository_token
236
 
 
237
 
    def __repr__(self):
238
 
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
239
 
            self.unlock)
240
 
 
241
 
 
242
 
######################################################################
243
 
# Repositories
244
 
 
245
 
 
246
 
class Repository(_RelockDebugMixin, controldir.ControlComponent):
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
from copy import deepcopy
 
18
from cStringIO import StringIO
 
19
from unittest import TestSuite
 
20
import xml.sax.saxutils
 
21
 
 
22
 
 
23
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
24
import bzrlib.errors as errors
 
25
from bzrlib.errors import InvalidRevisionId
 
26
from bzrlib.lockable_files import LockableFiles
 
27
from bzrlib.osutils import safe_unicode
 
28
from bzrlib.revision import NULL_REVISION
 
29
from bzrlib.store import copy_all
 
30
from bzrlib.store.weave import WeaveStore
 
31
from bzrlib.store.text import TextStore
 
32
from bzrlib.symbol_versioning import *
 
33
from bzrlib.trace import mutter
 
34
from bzrlib.tree import RevisionTree
 
35
from bzrlib.testament import Testament
 
36
from bzrlib.tree import EmptyTree
 
37
import bzrlib.ui
 
38
import bzrlib.xml5
 
39
 
 
40
 
 
41
class Repository(object):
247
42
    """Repository holding history for one or more branches.
248
43
 
249
44
    The repository holds and retrieves historical information including
250
45
    revisions and file history.  It's normally accessed only by the Branch,
251
46
    which views a particular line of development through that history.
252
47
 
253
 
    See VersionedFileRepository in bzrlib.vf_repository for the
254
 
    base class for most Bazaar repositories.
 
48
    The Repository builds on top of Stores and a Transport, which respectively 
 
49
    describe the disk data format and the way of accessing the (possibly 
 
50
    remote) disk.
255
51
    """
256
52
 
257
 
    def abort_write_group(self, suppress_errors=False):
258
 
        """Commit the contents accrued within the current write group.
259
 
 
260
 
        :param suppress_errors: if true, abort_write_group will catch and log
261
 
            unexpected errors that happen during the abort, rather than
262
 
            allowing them to propagate.  Defaults to False.
263
 
 
264
 
        :seealso: start_write_group.
265
 
        """
266
 
        if self._write_group is not self.get_transaction():
267
 
            # has an unlock or relock occured ?
268
 
            if suppress_errors:
269
 
                mutter(
270
 
                '(suppressed) mismatched lock context and write group. %r, %r',
271
 
                self._write_group, self.get_transaction())
272
 
                return
273
 
            raise errors.BzrError(
274
 
                'mismatched lock context and write group. %r, %r' %
275
 
                (self._write_group, self.get_transaction()))
276
 
        try:
277
 
            self._abort_write_group()
278
 
        except Exception, exc:
279
 
            self._write_group = None
280
 
            if not suppress_errors:
281
 
                raise
282
 
            mutter('abort_write_group failed')
283
 
            log_exception_quietly()
284
 
            note(gettext('bzr: ERROR (ignored): %s'), exc)
285
 
        self._write_group = None
286
 
 
287
 
    def _abort_write_group(self):
288
 
        """Template method for per-repository write group cleanup.
289
 
 
290
 
        This is called during abort before the write group is considered to be
291
 
        finished and should cleanup any internal state accrued during the write
292
 
        group. There is no requirement that data handed to the repository be
293
 
        *not* made available - this is not a rollback - but neither should any
294
 
        attempt be made to ensure that data added is fully commited. Abort is
295
 
        invoked when an error has occured so futher disk or network operations
296
 
        may not be possible or may error and if possible should not be
297
 
        attempted.
298
 
        """
299
 
 
300
 
    def add_fallback_repository(self, repository):
301
 
        """Add a repository to use for looking up data not held locally.
302
 
 
303
 
        :param repository: A repository.
304
 
        """
305
 
        raise NotImplementedError(self.add_fallback_repository)
306
 
 
307
 
    def _check_fallback_repository(self, repository):
308
 
        """Check that this repository can fallback to repository safely.
309
 
 
310
 
        Raise an error if not.
311
 
 
312
 
        :param repository: A repository to fallback to.
313
 
        """
314
 
        return InterRepository._assert_same_model(self, repository)
315
 
 
 
53
    @needs_read_lock
 
54
    def _all_possible_ids(self):
 
55
        """Return all the possible revisions that we could find."""
 
56
        return self.get_inventory_weave().names()
 
57
 
 
58
    @needs_read_lock
316
59
    def all_revision_ids(self):
317
 
        """Returns a list of all the revision ids in the repository.
318
 
 
319
 
        This is conceptually deprecated because code should generally work on
320
 
        the graph reachable from a particular revision, and ignore any other
321
 
        revisions that might be present.  There is no direct replacement
322
 
        method.
323
 
        """
324
 
        if 'evil' in debug.debug_flags:
325
 
            mutter_callsite(2, "all_revision_ids is linear with history.")
326
 
        return self._all_revision_ids()
327
 
 
328
 
    def _all_revision_ids(self):
329
 
        """Returns a list of all the revision ids in the repository.
330
 
 
331
 
        These are in as much topological order as the underlying store can
332
 
        present.
333
 
        """
334
 
        raise NotImplementedError(self._all_revision_ids)
335
 
 
336
 
    def break_lock(self):
337
 
        """Break a lock if one is present from another instance.
338
 
 
339
 
        Uses the ui factory to ask for confirmation if the lock may be from
340
 
        an active process.
341
 
        """
342
 
        self.control_files.break_lock()
 
60
        """Returns a list of all the revision ids in the repository. 
 
61
 
 
62
        These are in as much topological order as the underlying store can 
 
63
        present: for weaves ghosts may lead to a lack of correctness until
 
64
        the reweave updates the parents list.
 
65
        """
 
66
        result = self._all_possible_ids()
 
67
        return self._eliminate_revisions_not_present(result)
 
68
 
 
69
    @needs_read_lock
 
70
    def _eliminate_revisions_not_present(self, revision_ids):
 
71
        """Check every revision id in revision_ids to see if we have it.
 
72
 
 
73
        Returns a set of the present revisions.
 
74
        """
 
75
        result = []
 
76
        for id in revision_ids:
 
77
            if self.has_revision(id):
 
78
               result.append(id)
 
79
        return result
343
80
 
344
81
    @staticmethod
345
 
    def create(controldir):
346
 
        """Construct the current default format repository in controldir."""
347
 
        return RepositoryFormat.get_default_format().initialize(controldir)
 
82
    def create(a_bzrdir):
 
83
        """Construct the current default format repository in a_bzrdir."""
 
84
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
348
85
 
349
 
    def __init__(self, _format, controldir, control_files):
 
86
    def __init__(self, _format, a_bzrdir, control_files, revision_store):
350
87
        """instantiate a Repository.
351
88
 
352
89
        :param _format: The format of the repository on disk.
353
 
        :param controldir: The ControlDir of the repository.
354
 
        :param control_files: Control files to use for locking, etc.
 
90
        :param a_bzrdir: The BzrDir of the repository.
 
91
 
 
92
        In the future we will have a single api for all stores for
 
93
        getting file texts, inventories and revisions, then
 
94
        this construct will accept instances of those things.
355
95
        """
356
 
        # In the future we will have a single api for all stores for
357
 
        # getting file texts, inventories and revisions, then
358
 
        # this construct will accept instances of those things.
359
 
        super(Repository, self).__init__()
 
96
        object.__init__(self)
360
97
        self._format = _format
361
98
        # the following are part of the public API for Repository:
362
 
        self.bzrdir = controldir
 
99
        self.bzrdir = a_bzrdir
363
100
        self.control_files = control_files
364
 
        # for tests
365
 
        self._write_group = None
366
 
        # Additional places to query for data.
367
 
        self._fallback_repositories = []
368
 
 
369
 
    @property
370
 
    def user_transport(self):
371
 
        return self.bzrdir.user_transport
372
 
 
373
 
    @property
374
 
    def control_transport(self):
375
 
        return self._transport
376
 
 
377
 
    def __repr__(self):
378
 
        if self._fallback_repositories:
379
 
            return '%s(%r, fallback_repositories=%r)' % (
380
 
                self.__class__.__name__,
381
 
                self.base,
382
 
                self._fallback_repositories)
383
 
        else:
384
 
            return '%s(%r)' % (self.__class__.__name__,
385
 
                               self.base)
386
 
 
387
 
    def _has_same_fallbacks(self, other_repo):
388
 
        """Returns true if the repositories have the same fallbacks."""
389
 
        my_fb = self._fallback_repositories
390
 
        other_fb = other_repo._fallback_repositories
391
 
        if len(my_fb) != len(other_fb):
392
 
            return False
393
 
        for f, g in zip(my_fb, other_fb):
394
 
            if not f.has_same_location(g):
395
 
                return False
396
 
        return True
397
 
 
398
 
    def has_same_location(self, other):
399
 
        """Returns a boolean indicating if this repository is at the same
400
 
        location as another repository.
401
 
 
402
 
        This might return False even when two repository objects are accessing
403
 
        the same physical repository via different URLs.
404
 
        """
405
 
        if self.__class__ is not other.__class__:
406
 
            return False
407
 
        return (self.control_url == other.control_url)
408
 
 
409
 
    def is_in_write_group(self):
410
 
        """Return True if there is an open write group.
411
 
 
412
 
        :seealso: start_write_group.
413
 
        """
414
 
        return self._write_group is not None
415
 
 
416
 
    def is_locked(self):
417
 
        return self.control_files.is_locked()
418
 
 
419
 
    def is_write_locked(self):
420
 
        """Return True if this object is write locked."""
421
 
        return self.is_locked() and self.control_files._lock_mode == 'w'
422
 
 
423
 
    def lock_write(self, token=None):
424
 
        """Lock this repository for writing.
425
 
 
426
 
        This causes caching within the repository obejct to start accumlating
427
 
        data during reads, and allows a 'write_group' to be obtained. Write
428
 
        groups must be used for actual data insertion.
429
 
 
430
 
        A token should be passed in if you know that you have locked the object
431
 
        some other way, and need to synchronise this object's state with that
432
 
        fact.
433
 
 
434
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
435
 
 
436
 
        :param token: if this is already locked, then lock_write will fail
437
 
            unless the token matches the existing lock.
438
 
        :returns: a token if this instance supports tokens, otherwise None.
439
 
        :raises TokenLockingNotSupported: when a token is given but this
440
 
            instance doesn't support using token locks.
441
 
        :raises MismatchedToken: if the specified token doesn't match the token
442
 
            of the existing lock.
443
 
        :seealso: start_write_group.
444
 
        :return: A RepositoryWriteLockResult.
445
 
        """
446
 
        locked = self.is_locked()
447
 
        token = self.control_files.lock_write(token=token)
448
 
        if not locked:
449
 
            self._warn_if_deprecated()
450
 
            self._note_lock('w')
451
 
            for repo in self._fallback_repositories:
452
 
                # Writes don't affect fallback repos
453
 
                repo.lock_read()
454
 
            self._refresh_data()
455
 
        return RepositoryWriteLockResult(self.unlock, token)
 
101
        self.revision_store = revision_store
 
102
 
 
103
    def lock_write(self):
 
104
        self.control_files.lock_write()
456
105
 
457
106
    def lock_read(self):
458
 
        """Lock the repository for read operations.
459
 
 
460
 
        :return: An object with an unlock method which will release the lock
461
 
            obtained.
462
 
        """
463
 
        locked = self.is_locked()
464
107
        self.control_files.lock_read()
465
 
        if not locked:
466
 
            self._warn_if_deprecated()
467
 
            self._note_lock('r')
468
 
            for repo in self._fallback_repositories:
469
 
                repo.lock_read()
470
 
            self._refresh_data()
471
 
        return LogicalLockResult(self.unlock)
472
 
 
473
 
    def get_physical_lock_status(self):
474
 
        return self.control_files.get_physical_lock_status()
475
 
 
476
 
    def leave_lock_in_place(self):
477
 
        """Tell this repository not to release the physical lock when this
478
 
        object is unlocked.
479
 
 
480
 
        If lock_write doesn't return a token, then this method is not supported.
481
 
        """
482
 
        self.control_files.leave_in_place()
483
 
 
484
 
    def dont_leave_lock_in_place(self):
485
 
        """Tell this repository to release the physical lock when this
486
 
        object is unlocked, even if it didn't originally acquire it.
487
 
 
488
 
        If lock_write doesn't return a token, then this method is not supported.
489
 
        """
490
 
        self.control_files.dont_leave_in_place()
491
 
 
492
 
    @needs_read_lock
493
 
    def gather_stats(self, revid=None, committers=None):
494
 
        """Gather statistics from a revision id.
495
 
 
496
 
        :param revid: The revision id to gather statistics from, if None, then
497
 
            no revision specific statistics are gathered.
498
 
        :param committers: Optional parameter controlling whether to grab
499
 
            a count of committers from the revision specific statistics.
500
 
        :return: A dictionary of statistics. Currently this contains:
501
 
            committers: The number of committers if requested.
502
 
            firstrev: A tuple with timestamp, timezone for the penultimate left
503
 
                most ancestor of revid, if revid is not the NULL_REVISION.
504
 
            latestrev: A tuple with timestamp, timezone for revid, if revid is
505
 
                not the NULL_REVISION.
506
 
            revisions: The total revision count in the repository.
507
 
            size: An estimate disk size of the repository in bytes.
508
 
        """
509
 
        result = {}
510
 
        if revid and committers:
511
 
            result['committers'] = 0
512
 
        if revid and revid != _mod_revision.NULL_REVISION:
513
 
            graph = self.get_graph()
514
 
            if committers:
515
 
                all_committers = set()
516
 
            revisions = [r for (r, p) in graph.iter_ancestry([revid])
517
 
                        if r != _mod_revision.NULL_REVISION]
518
 
            last_revision = None
519
 
            if not committers:
520
 
                # ignore the revisions in the middle - just grab first and last
521
 
                revisions = revisions[0], revisions[-1]
522
 
            for revision in self.get_revisions(revisions):
523
 
                if not last_revision:
524
 
                    last_revision = revision
525
 
                if committers:
526
 
                    all_committers.add(revision.committer)
527
 
            first_revision = revision
528
 
            if committers:
529
 
                result['committers'] = len(all_committers)
530
 
            result['firstrev'] = (first_revision.timestamp,
531
 
                first_revision.timezone)
532
 
            result['latestrev'] = (last_revision.timestamp,
533
 
                last_revision.timezone)
534
 
        return result
535
 
 
536
 
    def find_branches(self, using=False):
537
 
        """Find branches underneath this repository.
538
 
 
539
 
        This will include branches inside other branches.
540
 
 
541
 
        :param using: If True, list only branches using this repository.
542
 
        """
543
 
        if using and not self.is_shared():
544
 
            return self.bzrdir.list_branches()
545
 
        class Evaluator(object):
546
 
 
547
 
            def __init__(self):
548
 
                self.first_call = True
549
 
 
550
 
            def __call__(self, controldir):
551
 
                # On the first call, the parameter is always the controldir
552
 
                # containing the current repo.
553
 
                if not self.first_call:
554
 
                    try:
555
 
                        repository = controldir.open_repository()
556
 
                    except errors.NoRepositoryPresent:
557
 
                        pass
558
 
                    else:
559
 
                        return False, ([], repository)
560
 
                self.first_call = False
561
 
                value = (controldir.list_branches(), None)
562
 
                return True, value
563
 
 
564
 
        ret = []
565
 
        for branches, repository in controldir.ControlDir.find_bzrdirs(
566
 
                self.user_transport, evaluate=Evaluator()):
567
 
            if branches is not None:
568
 
                ret.extend(branches)
569
 
            if not using and repository is not None:
570
 
                ret.extend(repository.find_branches())
571
 
        return ret
572
 
 
573
 
    @needs_read_lock
574
 
    def search_missing_revision_ids(self, other,
575
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
576
 
            find_ghosts=True, revision_ids=None, if_present_ids=None,
577
 
            limit=None):
 
108
 
 
109
    @needs_read_lock
 
110
    def missing_revision_ids(self, other, revision_id=None):
578
111
        """Return the revision ids that other has that this does not.
579
 
 
 
112
        
580
113
        These are returned in topological order.
581
114
 
582
115
        revision_id: only return revision ids included by revision_id.
583
116
        """
584
 
        if symbol_versioning.deprecated_passed(revision_id):
585
 
            symbol_versioning.warn(
586
 
                'search_missing_revision_ids(revision_id=...) was '
587
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
588
 
                DeprecationWarning, stacklevel=3)
589
 
            if revision_ids is not None:
590
 
                raise AssertionError(
591
 
                    'revision_ids is mutually exclusive with revision_id')
592
 
            if revision_id is not None:
593
 
                revision_ids = [revision_id]
594
 
        return InterRepository.get(other, self).search_missing_revision_ids(
595
 
            find_ghosts=find_ghosts, revision_ids=revision_ids,
596
 
            if_present_ids=if_present_ids, limit=limit)
 
117
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
597
118
 
598
119
    @staticmethod
599
120
    def open(base):
602
123
        For instance, if the repository is at URL/.bzr/repository,
603
124
        Repository.open(URL) -> a Repository instance.
604
125
        """
605
 
        control = controldir.ControlDir.open(base)
 
126
        control = bzrlib.bzrdir.BzrDir.open(base)
606
127
        return control.open_repository()
607
128
 
608
 
    def copy_content_into(self, destination, revision_id=None):
 
129
    def copy_content_into(self, destination, revision_id=None, basis=None):
609
130
        """Make a complete copy of the content in self into destination.
610
 
 
611
 
        This is a destructive operation! Do not use it on existing
 
131
        
 
132
        This is a destructive operation! Do not use it on existing 
612
133
        repositories.
613
134
        """
614
 
        return InterRepository.get(self, destination).copy_content(revision_id)
615
 
 
616
 
    def commit_write_group(self):
617
 
        """Commit the contents accrued within the current write group.
618
 
 
619
 
        :seealso: start_write_group.
620
 
        
621
 
        :return: it may return an opaque hint that can be passed to 'pack'.
622
 
        """
623
 
        if self._write_group is not self.get_transaction():
624
 
            # has an unlock or relock occured ?
625
 
            raise errors.BzrError('mismatched lock context %r and '
626
 
                'write group %r.' %
627
 
                (self.get_transaction(), self._write_group))
628
 
        result = self._commit_write_group()
629
 
        self._write_group = None
630
 
        return result
631
 
 
632
 
    def _commit_write_group(self):
633
 
        """Template method for per-repository write group cleanup.
634
 
 
635
 
        This is called before the write group is considered to be
636
 
        finished and should ensure that all data handed to the repository
637
 
        for writing during the write group is safely committed (to the
638
 
        extent possible considering file system caching etc).
639
 
        """
640
 
 
641
 
    def suspend_write_group(self):
642
 
        raise errors.UnsuspendableWriteGroup(self)
643
 
 
644
 
    def refresh_data(self):
645
 
        """Re-read any data needed to synchronise with disk.
646
 
 
647
 
        This method is intended to be called after another repository instance
648
 
        (such as one used by a smart server) has inserted data into the
649
 
        repository. On all repositories this will work outside of write groups.
650
 
        Some repository formats (pack and newer for bzrlib native formats)
651
 
        support refresh_data inside write groups. If called inside a write
652
 
        group on a repository that does not support refreshing in a write group
653
 
        IsInWriteGroupError will be raised.
654
 
        """
655
 
        self._refresh_data()
656
 
 
657
 
    def resume_write_group(self, tokens):
658
 
        if not self.is_write_locked():
659
 
            raise errors.NotWriteLocked(self)
660
 
        if self._write_group:
661
 
            raise errors.BzrError('already in a write group')
662
 
        self._resume_write_group(tokens)
663
 
        # so we can detect unlock/relock - the write group is now entered.
664
 
        self._write_group = self.get_transaction()
665
 
 
666
 
    def _resume_write_group(self, tokens):
667
 
        raise errors.UnsuspendableWriteGroup(self)
668
 
 
669
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
670
 
            fetch_spec=None):
 
135
        return InterRepository.get(self, destination).copy_content(revision_id, basis)
 
136
 
 
137
    def fetch(self, source, revision_id=None, pb=None):
671
138
        """Fetch the content required to construct revision_id from source.
672
139
 
673
 
        If revision_id is None and fetch_spec is None, then all content is
674
 
        copied.
675
 
 
676
 
        fetch() may not be used when the repository is in a write group -
677
 
        either finish the current write group before using fetch, or use
678
 
        fetch before starting the write group.
679
 
 
680
 
        :param find_ghosts: Find and copy revisions in the source that are
681
 
            ghosts in the target (and not reachable directly by walking out to
682
 
            the first-present revision in target from revision_id).
683
 
        :param revision_id: If specified, all the content needed for this
684
 
            revision ID will be copied to the target.  Fetch will determine for
685
 
            itself which content needs to be copied.
686
 
        :param fetch_spec: If specified, a SearchResult or
687
 
            PendingAncestryResult that describes which revisions to copy.  This
688
 
            allows copying multiple heads at once.  Mutually exclusive with
689
 
            revision_id.
690
 
        """
691
 
        if fetch_spec is not None and revision_id is not None:
692
 
            raise AssertionError(
693
 
                "fetch_spec and revision_id are mutually exclusive.")
694
 
        if self.is_in_write_group():
695
 
            raise errors.InternalBzrError(
696
 
                "May not fetch while in a write group.")
697
 
        # fast path same-url fetch operations
698
 
        # TODO: lift out to somewhere common with RemoteRepository
699
 
        # <https://bugs.launchpad.net/bzr/+bug/401646>
700
 
        if (self.has_same_location(source)
701
 
            and fetch_spec is None
702
 
            and self._has_same_fallbacks(source)):
703
 
            # check that last_revision is in 'from' and then return a
704
 
            # no-operation.
705
 
            if (revision_id is not None and
706
 
                not _mod_revision.is_null(revision_id)):
707
 
                self.get_revision(revision_id)
708
 
            return 0, []
709
 
        inter = InterRepository.get(source, self)
710
 
        return inter.fetch(revision_id=revision_id,
711
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
712
 
 
713
 
    def create_bundle(self, target, base, fileobj, format=None):
714
 
        return serializer.write_bundle(self, target, base, fileobj, format)
715
 
 
716
 
    def get_commit_builder(self, branch, parents, config, timestamp=None,
717
 
                           timezone=None, committer=None, revprops=None,
718
 
                           revision_id=None, lossy=False):
719
 
        """Obtain a CommitBuilder for this repository.
720
 
 
721
 
        :param branch: Branch to commit to.
722
 
        :param parents: Revision ids of the parents of the new revision.
723
 
        :param config: Configuration to use.
724
 
        :param timestamp: Optional timestamp recorded for commit.
725
 
        :param timezone: Optional timezone for timestamp.
726
 
        :param committer: Optional committer to set for commit.
727
 
        :param revprops: Optional dictionary of revision properties.
728
 
        :param revision_id: Optional revision id.
729
 
        :param lossy: Whether to discard data that can not be natively
730
 
            represented, when pushing to a foreign VCS
731
 
        """
732
 
        raise NotImplementedError(self.get_commit_builder)
733
 
 
734
 
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
140
        If revision_id is None all content is copied.
 
141
        """
 
142
        return InterRepository.get(source, self).fetch(revision_id=revision_id,
 
143
                                                       pb=pb)
 
144
 
735
145
    def unlock(self):
736
 
        if (self.control_files._lock_count == 1 and
737
 
            self.control_files._lock_mode == 'w'):
738
 
            if self._write_group is not None:
739
 
                self.abort_write_group()
740
 
                self.control_files.unlock()
741
 
                raise errors.BzrError(
742
 
                    'Must end write groups before releasing write locks.')
743
146
        self.control_files.unlock()
744
 
        if self.control_files._lock_count == 0:
745
 
            for repo in self._fallback_repositories:
746
 
                repo.unlock()
747
147
 
748
148
    @needs_read_lock
749
 
    def clone(self, controldir, revision_id=None):
750
 
        """Clone this repository into controldir using the current format.
 
149
    def clone(self, a_bzrdir, revision_id=None, basis=None):
 
150
        """Clone this repository into a_bzrdir using the current format.
751
151
 
752
152
        Currently no check is made that the format of this repository and
753
153
        the bzrdir format are compatible. FIXME RBC 20060201.
754
 
 
755
 
        :return: The newly created destination repository.
756
 
        """
757
 
        # TODO: deprecate after 0.16; cloning this with all its settings is
758
 
        # probably not very useful -- mbp 20070423
759
 
        dest_repo = self._create_sprouting_repo(
760
 
            controldir, shared=self.is_shared())
761
 
        self.copy_content_into(dest_repo, revision_id)
762
 
        return dest_repo
763
 
 
764
 
    def start_write_group(self):
765
 
        """Start a write group in the repository.
766
 
 
767
 
        Write groups are used by repositories which do not have a 1:1 mapping
768
 
        between file ids and backend store to manage the insertion of data from
769
 
        both fetch and commit operations.
770
 
 
771
 
        A write lock is required around the start_write_group/commit_write_group
772
 
        for the support of lock-requiring repository formats.
773
 
 
774
 
        One can only insert data into a repository inside a write group.
775
 
 
776
 
        :return: None.
777
 
        """
778
 
        if not self.is_write_locked():
779
 
            raise errors.NotWriteLocked(self)
780
 
        if self._write_group:
781
 
            raise errors.BzrError('already in a write group')
782
 
        self._start_write_group()
783
 
        # so we can detect unlock/relock - the write group is now entered.
784
 
        self._write_group = self.get_transaction()
785
 
 
786
 
    def _start_write_group(self):
787
 
        """Template method for per-repository write group startup.
788
 
 
789
 
        This is called before the write group is considered to be
790
 
        entered.
791
 
        """
792
 
 
793
 
    @needs_read_lock
794
 
    def sprout(self, to_bzrdir, revision_id=None):
795
 
        """Create a descendent repository for new development.
796
 
 
797
 
        Unlike clone, this does not copy the settings of the repository.
798
 
        """
799
 
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
800
 
        dest_repo.fetch(self, revision_id=revision_id)
801
 
        return dest_repo
802
 
 
803
 
    def _create_sprouting_repo(self, a_bzrdir, shared):
 
154
        """
804
155
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
805
156
            # use target default format.
806
 
            dest_repo = a_bzrdir.create_repository()
 
157
            result = a_bzrdir.create_repository()
 
158
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
 
159
        elif isinstance(a_bzrdir._format,
 
160
                      (bzrlib.bzrdir.BzrDirFormat4,
 
161
                       bzrlib.bzrdir.BzrDirFormat5,
 
162
                       bzrlib.bzrdir.BzrDirFormat6)):
 
163
            result = a_bzrdir.open_repository()
807
164
        else:
808
 
            # Most control formats need the repository to be specifically
809
 
            # created, but on some old all-in-one formats it's not needed
810
 
            try:
811
 
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
812
 
            except errors.UninitializableFormat:
813
 
                dest_repo = a_bzrdir.open_repository()
814
 
        return dest_repo
 
165
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
 
166
        self.copy_content_into(result, revision_id, basis)
 
167
        return result
815
168
 
816
 
    @needs_read_lock
817
169
    def has_revision(self, revision_id):
818
 
        """True if this repository has a copy of the revision."""
819
 
        return revision_id in self.has_revisions((revision_id,))
820
 
 
821
 
    @needs_read_lock
822
 
    def has_revisions(self, revision_ids):
823
 
        """Probe to find out the presence of multiple revisions.
824
 
 
825
 
        :param revision_ids: An iterable of revision_ids.
826
 
        :return: A set of the revision_ids that were present.
827
 
        """
828
 
        raise NotImplementedError(self.has_revisions)
 
170
        """True if this branch has a copy of the revision.
 
171
 
 
172
        This does not necessarily imply the revision is merge
 
173
        or on the mainline."""
 
174
        return (revision_id is None
 
175
                or self.revision_store.has_id(revision_id))
 
176
 
 
177
    @needs_read_lock
 
178
    def get_revision_xml_file(self, revision_id):
 
179
        """Return XML file object for revision object."""
 
180
        if not revision_id or not isinstance(revision_id, basestring):
 
181
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
182
        try:
 
183
            return self.revision_store.get(revision_id)
 
184
        except (IndexError, KeyError):
 
185
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
186
 
 
187
    @needs_read_lock
 
188
    def get_revision_xml(self, revision_id):
 
189
        return self.get_revision_xml_file(revision_id).read()
829
190
 
830
191
    @needs_read_lock
831
192
    def get_revision(self, revision_id):
832
 
        """Return the Revision object for a named revision."""
833
 
        return self.get_revisions([revision_id])[0]
834
 
 
835
 
    def get_revision_reconcile(self, revision_id):
836
 
        """'reconcile' helper routine that allows access to a revision always.
837
 
 
838
 
        This variant of get_revision does not cross check the weave graph
839
 
        against the revision one as get_revision does: but it should only
840
 
        be used by reconcile, or reconcile-alike commands that are correcting
841
 
        or testing the revision graph.
842
 
        """
843
 
        raise NotImplementedError(self.get_revision_reconcile)
844
 
 
845
 
    def get_revisions(self, revision_ids):
846
 
        """Get many revisions at once.
847
 
        
848
 
        Repositories that need to check data on every revision read should 
849
 
        subclass this method.
850
 
        """
851
 
        raise NotImplementedError(self.get_revisions)
852
 
 
853
 
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
854
 
        """Produce a generator of revision deltas.
855
 
 
856
 
        Note that the input is a sequence of REVISIONS, not revision_ids.
857
 
        Trees will be held in memory until the generator exits.
858
 
        Each delta is relative to the revision's lefthand predecessor.
859
 
 
860
 
        :param specific_fileids: if not None, the result is filtered
861
 
          so that only those file-ids, their parents and their
862
 
          children are included.
863
 
        """
864
 
        # Get the revision-ids of interest
865
 
        required_trees = set()
866
 
        for revision in revisions:
867
 
            required_trees.add(revision.revision_id)
868
 
            required_trees.update(revision.parent_ids[:1])
869
 
 
870
 
        # Get the matching filtered trees. Note that it's more
871
 
        # efficient to pass filtered trees to changes_from() rather
872
 
        # than doing the filtering afterwards. changes_from() could
873
 
        # arguably do the filtering itself but it's path-based, not
874
 
        # file-id based, so filtering before or afterwards is
875
 
        # currently easier.
876
 
        if specific_fileids is None:
877
 
            trees = dict((t.get_revision_id(), t) for
878
 
                t in self.revision_trees(required_trees))
879
 
        else:
880
 
            trees = dict((t.get_revision_id(), t) for
881
 
                t in self._filtered_revision_trees(required_trees,
882
 
                specific_fileids))
883
 
 
884
 
        # Calculate the deltas
885
 
        for revision in revisions:
886
 
            if not revision.parent_ids:
887
 
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
888
 
            else:
889
 
                old_tree = trees[revision.parent_ids[0]]
890
 
            yield trees[revision.revision_id].changes_from(old_tree)
 
193
        """Return the Revision object for a named revision"""
 
194
        xml_file = self.get_revision_xml_file(revision_id)
 
195
 
 
196
        try:
 
197
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
198
        except SyntaxError, e:
 
199
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
200
                                         [revision_id,
 
201
                                          str(e)])
 
202
            
 
203
        assert r.revision_id == revision_id
 
204
        return r
891
205
 
892
206
    @needs_read_lock
893
 
    def get_revision_delta(self, revision_id, specific_fileids=None):
894
 
        """Return the delta for one revision.
895
 
 
896
 
        The delta is relative to the left-hand predecessor of the
897
 
        revision.
898
 
 
899
 
        :param specific_fileids: if not None, the result is filtered
900
 
          so that only those file-ids, their parents and their
901
 
          children are included.
902
 
        """
903
 
        r = self.get_revision(revision_id)
904
 
        return list(self.get_deltas_for_revisions([r],
905
 
            specific_fileids=specific_fileids))[0]
 
207
    def get_revision_sha1(self, revision_id):
 
208
        """Hash the stored value of a revision, and return it."""
 
209
        # In the future, revision entries will be signed. At that
 
210
        # point, it is probably best *not* to include the signature
 
211
        # in the revision hash. Because that lets you re-sign
 
212
        # the revision, (add signatures/remove signatures) and still
 
213
        # have all hash pointers stay consistent.
 
214
        # But for now, just hash the contents.
 
215
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
906
216
 
907
217
    @needs_write_lock
908
218
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
909
 
        signature = gpg_strategy.sign(plaintext)
910
 
        self.add_signature_text(revision_id, signature)
911
 
 
912
 
    def add_signature_text(self, revision_id, signature):
913
 
        """Store a signature text for a revision.
914
 
 
915
 
        :param revision_id: Revision id of the revision
916
 
        :param signature: Signature text.
917
 
        """
918
 
        raise NotImplementedError(self.add_signature_text)
919
 
 
920
 
    def _find_parent_ids_of_revisions(self, revision_ids):
921
 
        """Find all parent ids that are mentioned in the revision graph.
922
 
 
923
 
        :return: set of revisions that are parents of revision_ids which are
924
 
            not part of revision_ids themselves
925
 
        """
926
 
        parent_map = self.get_parent_map(revision_ids)
927
 
        parent_ids = set()
928
 
        map(parent_ids.update, parent_map.itervalues())
929
 
        parent_ids.difference_update(revision_ids)
930
 
        parent_ids.discard(_mod_revision.NULL_REVISION)
931
 
        return parent_ids
932
 
 
933
 
    def iter_files_bytes(self, desired_files):
934
 
        """Iterate through file versions.
935
 
 
936
 
        Files will not necessarily be returned in the order they occur in
937
 
        desired_files.  No specific order is guaranteed.
938
 
 
939
 
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
940
 
        value supplied by the caller as part of desired_files.  It should
941
 
        uniquely identify the file version in the caller's context.  (Examples:
942
 
        an index number or a TreeTransform trans_id.)
943
 
 
944
 
        :param desired_files: a list of (file_id, revision_id, identifier)
945
 
            triples
946
 
        """
947
 
        raise NotImplementedError(self.iter_files_bytes)
948
 
 
949
 
    def get_rev_id_for_revno(self, revno, known_pair):
950
 
        """Return the revision id of a revno, given a later (revno, revid)
951
 
        pair in the same history.
952
 
 
953
 
        :return: if found (True, revid).  If the available history ran out
954
 
            before reaching the revno, then this returns
955
 
            (False, (closest_revno, closest_revid)).
956
 
        """
957
 
        known_revno, known_revid = known_pair
958
 
        partial_history = [known_revid]
959
 
        distance_from_known = known_revno - revno
960
 
        if distance_from_known < 0:
961
 
            raise ValueError(
962
 
                'requested revno (%d) is later than given known revno (%d)'
963
 
                % (revno, known_revno))
 
219
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
220
                                revision_id, "sig")
 
221
 
 
222
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
223
        """Find file_id(s) which are involved in the changes between revisions.
 
224
 
 
225
        This determines the set of revisions which are involved, and then
 
226
        finds all file ids affected by those revisions.
 
227
        """
 
228
        # TODO: jam 20060119 This code assumes that w.inclusions will
 
229
        #       always be correct. But because of the presence of ghosts
 
230
        #       it is possible to be wrong.
 
231
        #       One specific example from Robert Collins:
 
232
        #       Two branches, with revisions ABC, and AD
 
233
        #       C is a ghost merge of D.
 
234
        #       Inclusions doesn't recognize D as an ancestor.
 
235
        #       If D is ever merged in the future, the weave
 
236
        #       won't be fixed, because AD never saw revision C
 
237
        #       to cause a conflict which would force a reweave.
 
238
        w = self.get_inventory_weave()
 
239
        from_set = set(w.inclusions([w.lookup(from_revid)]))
 
240
        to_set = set(w.inclusions([w.lookup(to_revid)]))
 
241
        included = to_set.difference(from_set)
 
242
        changed = map(w.idx_to_name, included)
 
243
        return self._fileid_involved_by_set(changed)
 
244
 
 
245
    def fileid_involved(self, last_revid=None):
 
246
        """Find all file_ids modified in the ancestry of last_revid.
 
247
 
 
248
        :param last_revid: If None, last_revision() will be used.
 
249
        """
 
250
        w = self.get_inventory_weave()
 
251
        if not last_revid:
 
252
            changed = set(w._names)
 
253
        else:
 
254
            included = w.inclusions([w.lookup(last_revid)])
 
255
            changed = map(w.idx_to_name, included)
 
256
        return self._fileid_involved_by_set(changed)
 
257
 
 
258
    def fileid_involved_by_set(self, changes):
 
259
        """Find all file_ids modified by the set of revisions passed in.
 
260
 
 
261
        :param changes: A set() of revision ids
 
262
        """
 
263
        # TODO: jam 20060119 This line does *nothing*, remove it.
 
264
        #       or better yet, change _fileid_involved_by_set so
 
265
        #       that it takes the inventory weave, rather than
 
266
        #       pulling it out by itself.
 
267
        return self._fileid_involved_by_set(changes)
 
268
 
 
269
    def _fileid_involved_by_set(self, changes):
 
270
        """Find the set of file-ids affected by the set of revisions.
 
271
 
 
272
        :param changes: A set() of revision ids.
 
273
        :return: A set() of file ids.
 
274
        
 
275
        This peaks at the Weave, interpreting each line, looking to
 
276
        see if it mentions one of the revisions. And if so, includes
 
277
        the file id mentioned.
 
278
        This expects both the Weave format, and the serialization
 
279
        to have a single line per file/directory, and to have
 
280
        fileid="" and revision="" on that line.
 
281
        """
 
282
        assert isinstance(self._format, (RepositoryFormat5,
 
283
                                         RepositoryFormat6,
 
284
                                         RepositoryFormat7,
 
285
                                         RepositoryFormatKnit1)), \
 
286
            "fileid_involved only supported for branches which store inventory as unnested xml"
 
287
 
 
288
        w = self.get_inventory_weave()
 
289
        file_ids = set()
 
290
        for line in w._weave:
 
291
 
 
292
            # it is ugly, but it is due to the weave structure
 
293
            if not isinstance(line, basestring): continue
 
294
 
 
295
            start = line.find('file_id="')+9
 
296
            if start < 9: continue
 
297
            end = line.find('"', start)
 
298
            assert end>= 0
 
299
            file_id = xml.sax.saxutils.unescape(line[start:end])
 
300
 
 
301
            # check if file_id is already present
 
302
            if file_id in file_ids: continue
 
303
 
 
304
            start = line.find('revision="')+10
 
305
            if start < 10: continue
 
306
            end = line.find('"', start)
 
307
            assert end>= 0
 
308
            revision_id = xml.sax.saxutils.unescape(line[start:end])
 
309
 
 
310
            if revision_id in changes:
 
311
                file_ids.add(file_id)
 
312
        return file_ids
 
313
 
 
314
    @needs_read_lock
 
315
    def get_inventory_weave(self):
 
316
        return self.control_weaves.get_weave('inventory',
 
317
            self.get_transaction())
 
318
 
 
319
    @needs_read_lock
 
320
    def get_inventory(self, revision_id):
 
321
        """Get Inventory object by hash."""
 
322
        xml = self.get_inventory_xml(revision_id)
 
323
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
324
 
 
325
    @needs_read_lock
 
326
    def get_inventory_xml(self, revision_id):
 
327
        """Get inventory XML as a file object."""
964
328
        try:
965
 
            _iter_for_revno(
966
 
                self, partial_history, stop_index=distance_from_known)
967
 
        except errors.RevisionNotPresent, err:
968
 
            if err.revision_id == known_revid:
969
 
                # The start revision (known_revid) wasn't found.
970
 
                raise
971
 
            # This is a stacked repository with no fallbacks, or a there's a
972
 
            # left-hand ghost.  Either way, even though the revision named in
973
 
            # the error isn't in this repo, we know it's the next step in this
974
 
            # left-hand history.
975
 
            partial_history.append(err.revision_id)
976
 
        if len(partial_history) <= distance_from_known:
977
 
            # Didn't find enough history to get a revid for the revno.
978
 
            earliest_revno = known_revno - len(partial_history) + 1
979
 
            return (False, (earliest_revno, partial_history[-1]))
980
 
        if len(partial_history) - 1 > distance_from_known:
981
 
            raise AssertionError('_iter_for_revno returned too much history')
982
 
        return (True, partial_history[-1])
983
 
 
984
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
985
 
    def iter_reverse_revision_history(self, revision_id):
986
 
        """Iterate backwards through revision ids in the lefthand history
987
 
 
988
 
        :param revision_id: The revision id to start with.  All its lefthand
989
 
            ancestors will be traversed.
 
329
            assert isinstance(revision_id, basestring), type(revision_id)
 
330
            iw = self.get_inventory_weave()
 
331
            return iw.get_text(iw.lookup(revision_id))
 
332
        except IndexError:
 
333
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
334
 
 
335
    @needs_read_lock
 
336
    def get_inventory_sha1(self, revision_id):
 
337
        """Return the sha1 hash of the inventory entry
990
338
        """
991
 
        graph = self.get_graph()
992
 
        stop_revisions = (None, _mod_revision.NULL_REVISION)
993
 
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
994
 
 
 
339
        return self.get_revision(revision_id).inventory_sha1
 
340
 
 
341
    @needs_read_lock
 
342
    def get_revision_inventory(self, revision_id):
 
343
        """Return inventory of a past revision."""
 
344
        # TODO: Unify this with get_inventory()
 
345
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
346
        # must be the same as its revision, so this is trivial.
 
347
        if revision_id is None:
 
348
            # This does not make sense: if there is no revision,
 
349
            # then it is the current tree inventory surely ?!
 
350
            # and thus get_root_id() is something that looks at the last
 
351
            # commit on the branch, and the get_root_id is an inventory check.
 
352
            raise NotImplementedError
 
353
            # return Inventory(self.get_root_id())
 
354
        else:
 
355
            return self.get_inventory(revision_id)
 
356
 
 
357
    @needs_read_lock
995
358
    def is_shared(self):
996
359
        """Return True if this repository is flagged as a shared repository."""
997
 
        raise NotImplementedError(self.is_shared)
998
 
 
999
 
    @needs_write_lock
1000
 
    def reconcile(self, other=None, thorough=False):
1001
 
        """Reconcile this repository."""
1002
 
        from bzrlib.reconcile import RepoReconciler
1003
 
        reconciler = RepoReconciler(self, thorough=thorough)
1004
 
        reconciler.reconcile()
1005
 
        return reconciler
1006
 
 
1007
 
    def _refresh_data(self):
1008
 
        """Helper called from lock_* to ensure coherency with disk.
1009
 
 
1010
 
        The default implementation does nothing; it is however possible
1011
 
        for repositories to maintain loaded indices across multiple locks
1012
 
        by checking inside their implementation of this method to see
1013
 
        whether their indices are still valid. This depends of course on
1014
 
        the disk format being validatable in this manner. This method is
1015
 
        also called by the refresh_data() public interface to cause a refresh
1016
 
        to occur while in a write lock so that data inserted by a smart server
1017
 
        push operation is visible on the client's instance of the physical
1018
 
        repository.
1019
 
        """
 
360
        # FIXME format 4-6 cannot be shared, this is technically faulty.
 
361
        return self.control_files._transport.has('shared-storage')
1020
362
 
1021
363
    @needs_read_lock
1022
364
    def revision_tree(self, revision_id):
1023
365
        """Return Tree for a revision on this branch.
1024
366
 
1025
 
        `revision_id` may be NULL_REVISION for the empty tree revision.
1026
 
        """
1027
 
        raise NotImplementedError(self.revision_tree)
1028
 
 
1029
 
    def revision_trees(self, revision_ids):
1030
 
        """Return Trees for revisions in this repository.
1031
 
 
1032
 
        :param revision_ids: a sequence of revision-ids;
1033
 
          a revision-id may not be None or 'null:'
1034
 
        """
1035
 
        raise NotImplementedError(self.revision_trees)
 
367
        `revision_id` may be None for the null revision, in which case
 
368
        an `EmptyTree` is returned."""
 
369
        # TODO: refactor this to use an existing revision object
 
370
        # so we don't need to read it in twice.
 
371
        if revision_id is None or revision_id == NULL_REVISION:
 
372
            return EmptyTree()
 
373
        else:
 
374
            inv = self.get_revision_inventory(revision_id)
 
375
            return RevisionTree(self, inv, revision_id)
1036
376
 
1037
377
    @needs_read_lock
1038
 
    @symbol_versioning.deprecated_method(
1039
 
        symbol_versioning.deprecated_in((2, 4, 0)))
1040
 
    def get_ancestry(self, revision_id, topo_sorted=True):
 
378
    def get_ancestry(self, revision_id):
1041
379
        """Return a list of revision-ids integrated by a revision.
1042
 
 
1043
 
        The first element of the list is always None, indicating the origin
1044
 
        revision.  This might change when we have history horizons, or
1045
 
        perhaps we should have a new API.
1046
 
 
 
380
        
1047
381
        This is topologically sorted.
1048
382
        """
1049
 
        if 'evil' in debug.debug_flags:
1050
 
            mutter_callsite(2, "get_ancestry is linear with history.")
1051
 
        if _mod_revision.is_null(revision_id):
 
383
        if revision_id is None:
1052
384
            return [None]
1053
385
        if not self.has_revision(revision_id):
1054
386
            raise errors.NoSuchRevision(self, revision_id)
1055
 
        graph = self.get_graph()
1056
 
        keys = set()
1057
 
        search = graph._make_breadth_first_searcher([revision_id])
1058
 
        while True:
 
387
        w = self.get_inventory_weave()
 
388
        return [None] + map(w.idx_to_name,
 
389
                            w.inclusions([w.lookup(revision_id)]))
 
390
 
 
391
    @needs_read_lock
 
392
    def print_file(self, file, revision_id):
 
393
        """Print `file` to stdout.
 
394
        
 
395
        FIXME RBC 20060125 as John Meinel points out this is a bad api
 
396
        - it writes to stdout, it assumes that that is valid etc. Fix
 
397
        by creating a new more flexible convenience function.
 
398
        """
 
399
        tree = self.revision_tree(revision_id)
 
400
        # use inventory as it was in that revision
 
401
        file_id = tree.inventory.path2id(file)
 
402
        if not file_id:
 
403
            raise BzrError("%r is not present in revision %s" % (file, revno))
1059
404
            try:
1060
 
                found, ghosts = search.next_with_ghosts()
1061
 
            except StopIteration:
1062
 
                break
1063
 
            keys.update(found)
1064
 
        if _mod_revision.NULL_REVISION in keys:
1065
 
            keys.remove(_mod_revision.NULL_REVISION)
1066
 
        if topo_sorted:
1067
 
            parent_map = graph.get_parent_map(keys)
1068
 
            keys = tsort.topo_sort(parent_map)
1069
 
        return [None] + list(keys)
1070
 
 
1071
 
    def pack(self, hint=None, clean_obsolete_packs=False):
1072
 
        """Compress the data within the repository.
1073
 
 
1074
 
        This operation only makes sense for some repository types. For other
1075
 
        types it should be a no-op that just returns.
1076
 
 
1077
 
        This stub method does not require a lock, but subclasses should use
1078
 
        @needs_write_lock as this is a long running call it's reasonable to
1079
 
        implicitly lock for the user.
1080
 
 
1081
 
        :param hint: If not supplied, the whole repository is packed.
1082
 
            If supplied, the repository may use the hint parameter as a
1083
 
            hint for the parts of the repository to pack. A hint can be
1084
 
            obtained from the result of commit_write_group(). Out of
1085
 
            date hints are simply ignored, because concurrent operations
1086
 
            can obsolete them rapidly.
1087
 
 
1088
 
        :param clean_obsolete_packs: Clean obsolete packs immediately after
1089
 
            the pack operation.
1090
 
        """
 
405
                revno = self.revision_id_to_revno(revision_id)
 
406
            except errors.NoSuchRevision:
 
407
                # TODO: This should not be BzrError,
 
408
                # but NoSuchFile doesn't fit either
 
409
                raise BzrError('%r is not present in revision %s' 
 
410
                                % (file, revision_id))
 
411
            else:
 
412
                raise BzrError('%r is not present in revision %s'
 
413
                                % (file, revno))
 
414
        tree.print_file(file_id)
1091
415
 
1092
416
    def get_transaction(self):
1093
417
        return self.control_files.get_transaction()
1094
418
 
1095
 
    def get_parent_map(self, revision_ids):
1096
 
        """See graph.StackedParentsProvider.get_parent_map"""
1097
 
        raise NotImplementedError(self.get_parent_map)
1098
 
 
1099
 
    def _get_parent_map_no_fallbacks(self, revision_ids):
1100
 
        """Same as Repository.get_parent_map except doesn't query fallbacks."""
1101
 
        # revisions index works in keys; this just works in revisions
1102
 
        # therefore wrap and unwrap
1103
 
        query_keys = []
1104
 
        result = {}
1105
 
        for revision_id in revision_ids:
1106
 
            if revision_id == _mod_revision.NULL_REVISION:
1107
 
                result[revision_id] = ()
1108
 
            elif revision_id is None:
1109
 
                raise ValueError('get_parent_map(None) is not valid')
1110
 
            else:
1111
 
                query_keys.append((revision_id ,))
1112
 
        vf = self.revisions.without_fallbacks()
1113
 
        for ((revision_id,), parent_keys) in \
1114
 
                vf.get_parent_map(query_keys).iteritems():
1115
 
            if parent_keys:
1116
 
                result[revision_id] = tuple([parent_revid
1117
 
                    for (parent_revid,) in parent_keys])
1118
 
            else:
1119
 
                result[revision_id] = (_mod_revision.NULL_REVISION,)
1120
 
        return result
1121
 
 
1122
 
    def _make_parents_provider(self):
1123
 
        if not self._format.supports_external_lookups:
1124
 
            return self
1125
 
        return graph.StackedParentsProvider(_LazyListJoin(
1126
 
            [self._make_parents_provider_unstacked()],
1127
 
            self._fallback_repositories))
1128
 
 
1129
 
    def _make_parents_provider_unstacked(self):
1130
 
        return graph.CallableToParentsProviderAdapter(
1131
 
            self._get_parent_map_no_fallbacks)
1132
 
 
1133
 
    @needs_read_lock
1134
 
    def get_known_graph_ancestry(self, revision_ids):
1135
 
        """Return the known graph for a set of revision ids and their ancestors.
1136
 
        """
1137
 
        raise NotImplementedError(self.get_known_graph_ancestry)
1138
 
 
1139
 
    def get_file_graph(self):
1140
 
        """Return the graph walker for files."""
1141
 
        raise NotImplementedError(self.get_file_graph)
1142
 
 
1143
 
    def get_graph(self, other_repository=None):
1144
 
        """Return the graph walker for this repository format"""
1145
 
        parents_provider = self._make_parents_provider()
1146
 
        if (other_repository is not None and
1147
 
            not self.has_same_location(other_repository)):
1148
 
            parents_provider = graph.StackedParentsProvider(
1149
 
                [parents_provider, other_repository._make_parents_provider()])
1150
 
        return graph.Graph(parents_provider)
1151
 
 
1152
 
    def revision_ids_to_search_result(self, result_set):
1153
 
        """Convert a set of revision ids to a graph SearchResult."""
1154
 
        result_parents = set()
1155
 
        for parents in self.get_graph().get_parent_map(
1156
 
            result_set).itervalues():
1157
 
            result_parents.update(parents)
1158
 
        included_keys = result_set.intersection(result_parents)
1159
 
        start_keys = result_set.difference(included_keys)
1160
 
        exclude_keys = result_parents.difference(result_set)
1161
 
        result = graph.SearchResult(start_keys, exclude_keys,
1162
 
            len(result_set), result_set)
1163
 
        return result
1164
 
 
1165
419
    @needs_write_lock
1166
420
    def set_make_working_trees(self, new_value):
1167
421
        """Set the policy flag for making working trees when creating branches.
1172
426
        :param new_value: True to restore the default, False to disable making
1173
427
                          working trees.
1174
428
        """
1175
 
        raise NotImplementedError(self.set_make_working_trees)
1176
 
 
 
429
        # FIXME: split out into a new class/strategy ?
 
430
        if isinstance(self._format, (RepositoryFormat4,
 
431
                                     RepositoryFormat5,
 
432
                                     RepositoryFormat6)):
 
433
            raise NotImplementedError(self.set_make_working_trees)
 
434
        if new_value:
 
435
            try:
 
436
                self.control_files._transport.delete('no-working-trees')
 
437
            except errors.NoSuchFile:
 
438
                pass
 
439
        else:
 
440
            self.control_files.put_utf8('no-working-trees', '')
 
441
    
1177
442
    def make_working_trees(self):
1178
443
        """Returns the policy for making working trees on new branches."""
1179
 
        raise NotImplementedError(self.make_working_trees)
 
444
        # FIXME: split out into a new class/strategy ?
 
445
        if isinstance(self._format, (RepositoryFormat4,
 
446
                                     RepositoryFormat5,
 
447
                                     RepositoryFormat6)):
 
448
            return True
 
449
        return not self.control_files._transport.has('no-working-trees')
1180
450
 
1181
451
    @needs_write_lock
1182
452
    def sign_revision(self, revision_id, gpg_strategy):
1183
 
        testament = _mod_testament.Testament.from_revision(self, revision_id)
1184
 
        plaintext = testament.as_short_text()
 
453
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1185
454
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1186
455
 
1187
 
    @needs_read_lock
1188
 
    def verify_revision(self, revision_id, gpg_strategy):
1189
 
        """Verify the signature on a revision.
1190
 
        
1191
 
        :param revision_id: the revision to verify
1192
 
        :gpg_strategy: the GPGStrategy object to used
1193
 
        
1194
 
        :return: gpg.SIGNATURE_VALID or a failed SIGNATURE_ value
1195
 
        """
1196
 
        if not self.has_signature_for_revision_id(revision_id):
1197
 
            return gpg.SIGNATURE_NOT_SIGNED, None
1198
 
        signature = self.get_signature_text(revision_id)
1199
 
 
1200
 
        testament = _mod_testament.Testament.from_revision(self, revision_id)
1201
 
        plaintext = testament.as_short_text()
1202
 
 
1203
 
        return gpg_strategy.verify(signature, plaintext)
1204
 
 
1205
 
    def has_signature_for_revision_id(self, revision_id):
1206
 
        """Query for a revision signature for revision_id in the repository."""
1207
 
        raise NotImplementedError(self.has_signature_for_revision_id)
1208
 
 
1209
 
    def get_signature_text(self, revision_id):
1210
 
        """Return the text for a signature."""
1211
 
        raise NotImplementedError(self.get_signature_text)
1212
 
 
1213
 
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1214
 
        """Check consistency of all history of given revision_ids.
1215
 
 
1216
 
        Different repository implementations should override _check().
1217
 
 
1218
 
        :param revision_ids: A non-empty list of revision_ids whose ancestry
1219
 
             will be checked.  Typically the last revision_id of a branch.
1220
 
        :param callback_refs: A dict of check-refs to resolve and callback
1221
 
            the check/_check method on the items listed as wanting the ref.
1222
 
            see bzrlib.check.
1223
 
        :param check_repo: If False do not check the repository contents, just 
1224
 
            calculate the data callback_refs requires and call them back.
1225
 
        """
1226
 
        return self._check(revision_ids=revision_ids, callback_refs=callback_refs,
1227
 
            check_repo=check_repo)
1228
 
 
1229
 
    def _check(self, revision_ids=None, callback_refs=None, check_repo=True):
1230
 
        raise NotImplementedError(self.check)
1231
 
 
1232
 
    def _warn_if_deprecated(self, branch=None):
1233
 
        if not self._format.is_deprecated():
1234
 
            return
1235
 
        global _deprecation_warning_done
1236
 
        if _deprecation_warning_done:
1237
 
            return
1238
 
        try:
1239
 
            if branch is None:
1240
 
                conf = config.GlobalConfig()
1241
 
            else:
1242
 
                conf = branch.get_config()
1243
 
            if conf.suppress_warning('format_deprecation'):
1244
 
                return
1245
 
            warning("Format %s for %s is deprecated -"
1246
 
                    " please use 'bzr upgrade' to get better performance"
1247
 
                    % (self._format, self.bzrdir.transport.base))
1248
 
        finally:
1249
 
            _deprecation_warning_done = True
1250
 
 
1251
 
    def supports_rich_root(self):
1252
 
        return self._format.rich_root_data
1253
 
 
1254
 
    def _check_ascii_revisionid(self, revision_id, method):
1255
 
        """Private helper for ascii-only repositories."""
1256
 
        # weave repositories refuse to store revisionids that are non-ascii.
1257
 
        if revision_id is not None:
1258
 
            # weaves require ascii revision ids.
1259
 
            if isinstance(revision_id, unicode):
1260
 
                try:
1261
 
                    revision_id.encode('ascii')
1262
 
                except UnicodeEncodeError:
1263
 
                    raise errors.NonAsciiRevisionId(method, self)
1264
 
            else:
1265
 
                try:
1266
 
                    revision_id.decode('ascii')
1267
 
                except UnicodeDecodeError:
1268
 
                    raise errors.NonAsciiRevisionId(method, self)
 
456
 
 
457
class AllInOneRepository(Repository):
 
458
    """Legacy support - the repository behaviour for all-in-one branches."""
 
459
 
 
460
    def __init__(self, _format, a_bzrdir, revision_store):
 
461
        # we reuse one control files instance.
 
462
        dir_mode = a_bzrdir._control_files._dir_mode
 
463
        file_mode = a_bzrdir._control_files._file_mode
 
464
 
 
465
        def get_weave(name, prefixed=False):
 
466
            if name:
 
467
                name = safe_unicode(name)
 
468
            else:
 
469
                name = ''
 
470
            relpath = a_bzrdir._control_files._escape(name)
 
471
            weave_transport = a_bzrdir._control_files._transport.clone(relpath)
 
472
            ws = WeaveStore(weave_transport, prefixed=prefixed,
 
473
                            dir_mode=dir_mode,
 
474
                            file_mode=file_mode)
 
475
            if a_bzrdir._control_files._transport.should_cache():
 
476
                ws.enable_cache = True
 
477
            return ws
 
478
 
 
479
        def get_store(name, compressed=True, prefixed=False):
 
480
            # FIXME: This approach of assuming stores are all entirely compressed
 
481
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
482
            # some existing branches where there's a mixture; we probably 
 
483
            # still want the option to look for both.
 
484
            relpath = a_bzrdir._control_files._escape(name)
 
485
            store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
 
486
                              prefixed=prefixed, compressed=compressed,
 
487
                              dir_mode=dir_mode,
 
488
                              file_mode=file_mode)
 
489
            #if self._transport.should_cache():
 
490
            #    cache_path = os.path.join(self.cache_root, name)
 
491
            #    os.mkdir(cache_path)
 
492
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
493
            return store
 
494
 
 
495
        # not broken out yet because the controlweaves|inventory_store
 
496
        # and text_store | weave_store bits are still different.
 
497
        if isinstance(_format, RepositoryFormat4):
 
498
            self.inventory_store = get_store('inventory-store')
 
499
            self.text_store = get_store('text-store')
 
500
        elif isinstance(_format, RepositoryFormat5):
 
501
            self.control_weaves = get_weave('')
 
502
            self.weave_store = get_weave('weaves')
 
503
        elif isinstance(_format, RepositoryFormat6):
 
504
            self.control_weaves = get_weave('')
 
505
            self.weave_store = get_weave('weaves', prefixed=True)
 
506
        else:
 
507
            raise errors.BzrError('unreachable code: unexpected repository'
 
508
                                  ' format.')
 
509
        revision_store.register_suffix('sig')
 
510
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, revision_store)
1269
511
 
1270
512
 
1271
513
class MetaDirRepository(Repository):
1272
 
    """Repositories in the new meta-dir layout.
1273
 
 
1274
 
    :ivar _transport: Transport for access to repository control files,
1275
 
        typically pointing to .bzr/repository.
1276
 
    """
1277
 
 
1278
 
    def __init__(self, _format, a_bzrdir, control_files):
1279
 
        super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
1280
 
        self._transport = control_files._transport
1281
 
 
1282
 
    def is_shared(self):
1283
 
        """Return True if this repository is flagged as a shared repository."""
1284
 
        return self._transport.has('shared-storage')
1285
 
 
1286
 
    @needs_write_lock
1287
 
    def set_make_working_trees(self, new_value):
1288
 
        """Set the policy flag for making working trees when creating branches.
1289
 
 
1290
 
        This only applies to branches that use this repository.
1291
 
 
1292
 
        The default is 'True'.
1293
 
        :param new_value: True to restore the default, False to disable making
1294
 
                          working trees.
1295
 
        """
1296
 
        if new_value:
1297
 
            try:
1298
 
                self._transport.delete('no-working-trees')
1299
 
            except errors.NoSuchFile:
1300
 
                pass
 
514
    """Repositories in the new meta-dir layout."""
 
515
 
 
516
    def __init__(self, _format, a_bzrdir, control_files, revision_store):
 
517
        super(MetaDirRepository, self).__init__(_format,
 
518
                                                a_bzrdir,
 
519
                                                control_files,
 
520
                                                revision_store)
 
521
 
 
522
        dir_mode = self.control_files._dir_mode
 
523
        file_mode = self.control_files._file_mode
 
524
 
 
525
        def get_weave(name, prefixed=False):
 
526
            if name:
 
527
                name = safe_unicode(name)
 
528
            else:
 
529
                name = ''
 
530
            relpath = self.control_files._escape(name)
 
531
            weave_transport = self.control_files._transport.clone(relpath)
 
532
            ws = WeaveStore(weave_transport, prefixed=prefixed,
 
533
                            dir_mode=dir_mode,
 
534
                            file_mode=file_mode)
 
535
            if self.control_files._transport.should_cache():
 
536
                ws.enable_cache = True
 
537
            return ws
 
538
 
 
539
        if isinstance(self._format, RepositoryFormat7):
 
540
            self.control_weaves = get_weave('')
 
541
            self.weave_store = get_weave('weaves', prefixed=True)
 
542
        elif isinstance(self._format, RepositoryFormatKnit1):
 
543
            self.control_weaves = get_weave('')
 
544
            self.weave_store = get_weave('knits', prefixed=True)
1301
545
        else:
1302
 
            self._transport.put_bytes('no-working-trees', '',
1303
 
                mode=self.bzrdir._get_file_mode())
1304
 
 
1305
 
    def make_working_trees(self):
1306
 
        """Returns the policy for making working trees on new branches."""
1307
 
        return not self._transport.has('no-working-trees')
1308
 
 
1309
 
 
1310
 
class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry):
1311
 
    """Repository format registry."""
1312
 
 
1313
 
    def get_default(self):
1314
 
        """Return the current default format."""
1315
 
        return controldir.format_registry.make_bzrdir('default').repository_format
1316
 
 
1317
 
 
1318
 
network_format_registry = registry.FormatRegistry()
1319
 
"""Registry of formats indexed by their network name.
1320
 
 
1321
 
The network name for a repository format is an identifier that can be used when
1322
 
referring to formats with smart server operations. See
1323
 
RepositoryFormat.network_name() for more detail.
1324
 
"""
1325
 
 
1326
 
 
1327
 
format_registry = RepositoryFormatRegistry(network_format_registry)
1328
 
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
1329
 
 
1330
 
This can contain either format instances themselves, or classes/factories that
1331
 
can be called to obtain one.
1332
 
"""
1333
 
 
1334
 
 
1335
 
#####################################################################
1336
 
# Repository Formats
1337
 
 
1338
 
class RepositoryFormat(controldir.ControlComponentFormat):
 
546
            raise errors.BzrError('unreachable code: unexpected repository'
 
547
                                  ' format.')
 
548
 
 
549
 
 
550
class RepositoryFormat(object):
1339
551
    """A repository format.
1340
552
 
1341
 
    Formats provide four things:
 
553
    Formats provide three things:
1342
554
     * An initialization routine to construct repository data on disk.
1343
 
     * a optional format string which is used when the BzrDir supports
1344
 
       versioned children.
 
555
     * a format string which is used when the BzrDir supports versioned
 
556
       children.
1345
557
     * an open routine which returns a Repository instance.
1346
 
     * A network name for referring to the format in smart server RPC
1347
 
       methods.
1348
 
 
1349
 
    There is one and only one Format subclass for each on-disk format. But
1350
 
    there can be one Repository subclass that is used for several different
1351
 
    formats. The _format attribute on a Repository instance can be used to
1352
 
    determine the disk format.
1353
 
 
1354
 
    Formats are placed in a registry by their format string for reference
1355
 
    during opening. These should be subclasses of RepositoryFormat for
1356
 
    consistency.
 
558
 
 
559
    Formats are placed in an dict by their format string for reference 
 
560
    during opening. These should be subclasses of RepositoryFormat
 
561
    for consistency.
1357
562
 
1358
563
    Once a format is deprecated, just deprecate the initialize and open
1359
 
    methods on the format class. Do not deprecate the object, as the
1360
 
    object may be created even when a repository instance hasn't been
1361
 
    created.
 
564
    methods on the format class. Do not deprecate the object, as the 
 
565
    object will be created every system load.
1362
566
 
1363
567
    Common instance attributes:
1364
 
    _matchingbzrdir - the controldir format that the repository format was
 
568
    _matchingbzrdir - the bzrdir format that the repository format was
1365
569
    originally written to work with. This can be used if manually
1366
570
    constructing a bzrdir and repository, or more commonly for test suite
1367
 
    parameterization.
 
571
    parameterisation.
1368
572
    """
1369
573
 
1370
 
    # Set to True or False in derived classes. True indicates that the format
1371
 
    # supports ghosts gracefully.
1372
 
    supports_ghosts = None
1373
 
    # Can this repository be given external locations to lookup additional
1374
 
    # data. Set to True or False in derived classes.
1375
 
    supports_external_lookups = None
1376
 
    # Does this format support CHK bytestring lookups. Set to True or False in
1377
 
    # derived classes.
1378
 
    supports_chks = None
1379
 
    # Should fetch trigger a reconcile after the fetch? Only needed for
1380
 
    # some repository formats that can suffer internal inconsistencies.
1381
 
    _fetch_reconcile = False
1382
 
    # Does this format have < O(tree_size) delta generation. Used to hint what
1383
 
    # code path for commit, amongst other things.
1384
 
    fast_deltas = None
1385
 
    # Does doing a pack operation compress data? Useful for the pack UI command
1386
 
    # (so if there is one pack, the operation can still proceed because it may
1387
 
    # help), and for fetching when data won't have come from the same
1388
 
    # compressor.
1389
 
    pack_compresses = False
1390
 
    # Does the repository storage understand references to trees?
1391
 
    supports_tree_reference = None
1392
 
    # Is the format experimental ?
1393
 
    experimental = False
1394
 
    # Does this repository format escape funky characters, or does it create
1395
 
    # files with similar names as the versioned files in its contents on disk
1396
 
    # ?
1397
 
    supports_funky_characters = None
1398
 
    # Does this repository format support leaving locks?
1399
 
    supports_leaving_lock = None
1400
 
    # Does this format support the full VersionedFiles interface?
1401
 
    supports_full_versioned_files = None
1402
 
    # Does this format support signing revision signatures?
1403
 
    supports_revision_signatures = True
1404
 
    # Can the revision graph have incorrect parents?
1405
 
    revision_graph_can_have_wrong_parents = None
1406
 
    # Does this format support rich root data?
1407
 
    rich_root_data = None
1408
 
    # Does this format support explicitly versioned directories?
1409
 
    supports_versioned_directories = None
1410
 
    # Can other repositories be nested into one of this format?
1411
 
    supports_nesting_repositories = None
1412
 
 
1413
 
    def __repr__(self):
1414
 
        return "%s()" % self.__class__.__name__
1415
 
 
1416
 
    def __eq__(self, other):
1417
 
        # format objects are generally stateless
1418
 
        return isinstance(other, self.__class__)
1419
 
 
1420
 
    def __ne__(self, other):
1421
 
        return not self == other
 
574
    _default_format = None
 
575
    """The default format used for new repositories."""
 
576
 
 
577
    _formats = {}
 
578
    """The known formats."""
1422
579
 
1423
580
    @classmethod
1424
581
    def find_format(klass, a_bzrdir):
1425
 
        """Return the format for the repository object in a_bzrdir.
1426
 
 
1427
 
        This is used by bzr native formats that have a "format" file in
1428
 
        the repository.  Other methods may be used by different types of
1429
 
        control directory.
1430
 
        """
 
582
        """Return the format for the repository object in a_bzrdir."""
1431
583
        try:
1432
584
            transport = a_bzrdir.get_repository_transport(None)
1433
 
            format_string = transport.get_bytes("format")
1434
 
            return format_registry.get(format_string)
 
585
            format_string = transport.get("format").read()
 
586
            return klass._formats[format_string]
1435
587
        except errors.NoSuchFile:
1436
588
            raise errors.NoRepositoryPresent(a_bzrdir)
1437
589
        except KeyError:
1438
 
            raise errors.UnknownFormatError(format=format_string,
1439
 
                                            kind='repository')
1440
 
 
1441
 
    @classmethod
1442
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1443
 
    def register_format(klass, format):
1444
 
        format_registry.register(format)
1445
 
 
1446
 
    @classmethod
1447
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1448
 
    def unregister_format(klass, format):
1449
 
        format_registry.remove(format)
1450
 
 
1451
 
    @classmethod
1452
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
 
590
            raise errors.UnknownFormatError(format_string)
 
591
 
 
592
    @classmethod
1453
593
    def get_default_format(klass):
1454
594
        """Return the current default format."""
1455
 
        return format_registry.get_default()
 
595
        return klass._default_format
1456
596
 
1457
597
    def get_format_string(self):
1458
598
        """Return the ASCII format string that identifies this format.
1459
 
 
1460
 
        Note that in pre format ?? repositories the format string is
 
599
        
 
600
        Note that in pre format ?? repositories the format string is 
1461
601
        not permitted nor written to disk.
1462
602
        """
1463
603
        raise NotImplementedError(self.get_format_string)
1464
604
 
1465
 
    def get_format_description(self):
1466
 
        """Return the short description for this format."""
1467
 
        raise NotImplementedError(self.get_format_description)
1468
 
 
1469
 
    def initialize(self, controldir, shared=False):
1470
 
        """Initialize a repository of this format in controldir.
1471
 
 
1472
 
        :param controldir: The controldir to put the new repository in it.
 
605
    def _get_revision_store(self, repo_transport, control_files):
 
606
        """Return the revision store object for this a_bzrdir."""
 
607
        raise NotImplementedError(self._get_revision_store)
 
608
 
 
609
    def _get_rev_store(self,
 
610
                   transport,
 
611
                   control_files,
 
612
                   name,
 
613
                   compressed=True,
 
614
                   prefixed=False):
 
615
        """Common logic for getting a revision store for a repository.
 
616
        
 
617
        see self._get_revision_store for the method to 
 
618
        get the store for a repository.
 
619
        """
 
620
        if name:
 
621
            name = safe_unicode(name)
 
622
        else:
 
623
            name = ''
 
624
        dir_mode = control_files._dir_mode
 
625
        file_mode = control_files._file_mode
 
626
        revision_store =TextStore(transport.clone(name),
 
627
                                  prefixed=prefixed,
 
628
                                  compressed=compressed,
 
629
                                  dir_mode=dir_mode,
 
630
                                  file_mode=file_mode)
 
631
        revision_store.register_suffix('sig')
 
632
        return revision_store
 
633
 
 
634
    def initialize(self, a_bzrdir, shared=False):
 
635
        """Initialize a repository of this format in a_bzrdir.
 
636
 
 
637
        :param a_bzrdir: The bzrdir to put the new repository in it.
1473
638
        :param shared: The repository should be initialized as a sharable one.
1474
 
        :returns: The new repository object.
1475
639
 
1476
640
        This may raise UninitializableFormat if shared repository are not
1477
 
        compatible the controldir.
 
641
        compatible the a_bzrdir.
1478
642
        """
1479
 
        raise NotImplementedError(self.initialize)
1480
643
 
1481
644
    def is_supported(self):
1482
645
        """Is this format supported?
1483
646
 
1484
647
        Supported formats must be initializable and openable.
1485
 
        Unsupported formats may not support initialization or committing or
 
648
        Unsupported formats may not support initialization or committing or 
1486
649
        some other features depending on the reason for not being supported.
1487
650
        """
1488
651
        return True
1489
652
 
1490
 
    def is_deprecated(self):
1491
 
        """Is this format deprecated?
1492
 
 
1493
 
        Deprecated formats may trigger a user-visible warning recommending
1494
 
        the user to upgrade. They are still fully supported.
1495
 
        """
1496
 
        return False
1497
 
 
1498
 
    def network_name(self):
1499
 
        """A simple byte string uniquely identifying this format for RPC calls.
1500
 
 
1501
 
        MetaDir repository formats use their disk format string to identify the
1502
 
        repository over the wire. All in one formats such as bzr < 0.8, and
1503
 
        foreign formats like svn/git and hg should use some marker which is
1504
 
        unique and immutable.
1505
 
        """
1506
 
        raise NotImplementedError(self.network_name)
1507
 
 
1508
 
    def check_conversion_target(self, target_format):
1509
 
        if self.rich_root_data and not target_format.rich_root_data:
1510
 
            raise errors.BadConversionTarget(
1511
 
                'Does not support rich root data.', target_format,
1512
 
                from_format=self)
1513
 
        if (self.supports_tree_reference and 
1514
 
            not getattr(target_format, 'supports_tree_reference', False)):
1515
 
            raise errors.BadConversionTarget(
1516
 
                'Does not support nested trees', target_format,
1517
 
                from_format=self)
1518
 
 
1519
 
    def open(self, controldir, _found=False):
1520
 
        """Return an instance of this format for a controldir.
1521
 
 
 
653
    def open(self, a_bzrdir, _found=False):
 
654
        """Return an instance of this format for the bzrdir a_bzrdir.
 
655
        
1522
656
        _found is a private parameter, do not use it.
1523
657
        """
1524
658
        raise NotImplementedError(self.open)
1525
659
 
1526
 
    def _run_post_repo_init_hooks(self, repository, controldir, shared):
1527
 
        from bzrlib.controldir import ControlDir, RepoInitHookParams
1528
 
        hooks = ControlDir.hooks['post_repo_init']
1529
 
        if not hooks:
1530
 
            return
1531
 
        params = RepoInitHookParams(repository, self, controldir, shared)
1532
 
        for hook in hooks:
1533
 
            hook(params)
 
660
    @classmethod
 
661
    def register_format(klass, format):
 
662
        klass._formats[format.get_format_string()] = format
 
663
 
 
664
    @classmethod
 
665
    def set_default_format(klass, format):
 
666
        klass._default_format = format
 
667
 
 
668
    @classmethod
 
669
    def unregister_format(klass, format):
 
670
        assert klass._formats[format.get_format_string()] is format
 
671
        del klass._formats[format.get_format_string()]
 
672
 
 
673
 
 
674
class PreSplitOutRepositoryFormat(RepositoryFormat):
 
675
    """Base class for the pre split out repository formats."""
 
676
 
 
677
    def initialize(self, a_bzrdir, shared=False, _internal=False):
 
678
        """Create a weave repository.
 
679
        
 
680
        TODO: when creating split out bzr branch formats, move this to a common
 
681
        base for Format5, Format6. or something like that.
 
682
        """
 
683
        from bzrlib.weavefile import write_weave_v5
 
684
        from bzrlib.weave import Weave
 
685
 
 
686
        if shared:
 
687
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
688
 
 
689
        if not _internal:
 
690
            # always initialized when the bzrdir is.
 
691
            return self.open(a_bzrdir, _found=True)
 
692
        
 
693
        # Create an empty weave
 
694
        sio = StringIO()
 
695
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
696
        empty_weave = sio.getvalue()
 
697
 
 
698
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
699
        dirs = ['revision-store', 'weaves']
 
700
        lock_file = 'branch-lock'
 
701
        files = [('inventory.weave', StringIO(empty_weave)), 
 
702
                 ]
 
703
        
 
704
        # FIXME: RBC 20060125 dont peek under the covers
 
705
        # NB: no need to escape relative paths that are url safe.
 
706
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
 
707
        control_files.lock_write()
 
708
        control_files._transport.mkdir_multi(dirs,
 
709
                mode=control_files._dir_mode)
 
710
        try:
 
711
            for file, content in files:
 
712
                control_files.put(file, content)
 
713
        finally:
 
714
            control_files.unlock()
 
715
        return self.open(a_bzrdir, _found=True)
 
716
 
 
717
    def open(self, a_bzrdir, _found=False):
 
718
        """See RepositoryFormat.open()."""
 
719
        if not _found:
 
720
            # we are being called directly and must probe.
 
721
            raise NotImplementedError
 
722
 
 
723
        repo_transport = a_bzrdir.get_repository_transport(None)
 
724
        control_files = a_bzrdir._control_files
 
725
        revision_store = self._get_revision_store(repo_transport, control_files)
 
726
        return AllInOneRepository(_format=self,
 
727
                                  a_bzrdir=a_bzrdir,
 
728
                                  revision_store=revision_store)
 
729
 
 
730
 
 
731
class RepositoryFormat4(PreSplitOutRepositoryFormat):
 
732
    """Bzr repository format 4.
 
733
 
 
734
    This repository format has:
 
735
     - flat stores
 
736
     - TextStores for texts, inventories,revisions.
 
737
 
 
738
    This format is deprecated: it indexes texts using a text id which is
 
739
    removed in format 5; initializationa and write support for this format
 
740
    has been removed.
 
741
    """
 
742
 
 
743
    def __init__(self):
 
744
        super(RepositoryFormat4, self).__init__()
 
745
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
 
746
 
 
747
    def initialize(self, url, shared=False, _internal=False):
 
748
        """Format 4 branches cannot be created."""
 
749
        raise errors.UninitializableFormat(self)
 
750
 
 
751
    def is_supported(self):
 
752
        """Format 4 is not supported.
 
753
 
 
754
        It is not supported because the model changed from 4 to 5 and the
 
755
        conversion logic is expensive - so doing it on the fly was not 
 
756
        feasible.
 
757
        """
 
758
        return False
 
759
 
 
760
    def _get_revision_store(self, repo_transport, control_files):
 
761
        """See RepositoryFormat._get_revision_store()."""
 
762
        return self._get_rev_store(repo_transport,
 
763
                                   control_files,
 
764
                                   'revision-store')
 
765
 
 
766
 
 
767
class RepositoryFormat5(PreSplitOutRepositoryFormat):
 
768
    """Bzr control format 5.
 
769
 
 
770
    This repository format has:
 
771
     - weaves for file texts and inventory
 
772
     - flat stores
 
773
     - TextStores for revisions and signatures.
 
774
    """
 
775
 
 
776
    def __init__(self):
 
777
        super(RepositoryFormat5, self).__init__()
 
778
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
 
779
 
 
780
    def _get_revision_store(self, repo_transport, control_files):
 
781
        """See RepositoryFormat._get_revision_store()."""
 
782
        """Return the revision store object for this a_bzrdir."""
 
783
        return self._get_rev_store(repo_transport,
 
784
                                   control_files,
 
785
                                   'revision-store',
 
786
                                   compressed=False)
 
787
 
 
788
 
 
789
class RepositoryFormat6(PreSplitOutRepositoryFormat):
 
790
    """Bzr control format 6.
 
791
 
 
792
    This repository format has:
 
793
     - weaves for file texts and inventory
 
794
     - hash subdirectory based stores.
 
795
     - TextStores for revisions and signatures.
 
796
    """
 
797
 
 
798
    def __init__(self):
 
799
        super(RepositoryFormat6, self).__init__()
 
800
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
 
801
 
 
802
    def _get_revision_store(self, repo_transport, control_files):
 
803
        """See RepositoryFormat._get_revision_store()."""
 
804
        return self._get_rev_store(repo_transport,
 
805
                                   control_files,
 
806
                                   'revision-store',
 
807
                                   compressed=False,
 
808
                                   prefixed=True)
1534
809
 
1535
810
 
1536
811
class MetaDirRepositoryFormat(RepositoryFormat):
1537
 
    """Common base class for the new repositories using the metadir layout."""
1538
 
 
1539
 
    rich_root_data = False
1540
 
    supports_tree_reference = False
1541
 
    supports_external_lookups = False
1542
 
    supports_leaving_lock = True
1543
 
    supports_nesting_repositories = True
1544
 
 
1545
 
    @property
1546
 
    def _matchingbzrdir(self):
1547
 
        matching = bzrdir.BzrDirMetaFormat1()
1548
 
        matching.repository_format = self
1549
 
        return matching
 
812
    """Common base class for the new repositories using the metadir layour."""
1550
813
 
1551
814
    def __init__(self):
1552
815
        super(MetaDirRepositoryFormat, self).__init__()
 
816
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
1553
817
 
1554
818
    def _create_control_files(self, a_bzrdir):
1555
819
        """Create the required files and the initial control_files object."""
1556
 
        # FIXME: RBC 20060125 don't peek under the covers
 
820
        # FIXME: RBC 20060125 dont peek under the covers
1557
821
        # NB: no need to escape relative paths that are url safe.
 
822
        lock_file = 'lock'
1558
823
        repository_transport = a_bzrdir.get_repository_transport(self)
1559
 
        control_files = lockable_files.LockableFiles(repository_transport,
1560
 
                                'lock', lockdir.LockDir)
1561
 
        control_files.create_lock()
 
824
        repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
 
825
        control_files = LockableFiles(repository_transport, 'lock')
1562
826
        return control_files
1563
827
 
 
828
    def _get_revision_store(self, repo_transport, control_files):
 
829
        """See RepositoryFormat._get_revision_store()."""
 
830
        return self._get_rev_store(repo_transport,
 
831
                                   control_files,
 
832
                                   'revision-store',
 
833
                                   compressed=False,
 
834
                                   prefixed=True,
 
835
                                   )
 
836
 
 
837
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
838
        """See RepositoryFormat.open().
 
839
        
 
840
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
841
                                    repository at a slightly different url
 
842
                                    than normal. I.e. during 'upgrade'.
 
843
        """
 
844
        if not _found:
 
845
            format = RepositoryFormat.find_format(a_bzrdir)
 
846
            assert format.__class__ ==  self.__class__
 
847
        if _override_transport is not None:
 
848
            repo_transport = _override_transport
 
849
        else:
 
850
            repo_transport = a_bzrdir.get_repository_transport(None)
 
851
        control_files = LockableFiles(repo_transport, 'lock')
 
852
        revision_store = self._get_revision_store(repo_transport, control_files)
 
853
        return MetaDirRepository(_format=self,
 
854
                                 a_bzrdir=a_bzrdir,
 
855
                                 control_files=control_files,
 
856
                                 revision_store=revision_store)
 
857
 
1564
858
    def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
1565
859
        """Upload the initial blank content."""
1566
860
        control_files = self._create_control_files(a_bzrdir)
1567
861
        control_files.lock_write()
1568
 
        transport = control_files._transport
1569
 
        if shared == True:
1570
 
            utf8_files += [('shared-storage', '')]
 
862
        control_files._transport.mkdir_multi(dirs,
 
863
                mode=control_files._dir_mode)
1571
864
        try:
1572
 
            transport.mkdir_multi(dirs, mode=a_bzrdir._get_dir_mode())
1573
 
            for (filename, content_stream) in files:
1574
 
                transport.put_file(filename, content_stream,
1575
 
                    mode=a_bzrdir._get_file_mode())
1576
 
            for (filename, content_bytes) in utf8_files:
1577
 
                transport.put_bytes_non_atomic(filename, content_bytes,
1578
 
                    mode=a_bzrdir._get_file_mode())
 
865
            for file, content in files:
 
866
                control_files.put(file, content)
 
867
            for file, content in utf8_files:
 
868
                control_files.put_utf8(file, content)
 
869
            if shared == True:
 
870
                control_files.put_utf8('shared-storage', '')
1579
871
        finally:
1580
872
            control_files.unlock()
1581
873
 
1582
 
    def network_name(self):
1583
 
        """Metadir formats have matching disk and network format strings."""
1584
 
        return self.get_format_string()
1585
 
 
1586
 
 
1587
 
# formats which have no format string are not discoverable or independently
1588
 
# creatable on disk, so are not registered in format_registry.  They're
1589
 
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
1590
 
# needed, it's constructed directly by the ControlDir.  Non-native formats where
1591
 
# the repository is not separately opened are similar.
1592
 
 
1593
 
format_registry.register_lazy(
1594
 
    'Bazaar-NG Knit Repository Format 1',
1595
 
    'bzrlib.repofmt.knitrepo',
1596
 
    'RepositoryFormatKnit1',
1597
 
    )
1598
 
 
1599
 
format_registry.register_lazy(
1600
 
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1601
 
    'bzrlib.repofmt.knitrepo',
1602
 
    'RepositoryFormatKnit3',
1603
 
    )
1604
 
 
1605
 
format_registry.register_lazy(
1606
 
    'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
1607
 
    'bzrlib.repofmt.knitrepo',
1608
 
    'RepositoryFormatKnit4',
1609
 
    )
1610
 
 
1611
 
# Pack-based formats. There is one format for pre-subtrees, and one for
1612
 
# post-subtrees to allow ease of testing.
1613
 
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
1614
 
format_registry.register_lazy(
1615
 
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
1616
 
    'bzrlib.repofmt.knitpack_repo',
1617
 
    'RepositoryFormatKnitPack1',
1618
 
    )
1619
 
format_registry.register_lazy(
1620
 
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
1621
 
    'bzrlib.repofmt.knitpack_repo',
1622
 
    'RepositoryFormatKnitPack3',
1623
 
    )
1624
 
format_registry.register_lazy(
1625
 
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
1626
 
    'bzrlib.repofmt.knitpack_repo',
1627
 
    'RepositoryFormatKnitPack4',
1628
 
    )
1629
 
format_registry.register_lazy(
1630
 
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
1631
 
    'bzrlib.repofmt.knitpack_repo',
1632
 
    'RepositoryFormatKnitPack5',
1633
 
    )
1634
 
format_registry.register_lazy(
1635
 
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
1636
 
    'bzrlib.repofmt.knitpack_repo',
1637
 
    'RepositoryFormatKnitPack5RichRoot',
1638
 
    )
1639
 
format_registry.register_lazy(
1640
 
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
1641
 
    'bzrlib.repofmt.knitpack_repo',
1642
 
    'RepositoryFormatKnitPack5RichRootBroken',
1643
 
    )
1644
 
format_registry.register_lazy(
1645
 
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
1646
 
    'bzrlib.repofmt.knitpack_repo',
1647
 
    'RepositoryFormatKnitPack6',
1648
 
    )
1649
 
format_registry.register_lazy(
1650
 
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
1651
 
    'bzrlib.repofmt.knitpack_repo',
1652
 
    'RepositoryFormatKnitPack6RichRoot',
1653
 
    )
1654
 
format_registry.register_lazy(
1655
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
1656
 
    'bzrlib.repofmt.groupcompress_repo',
1657
 
    'RepositoryFormat2a',
1658
 
    )
1659
 
 
1660
 
# Development formats.
1661
 
# Check their docstrings to see if/when they are obsolete.
1662
 
format_registry.register_lazy(
1663
 
    ("Bazaar development format 2 with subtree support "
1664
 
        "(needs bzr.dev from before 1.8)\n"),
1665
 
    'bzrlib.repofmt.knitpack_repo',
1666
 
    'RepositoryFormatPackDevelopment2Subtree',
1667
 
    )
1668
 
format_registry.register_lazy(
1669
 
    'Bazaar development format 8\n',
1670
 
    'bzrlib.repofmt.groupcompress_repo',
1671
 
    'RepositoryFormat2aSubtree',
1672
 
    )
1673
 
 
1674
 
 
1675
 
class InterRepository(InterObject):
 
874
 
 
875
class RepositoryFormat7(MetaDirRepositoryFormat):
 
876
    """Bzr repository 7.
 
877
 
 
878
    This repository format has:
 
879
     - weaves for file texts and inventory
 
880
     - hash subdirectory based stores.
 
881
     - TextStores for revisions and signatures.
 
882
     - a format marker of its own
 
883
     - an optional 'shared-storage' flag
 
884
     - an optional 'no-working-trees' flag
 
885
    """
 
886
 
 
887
    def get_format_string(self):
 
888
        """See RepositoryFormat.get_format_string()."""
 
889
        return "Bazaar-NG Repository format 7"
 
890
 
 
891
    def initialize(self, a_bzrdir, shared=False):
 
892
        """Create a weave repository.
 
893
 
 
894
        :param shared: If true the repository will be initialized as a shared
 
895
                       repository.
 
896
        """
 
897
        from bzrlib.weavefile import write_weave_v5
 
898
        from bzrlib.weave import Weave
 
899
 
 
900
        # Create an empty weave
 
901
        sio = StringIO()
 
902
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
903
        empty_weave = sio.getvalue()
 
904
 
 
905
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
906
        dirs = ['revision-store', 'weaves']
 
907
        files = [('inventory.weave', StringIO(empty_weave)), 
 
908
                 ]
 
909
        utf8_files = [('format', self.get_format_string())]
 
910
 
 
911
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
912
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
913
 
 
914
 
 
915
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
 
916
    """Bzr repository knit format 1.
 
917
 
 
918
    This repository format has:
 
919
     - knits for file texts and inventory
 
920
     - hash subdirectory based stores.
 
921
     - knits for revisions and signatures
 
922
     - TextStores for revisions and signatures.
 
923
     - a format marker of its own
 
924
     - an optional 'shared-storage' flag
 
925
     - an optional 'no-working-trees' flag
 
926
    """
 
927
 
 
928
    def get_format_string(self):
 
929
        """See RepositoryFormat.get_format_string()."""
 
930
        return "Bazaar-NG Knit Repository Format 1"
 
931
 
 
932
    def initialize(self, a_bzrdir, shared=False):
 
933
        """Create a knit format 1 repository.
 
934
 
 
935
        :param shared: If true the repository will be initialized as a shared
 
936
                       repository.
 
937
        XXX NOTE that this current uses a Weave for testing and will become 
 
938
            A Knit in due course.
 
939
        """
 
940
        from bzrlib.weavefile import write_weave_v5
 
941
        from bzrlib.weave import Weave
 
942
 
 
943
        # Create an empty weave
 
944
        sio = StringIO()
 
945
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
946
        empty_weave = sio.getvalue()
 
947
 
 
948
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
949
        dirs = ['revision-store', 'knits']
 
950
        files = [('inventory.weave', StringIO(empty_weave)), 
 
951
                 ]
 
952
        utf8_files = [('format', self.get_format_string())]
 
953
        
 
954
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
955
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
956
 
 
957
 
 
958
# formats which have no format string are not discoverable
 
959
# and not independently creatable, so are not registered.
 
960
_default_format = RepositoryFormat7()
 
961
RepositoryFormat.register_format(_default_format)
 
962
RepositoryFormat.register_format(RepositoryFormatKnit1())
 
963
RepositoryFormat.set_default_format(_default_format)
 
964
_legacy_formats = [RepositoryFormat4(),
 
965
                   RepositoryFormat5(),
 
966
                   RepositoryFormat6()]
 
967
 
 
968
 
 
969
class InterRepository(object):
1676
970
    """This class represents operations taking place between two repositories.
1677
971
 
1678
972
    Its instances have methods like copy_content and fetch, and contain
1679
 
    references to the source and target repositories these operations can be
 
973
    references to the source and target repositories these operations can be 
1680
974
    carried out on.
1681
975
 
1682
976
    Often we will provide convenience methods on 'repository' which carry out
1683
977
    operations with another repository - they will always forward to
1684
978
    InterRepository.get(other).method_name(parameters).
1685
979
    """
 
980
    # XXX: FIXME: FUTURE: robertc
 
981
    # testing of these probably requires a factory in optimiser type, and 
 
982
    # then a test adapter to test each type thoroughly.
 
983
    #
1686
984
 
1687
 
    _optimisers = []
 
985
    _optimisers = set()
1688
986
    """The available optimised InterRepository types."""
1689
987
 
 
988
    def __init__(self, source, target):
 
989
        """Construct a default InterRepository instance. Please use 'get'.
 
990
        
 
991
        Only subclasses of InterRepository should call 
 
992
        InterRepository.__init__ - clients should call InterRepository.get
 
993
        instead which will create an optimised InterRepository if possible.
 
994
        """
 
995
        self.source = source
 
996
        self.target = target
 
997
 
1690
998
    @needs_write_lock
1691
 
    def copy_content(self, revision_id=None):
 
999
    def copy_content(self, revision_id=None, basis=None):
1692
1000
        """Make a complete copy of the content in self into destination.
1693
 
 
1694
 
        This is a destructive operation! Do not use it on existing
 
1001
        
 
1002
        This is a destructive operation! Do not use it on existing 
1695
1003
        repositories.
1696
1004
 
1697
1005
        :param revision_id: Only copy the content needed to construct
1698
1006
                            revision_id and its parents.
 
1007
        :param basis: Copy the needed data preferentially from basis.
1699
1008
        """
1700
1009
        try:
1701
1010
            self.target.set_make_working_trees(self.source.make_working_trees())
1702
1011
        except NotImplementedError:
1703
1012
            pass
 
1013
        # grab the basis available data
 
1014
        if basis is not None:
 
1015
            self.target.fetch(basis, revision_id=revision_id)
 
1016
        # but dont both fetching if we have the needed data now.
 
1017
        if (revision_id not in (None, NULL_REVISION) and 
 
1018
            self.target.has_revision(revision_id)):
 
1019
            return
1704
1020
        self.target.fetch(self.source, revision_id=revision_id)
1705
1021
 
 
1022
    def _double_lock(self, lock_source, lock_target):
 
1023
        """Take out too locks, rolling back the first if the second throws."""
 
1024
        lock_source()
 
1025
        try:
 
1026
            lock_target()
 
1027
        except Exception:
 
1028
            # we want to ensure that we don't leave source locked by mistake.
 
1029
            # and any error on target should not confuse source.
 
1030
            self.source.unlock()
 
1031
            raise
 
1032
 
1706
1033
    @needs_write_lock
1707
 
    def fetch(self, revision_id=None, find_ghosts=False,
1708
 
            fetch_spec=None):
 
1034
    def fetch(self, revision_id=None, pb=None):
1709
1035
        """Fetch the content required to construct revision_id.
1710
1036
 
1711
 
        The content is copied from self.source to self.target.
 
1037
        The content is copied from source to target.
1712
1038
 
1713
1039
        :param revision_id: if None all content is copied, if NULL_REVISION no
1714
1040
                            content is copied.
1715
 
        :return: None.
1716
 
        """
1717
 
        raise NotImplementedError(self.fetch)
 
1041
        :param pb: optional progress bar to use for progress reports. If not
 
1042
                   provided a default one will be created.
 
1043
 
 
1044
        Returns the copied revision count and the failed revisions in a tuple:
 
1045
        (copied, failures).
 
1046
        """
 
1047
        from bzrlib.fetch import RepoFetcher
 
1048
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
1049
               self.source, self.source._format, self.target, self.target._format)
 
1050
        f = RepoFetcher(to_repository=self.target,
 
1051
                        from_repository=self.source,
 
1052
                        last_revision=revision_id,
 
1053
                        pb=pb)
 
1054
        return f.count_copied, f.failed_revisions
 
1055
 
 
1056
    @classmethod
 
1057
    def get(klass, repository_source, repository_target):
 
1058
        """Retrieve a InterRepository worker object for these repositories.
 
1059
 
 
1060
        :param repository_source: the repository to be the 'source' member of
 
1061
                                  the InterRepository instance.
 
1062
        :param repository_target: the repository to be the 'target' member of
 
1063
                                the InterRepository instance.
 
1064
        If an optimised InterRepository worker exists it will be used otherwise
 
1065
        a default InterRepository instance will be created.
 
1066
        """
 
1067
        for provider in klass._optimisers:
 
1068
            if provider.is_compatible(repository_source, repository_target):
 
1069
                return provider(repository_source, repository_target)
 
1070
        return InterRepository(repository_source, repository_target)
 
1071
 
 
1072
    def lock_read(self):
 
1073
        """Take out a logical read lock.
 
1074
 
 
1075
        This will lock the source branch and the target branch. The source gets
 
1076
        a read lock and the target a read lock.
 
1077
        """
 
1078
        self._double_lock(self.source.lock_read, self.target.lock_read)
 
1079
 
 
1080
    def lock_write(self):
 
1081
        """Take out a logical write lock.
 
1082
 
 
1083
        This will lock the source branch and the target branch. The source gets
 
1084
        a read lock and the target a write lock.
 
1085
        """
 
1086
        self._double_lock(self.source.lock_read, self.target.lock_write)
1718
1087
 
1719
1088
    @needs_read_lock
1720
 
    def search_missing_revision_ids(self,
1721
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1722
 
            find_ghosts=True, revision_ids=None, if_present_ids=None,
1723
 
            limit=None):
 
1089
    def missing_revision_ids(self, revision_id=None):
1724
1090
        """Return the revision ids that source has that target does not.
 
1091
        
 
1092
        These are returned in topological order.
1725
1093
 
1726
1094
        :param revision_id: only return revision ids included by this
1727
 
            revision_id.
1728
 
        :param revision_ids: return revision ids included by these
1729
 
            revision_ids.  NoSuchRevision will be raised if any of these
1730
 
            revisions are not present.
1731
 
        :param if_present_ids: like revision_ids, but will not cause
1732
 
            NoSuchRevision if any of these are absent, instead they will simply
1733
 
            not be in the result.  This is useful for e.g. finding revisions
1734
 
            to fetch for tags, which may reference absent revisions.
1735
 
        :param find_ghosts: If True find missing revisions in deep history
1736
 
            rather than just finding the surface difference.
1737
 
        :param limit: Maximum number of revisions to return, topologically
1738
 
            ordered
1739
 
        :return: A bzrlib.graph.SearchResult.
 
1095
                            revision_id.
1740
1096
        """
1741
 
        raise NotImplementedError(self.search_missing_revision_ids)
 
1097
        # generic, possibly worst case, slow code path.
 
1098
        target_ids = set(self.target.all_revision_ids())
 
1099
        if revision_id is not None:
 
1100
            source_ids = self.source.get_ancestry(revision_id)
 
1101
            assert source_ids.pop(0) == None
 
1102
        else:
 
1103
            source_ids = self.source.all_revision_ids()
 
1104
        result_set = set(source_ids).difference(target_ids)
 
1105
        # this may look like a no-op: its not. It preserves the ordering
 
1106
        # other_ids had while only returning the members from other_ids
 
1107
        # that we've decided we need.
 
1108
        return [rev_id for rev_id in source_ids if rev_id in result_set]
 
1109
 
 
1110
    @classmethod
 
1111
    def register_optimiser(klass, optimiser):
 
1112
        """Register an InterRepository optimiser."""
 
1113
        klass._optimisers.add(optimiser)
 
1114
 
 
1115
    def unlock(self):
 
1116
        """Release the locks on source and target."""
 
1117
        try:
 
1118
            self.target.unlock()
 
1119
        finally:
 
1120
            self.source.unlock()
 
1121
 
 
1122
    @classmethod
 
1123
    def unregister_optimiser(klass, optimiser):
 
1124
        """Unregister an InterRepository optimiser."""
 
1125
        klass._optimisers.remove(optimiser)
 
1126
 
 
1127
 
 
1128
class InterWeaveRepo(InterRepository):
 
1129
    """Optimised code paths between Weave based repositories."""
 
1130
 
 
1131
    _matching_repo_format = _default_format
 
1132
    """Repository format for testing with."""
1742
1133
 
1743
1134
    @staticmethod
1744
 
    def _same_model(source, target):
1745
 
        """True if source and target have the same data representation.
1746
 
 
1747
 
        Note: this is always called on the base class; overriding it in a
1748
 
        subclass will have no effect.
 
1135
    def is_compatible(source, target):
 
1136
        """Be compatible with known Weave formats.
 
1137
        
 
1138
        We dont test for the stores being of specific types becase that
 
1139
        could lead to confusing results, and there is no need to be 
 
1140
        overly general.
1749
1141
        """
1750
1142
        try:
1751
 
            InterRepository._assert_same_model(source, target)
1752
 
            return True
1753
 
        except errors.IncompatibleRepositories, e:
 
1143
            return (isinstance(source._format, (RepositoryFormat5,
 
1144
                                                RepositoryFormat6,
 
1145
                                                RepositoryFormat7)) and
 
1146
                    isinstance(target._format, (RepositoryFormat5,
 
1147
                                                RepositoryFormat6,
 
1148
                                                RepositoryFormat7)))
 
1149
        except AttributeError:
1754
1150
            return False
 
1151
    
 
1152
    @needs_write_lock
 
1153
    def copy_content(self, revision_id=None, basis=None):
 
1154
        """See InterRepository.copy_content()."""
 
1155
        # weave specific optimised path:
 
1156
        if basis is not None:
 
1157
            # copy the basis in, then fetch remaining data.
 
1158
            basis.copy_content_into(self.target, revision_id)
 
1159
            # the basis copy_content_into could misset this.
 
1160
            try:
 
1161
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1162
            except NotImplementedError:
 
1163
                pass
 
1164
            self.target.fetch(self.source, revision_id=revision_id)
 
1165
        else:
 
1166
            try:
 
1167
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1168
            except NotImplementedError:
 
1169
                pass
 
1170
            # FIXME do not peek!
 
1171
            if self.source.control_files._transport.listable():
 
1172
                pb = bzrlib.ui.ui_factory.progress_bar()
 
1173
                copy_all(self.source.weave_store,
 
1174
                    self.target.weave_store, pb=pb)
 
1175
                pb.update('copying inventory', 0, 1)
 
1176
                self.target.control_weaves.copy_multi(
 
1177
                    self.source.control_weaves, ['inventory'])
 
1178
                copy_all(self.source.revision_store,
 
1179
                    self.target.revision_store, pb=pb)
 
1180
            else:
 
1181
                self.target.fetch(self.source, revision_id=revision_id)
 
1182
 
 
1183
    @needs_write_lock
 
1184
    def fetch(self, revision_id=None, pb=None):
 
1185
        """See InterRepository.fetch()."""
 
1186
        from bzrlib.fetch import RepoFetcher
 
1187
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
1188
               self.source, self.source._format, self.target, self.target._format)
 
1189
        f = RepoFetcher(to_repository=self.target,
 
1190
                        from_repository=self.source,
 
1191
                        last_revision=revision_id,
 
1192
                        pb=pb)
 
1193
        return f.count_copied, f.failed_revisions
 
1194
 
 
1195
    @needs_read_lock
 
1196
    def missing_revision_ids(self, revision_id=None):
 
1197
        """See InterRepository.missing_revision_ids()."""
 
1198
        # we want all revisions to satisfy revision_id in source.
 
1199
        # but we dont want to stat every file here and there.
 
1200
        # we want then, all revisions other needs to satisfy revision_id 
 
1201
        # checked, but not those that we have locally.
 
1202
        # so the first thing is to get a subset of the revisions to 
 
1203
        # satisfy revision_id in source, and then eliminate those that
 
1204
        # we do already have. 
 
1205
        # this is slow on high latency connection to self, but as as this
 
1206
        # disk format scales terribly for push anyway due to rewriting 
 
1207
        # inventory.weave, this is considered acceptable.
 
1208
        # - RBC 20060209
 
1209
        if revision_id is not None:
 
1210
            source_ids = self.source.get_ancestry(revision_id)
 
1211
            assert source_ids.pop(0) == None
 
1212
        else:
 
1213
            source_ids = self.source._all_possible_ids()
 
1214
        source_ids_set = set(source_ids)
 
1215
        # source_ids is the worst possible case we may need to pull.
 
1216
        # now we want to filter source_ids against what we actually
 
1217
        # have in target, but dont try to check for existence where we know
 
1218
        # we do not have a revision as that would be pointless.
 
1219
        target_ids = set(self.target._all_possible_ids())
 
1220
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
1221
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
1222
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
1223
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
 
1224
        if revision_id is not None:
 
1225
            # we used get_ancestry to determine source_ids then we are assured all
 
1226
            # revisions referenced are present as they are installed in topological order.
 
1227
            # and the tip revision was validated by get_ancestry.
 
1228
            return required_topo_revisions
 
1229
        else:
 
1230
            # if we just grabbed the possibly available ids, then 
 
1231
            # we only have an estimate of whats available and need to validate
 
1232
            # that against the revision records.
 
1233
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
 
1234
 
 
1235
 
 
1236
InterRepository.register_optimiser(InterWeaveRepo)
 
1237
 
 
1238
 
 
1239
class RepositoryTestProviderAdapter(object):
 
1240
    """A tool to generate a suite testing multiple repository formats at once.
 
1241
 
 
1242
    This is done by copying the test once for each transport and injecting
 
1243
    the transport_server, transport_readonly_server, and bzrdir_format and
 
1244
    repository_format classes into each copy. Each copy is also given a new id()
 
1245
    to make it easy to identify.
 
1246
    """
 
1247
 
 
1248
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1249
        self._transport_server = transport_server
 
1250
        self._transport_readonly_server = transport_readonly_server
 
1251
        self._formats = formats
 
1252
    
 
1253
    def adapt(self, test):
 
1254
        result = TestSuite()
 
1255
        for repository_format, bzrdir_format in self._formats:
 
1256
            new_test = deepcopy(test)
 
1257
            new_test.transport_server = self._transport_server
 
1258
            new_test.transport_readonly_server = self._transport_readonly_server
 
1259
            new_test.bzrdir_format = bzrdir_format
 
1260
            new_test.repository_format = repository_format
 
1261
            def make_new_test_id():
 
1262
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
 
1263
                return lambda: new_id
 
1264
            new_test.id = make_new_test_id()
 
1265
            result.addTest(new_test)
 
1266
        return result
 
1267
 
 
1268
 
 
1269
class InterRepositoryTestProviderAdapter(object):
 
1270
    """A tool to generate a suite testing multiple inter repository formats.
 
1271
 
 
1272
    This is done by copying the test once for each interrepo provider and injecting
 
1273
    the transport_server, transport_readonly_server, repository_format and 
 
1274
    repository_to_format classes into each copy.
 
1275
    Each copy is also given a new id() to make it easy to identify.
 
1276
    """
 
1277
 
 
1278
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1279
        self._transport_server = transport_server
 
1280
        self._transport_readonly_server = transport_readonly_server
 
1281
        self._formats = formats
 
1282
    
 
1283
    def adapt(self, test):
 
1284
        result = TestSuite()
 
1285
        for interrepo_class, repository_format, repository_format_to in self._formats:
 
1286
            new_test = deepcopy(test)
 
1287
            new_test.transport_server = self._transport_server
 
1288
            new_test.transport_readonly_server = self._transport_readonly_server
 
1289
            new_test.interrepo_class = interrepo_class
 
1290
            new_test.repository_format = repository_format
 
1291
            new_test.repository_format_to = repository_format_to
 
1292
            def make_new_test_id():
 
1293
                new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
 
1294
                return lambda: new_id
 
1295
            new_test.id = make_new_test_id()
 
1296
            result.addTest(new_test)
 
1297
        return result
1755
1298
 
1756
1299
    @staticmethod
1757
 
    def _assert_same_model(source, target):
1758
 
        """Raise an exception if two repositories do not use the same model.
1759
 
        """
1760
 
        if source.supports_rich_root() != target.supports_rich_root():
1761
 
            raise errors.IncompatibleRepositories(source, target,
1762
 
                "different rich-root support")
1763
 
        if source._serializer != target._serializer:
1764
 
            raise errors.IncompatibleRepositories(source, target,
1765
 
                "different serializers")
 
1300
    def default_test_list():
 
1301
        """Generate the default list of interrepo permutations to test."""
 
1302
        result = []
 
1303
        # test the default InterRepository between format 6 and the current 
 
1304
        # default format.
 
1305
        # XXX: robertc 20060220 reinstate this when there are two supported
 
1306
        # formats which do not have an optimal code path between them.
 
1307
        result.append((InterRepository,
 
1308
                       RepositoryFormat6(),
 
1309
                       RepositoryFormatKnit1()))
 
1310
        for optimiser in InterRepository._optimisers:
 
1311
            result.append((optimiser,
 
1312
                           optimiser._matching_repo_format,
 
1313
                           optimiser._matching_repo_format
 
1314
                           ))
 
1315
        # if there are specific combinations we want to use, we can add them 
 
1316
        # here.
 
1317
        return result
1766
1318
 
1767
1319
 
1768
1320
class CopyConverter(object):
1769
1321
    """A repository conversion tool which just performs a copy of the content.
1770
 
 
 
1322
    
1771
1323
    This is slow but quite reliable.
1772
1324
    """
1773
1325
 
1777
1329
        :param target_format: The format the resulting repository should be.
1778
1330
        """
1779
1331
        self.target_format = target_format
1780
 
 
 
1332
        
1781
1333
    def convert(self, repo, pb):
1782
1334
        """Perform the conversion of to_convert, giving feedback via pb.
1783
1335
 
1784
1336
        :param to_convert: The disk object to convert.
1785
1337
        :param pb: a progress bar to use for progress information.
1786
1338
        """
1787
 
        pb = ui.ui_factory.nested_progress_bar()
 
1339
        self.pb = pb
1788
1340
        self.count = 0
1789
 
        self.total = 4
 
1341
        self.total = 3
1790
1342
        # this is only useful with metadir layouts - separated repo content.
1791
1343
        # trigger an assertion if not such
1792
1344
        repo._format.get_format_string()
1793
1345
        self.repo_dir = repo.bzrdir
1794
 
        pb.update(gettext('Moving repository to repository.backup'))
 
1346
        self.step('Moving repository to repository.backup')
1795
1347
        self.repo_dir.transport.move('repository', 'repository.backup')
1796
1348
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
1797
 
        repo._format.check_conversion_target(self.target_format)
1798
1349
        self.source_repo = repo._format.open(self.repo_dir,
1799
1350
            _found=True,
1800
1351
            _override_transport=backup_transport)
1801
 
        pb.update(gettext('Creating new repository'))
 
1352
        self.step('Creating new repository')
1802
1353
        converted = self.target_format.initialize(self.repo_dir,
1803
1354
                                                  self.source_repo.is_shared())
1804
1355
        converted.lock_write()
1805
1356
        try:
1806
 
            pb.update(gettext('Copying content'))
 
1357
            self.step('Copying content into repository.')
1807
1358
            self.source_repo.copy_content_into(converted)
1808
1359
        finally:
1809
1360
            converted.unlock()
1810
 
        pb.update(gettext('Deleting old repository content'))
 
1361
        self.step('Deleting old repository content.')
1811
1362
        self.repo_dir.transport.delete_tree('repository.backup')
1812
 
        ui.ui_factory.note(gettext('repository converted'))
1813
 
        pb.finished()
1814
 
 
1815
 
 
1816
 
def _strip_NULL_ghosts(revision_graph):
1817
 
    """Also don't use this. more compatibility code for unmigrated clients."""
1818
 
    # Filter ghosts, and null:
1819
 
    if _mod_revision.NULL_REVISION in revision_graph:
1820
 
        del revision_graph[_mod_revision.NULL_REVISION]
1821
 
    for key, parents in revision_graph.items():
1822
 
        revision_graph[key] = tuple(parent for parent in parents if parent
1823
 
            in revision_graph)
1824
 
    return revision_graph
1825
 
 
1826
 
 
1827
 
def _iter_for_revno(repo, partial_history_cache, stop_index=None,
1828
 
                    stop_revision=None):
1829
 
    """Extend the partial history to include a given index
1830
 
 
1831
 
    If a stop_index is supplied, stop when that index has been reached.
1832
 
    If a stop_revision is supplied, stop when that revision is
1833
 
    encountered.  Otherwise, stop when the beginning of history is
1834
 
    reached.
1835
 
 
1836
 
    :param stop_index: The index which should be present.  When it is
1837
 
        present, history extension will stop.
1838
 
    :param stop_revision: The revision id which should be present.  When
1839
 
        it is encountered, history extension will stop.
1840
 
    """
1841
 
    start_revision = partial_history_cache[-1]
1842
 
    graph = repo.get_graph()
1843
 
    iterator = graph.iter_lefthand_ancestry(start_revision,
1844
 
        (_mod_revision.NULL_REVISION,))
1845
 
    try:
1846
 
        # skip the last revision in the list
1847
 
        iterator.next()
1848
 
        while True:
1849
 
            if (stop_index is not None and
1850
 
                len(partial_history_cache) > stop_index):
1851
 
                break
1852
 
            if partial_history_cache[-1] == stop_revision:
1853
 
                break
1854
 
            revision_id = iterator.next()
1855
 
            partial_history_cache.append(revision_id)
1856
 
    except StopIteration:
1857
 
        # No more history
1858
 
        return
1859
 
 
1860
 
 
1861
 
class _LazyListJoin(object):
1862
 
    """An iterable yielding the contents of many lists as one list.
1863
 
 
1864
 
    Each iterator made from this will reflect the current contents of the lists
1865
 
    at the time the iterator is made.
1866
 
    
1867
 
    This is used by Repository's _make_parents_provider implementation so that
1868
 
    it is safe to do::
1869
 
 
1870
 
      pp = repo._make_parents_provider()      # uses a list of fallback repos
1871
 
      pp.add_fallback_repository(other_repo)  # appends to that list
1872
 
      result = pp.get_parent_map(...)
1873
 
      # The result will include revs from other_repo
1874
 
    """
1875
 
 
1876
 
    def __init__(self, *list_parts):
1877
 
        self.list_parts = list_parts
1878
 
 
1879
 
    def __iter__(self):
1880
 
        full_list = []
1881
 
        for list_part in self.list_parts:
1882
 
            full_list.extend(list_part)
1883
 
        return iter(full_list)
1884
 
 
1885
 
    def __repr__(self):
1886
 
        return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
1887
 
                              self.list_parts)
 
1363
        self.pb.note('repository converted')
 
1364
 
 
1365
    def step(self, message):
 
1366
        """Update the pb by a step."""
 
1367
        self.count +=1
 
1368
        self.pb.update(message, self.count, self.total)