1
# Copyright (C) 2005, 2006 Canonical Ltd
 
 
3
# This program is free software; you can redistribute it and/or modify
 
 
4
# it under the terms of the GNU General Public License as published by
 
 
5
# the Free Software Foundation; either version 2 of the License, or
 
 
6
# (at your option) any later version.
 
 
8
# This program is distributed in the hope that it will be useful,
 
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
11
# GNU General Public License for more details.
 
 
13
# You should have received a copy of the GNU General Public License
 
 
14
# along with this program; if not, write to the Free Software
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
18
from copy import deepcopy
 
 
19
from cStringIO import StringIO
 
 
20
from unittest import TestSuite
 
 
21
from warnings import warn
 
 
36
from bzrlib.config import TreeConfig
 
 
37
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
 
38
import bzrlib.errors as errors
 
 
39
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
 
 
40
                           HistoryMissing, InvalidRevisionId, 
 
 
41
                           InvalidRevisionNumber, LockError, NoSuchFile, 
 
 
42
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
 
 
43
                           NotBranchError, UninitializableFormat, 
 
 
44
                           UnlistableStore, UnlistableBranch, 
 
 
46
from bzrlib.lockable_files import LockableFiles, TransportLock
 
 
47
from bzrlib.symbol_versioning import (deprecated_function,
 
 
51
                                      zero_eight, zero_nine,
 
 
53
from bzrlib.trace import mutter, note
 
 
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
 
57
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
 
58
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
 
 
61
# TODO: Maybe include checks for common corruption of newlines, etc?
 
 
63
# TODO: Some operations like log might retrieve the same revisions
 
 
64
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
 
65
# cache in memory to make this faster.  In general anything can be
 
 
66
# cached in memory between lock and unlock operations. .. nb thats
 
 
67
# what the transaction identity map provides
 
 
70
######################################################################
 
 
74
    """Branch holding a history of revisions.
 
 
77
        Base directory/url of the branch.
 
 
79
    # this is really an instance variable - FIXME move it there
 
 
83
    def __init__(self, *ignored, **ignored_too):
 
 
84
        raise NotImplementedError('The Branch class is abstract')
 
 
87
        """Break a lock if one is present from another instance.
 
 
89
        Uses the ui factory to ask for confirmation if the lock may be from
 
 
92
        This will probe the repository for its lock as well.
 
 
94
        self.control_files.break_lock()
 
 
95
        self.repository.break_lock()
 
 
96
        master = self.get_master_branch()
 
 
97
        if master is not None:
 
 
101
    @deprecated_method(zero_eight)
 
 
102
    def open_downlevel(base):
 
 
103
        """Open a branch which may be of an old format."""
 
 
104
        return Branch.open(base, _unsupported=True)
 
 
107
    def open(base, _unsupported=False):
 
 
108
        """Open the branch rooted at base.
 
 
110
        For instance, if the branch is at URL/.bzr/branch,
 
 
111
        Branch.open(URL) -> a Branch instance.
 
 
113
        control = bzrdir.BzrDir.open(base, _unsupported)
 
 
114
        return control.open_branch(_unsupported)
 
 
117
    def open_containing(url):
 
 
118
        """Open an existing branch which contains url.
 
 
120
        This probes for a branch at url, and searches upwards from there.
 
 
122
        Basically we keep looking up until we find the control directory or
 
 
123
        run into the root.  If there isn't one, raises NotBranchError.
 
 
124
        If there is one and it is either an unrecognised format or an unsupported 
 
 
125
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
 
126
        If there is one, it is returned, along with the unused portion of url.
 
 
128
        control, relpath = bzrdir.BzrDir.open_containing(url)
 
 
129
        return control.open_branch(), relpath
 
 
132
    @deprecated_function(zero_eight)
 
 
133
    def initialize(base):
 
 
134
        """Create a new working tree and branch, rooted at 'base' (url)
 
 
136
        NOTE: This will soon be deprecated in favour of creation
 
 
139
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
 
 
141
    def setup_caching(self, cache_root):
 
 
142
        """Subclasses that care about caching should override this, and set
 
 
143
        up cached stores located under cache_root.
 
 
145
        # seems to be unused, 2006-01-13 mbp
 
 
146
        warn('%s is deprecated' % self.setup_caching)
 
 
147
        self.cache_root = cache_root
 
 
149
    def get_config(self):
 
 
150
        return bzrlib.config.BranchConfig(self)
 
 
153
        return self.get_config().get_nickname()
 
 
155
    def _set_nick(self, nick):
 
 
156
        self.get_config().set_user_option('nickname', nick)
 
 
158
    nick = property(_get_nick, _set_nick)
 
 
161
        raise NotImplementedError(self.is_locked)
 
 
163
    def lock_write(self):
 
 
164
        raise NotImplementedError(self.lock_write)
 
 
167
        raise NotImplementedError(self.lock_read)
 
 
170
        raise NotImplementedError(self.unlock)
 
 
172
    def peek_lock_mode(self):
 
 
173
        """Return lock mode for the Branch: 'r', 'w' or None"""
 
 
174
        raise NotImplementedError(self.peek_lock_mode)
 
 
176
    def get_physical_lock_status(self):
 
 
177
        raise NotImplementedError(self.get_physical_lock_status)
 
 
179
    def abspath(self, name):
 
 
180
        """Return absolute filename for something in the branch
 
 
182
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
 
183
        method and not a tree method.
 
 
185
        raise NotImplementedError(self.abspath)
 
 
187
    def bind(self, other):
 
 
188
        """Bind the local branch the other branch.
 
 
190
        :param other: The branch to bind to
 
 
193
        raise errors.UpgradeRequired(self.base)
 
 
196
    def fetch(self, from_branch, last_revision=None, pb=None):
 
 
197
        """Copy revisions from from_branch into this branch.
 
 
199
        :param from_branch: Where to copy from.
 
 
200
        :param last_revision: What revision to stop at (None for at the end
 
 
202
        :param pb: An optional progress bar to use.
 
 
204
        Returns the copied revision count and the failed revisions in a tuple:
 
 
207
        if self.base == from_branch.base:
 
 
210
            nested_pb = ui.ui_factory.nested_progress_bar()
 
 
215
        from_branch.lock_read()
 
 
217
            if last_revision is None:
 
 
218
                pb.update('get source history')
 
 
219
                from_history = from_branch.revision_history()
 
 
221
                    last_revision = from_history[-1]
 
 
223
                    # no history in the source branch
 
 
224
                    last_revision = revision.NULL_REVISION
 
 
225
            return self.repository.fetch(from_branch.repository,
 
 
226
                                         revision_id=last_revision,
 
 
229
            if nested_pb is not None:
 
 
233
    def get_bound_location(self):
 
 
234
        """Return the URL of the branch we are bound to.
 
 
236
        Older format branches cannot bind, please be sure to use a metadir
 
 
241
    def get_commit_builder(self, parents, config=None, timestamp=None, 
 
 
242
                           timezone=None, committer=None, revprops=None, 
 
 
244
        """Obtain a CommitBuilder for this branch.
 
 
246
        :param parents: Revision ids of the parents of the new revision.
 
 
247
        :param config: Optional configuration to use.
 
 
248
        :param timestamp: Optional timestamp recorded for commit.
 
 
249
        :param timezone: Optional timezone for timestamp.
 
 
250
        :param committer: Optional committer to set for commit.
 
 
251
        :param revprops: Optional dictionary of revision properties.
 
 
252
        :param revision_id: Optional revision id.
 
 
256
            config = self.get_config()
 
 
258
        return self.repository.get_commit_builder(self, parents, config, 
 
 
259
            timestamp, timezone, committer, revprops, revision_id)
 
 
261
    def get_master_branch(self):
 
 
262
        """Return the branch we are bound to.
 
 
264
        :return: Either a Branch, or None
 
 
268
    def get_revision_delta(self, revno):
 
 
269
        """Return the delta for one revision.
 
 
271
        The delta is relative to its mainline predecessor, or the
 
 
272
        empty tree for revision 1.
 
 
274
        assert isinstance(revno, int)
 
 
275
        rh = self.revision_history()
 
 
276
        if not (1 <= revno <= len(rh)):
 
 
277
            raise InvalidRevisionNumber(revno)
 
 
278
        return self.repository.get_revision_delta(rh[revno-1])
 
 
280
    def get_root_id(self):
 
 
281
        """Return the id of this branches root"""
 
 
282
        raise NotImplementedError(self.get_root_id)
 
 
284
    def print_file(self, file, revision_id):
 
 
285
        """Print `file` to stdout."""
 
 
286
        raise NotImplementedError(self.print_file)
 
 
288
    def append_revision(self, *revision_ids):
 
 
289
        raise NotImplementedError(self.append_revision)
 
 
291
    def set_revision_history(self, rev_history):
 
 
292
        raise NotImplementedError(self.set_revision_history)
 
 
294
    def revision_history(self):
 
 
295
        """Return sequence of revision hashes on to this branch."""
 
 
296
        raise NotImplementedError('revision_history is abstract')
 
 
299
        """Return current revision number for this branch.
 
 
301
        That is equivalent to the number of revisions committed to
 
 
304
        return len(self.revision_history())
 
 
307
        """Older format branches cannot bind or unbind."""
 
 
308
        raise errors.UpgradeRequired(self.base)
 
 
310
    def last_revision(self):
 
 
311
        """Return last revision id, or None"""
 
 
312
        ph = self.revision_history()
 
 
318
    def missing_revisions(self, other, stop_revision=None):
 
 
319
        """Return a list of new revisions that would perfectly fit.
 
 
321
        If self and other have not diverged, return a list of the revisions
 
 
322
        present in other, but missing from self.
 
 
324
        self_history = self.revision_history()
 
 
325
        self_len = len(self_history)
 
 
326
        other_history = other.revision_history()
 
 
327
        other_len = len(other_history)
 
 
328
        common_index = min(self_len, other_len) -1
 
 
329
        if common_index >= 0 and \
 
 
330
            self_history[common_index] != other_history[common_index]:
 
 
331
            raise DivergedBranches(self, other)
 
 
333
        if stop_revision is None:
 
 
334
            stop_revision = other_len
 
 
336
            assert isinstance(stop_revision, int)
 
 
337
            if stop_revision > other_len:
 
 
338
                raise errors.NoSuchRevision(self, stop_revision)
 
 
339
        return other_history[self_len:stop_revision]
 
 
341
    def update_revisions(self, other, stop_revision=None):
 
 
342
        """Pull in new perfect-fit revisions.
 
 
344
        :param other: Another Branch to pull from
 
 
345
        :param stop_revision: Updated until the given revision
 
 
348
        raise NotImplementedError(self.update_revisions)
 
 
350
    def revision_id_to_revno(self, revision_id):
 
 
351
        """Given a revision id, return its revno"""
 
 
352
        if revision_id is None:
 
 
354
        history = self.revision_history()
 
 
356
            return history.index(revision_id) + 1
 
 
358
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
 
360
    def get_rev_id(self, revno, history=None):
 
 
361
        """Find the revision id of the specified revno."""
 
 
365
            history = self.revision_history()
 
 
366
        elif revno <= 0 or revno > len(history):
 
 
367
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
 
368
        return history[revno - 1]
 
 
370
    def pull(self, source, overwrite=False, stop_revision=None):
 
 
371
        raise NotImplementedError(self.pull)
 
 
373
    def basis_tree(self):
 
 
374
        """Return `Tree` object for last revision."""
 
 
375
        return self.repository.revision_tree(self.last_revision())
 
 
377
    def rename_one(self, from_rel, to_rel):
 
 
380
        This can change the directory or the filename or both.
 
 
382
        raise NotImplementedError(self.rename_one)
 
 
384
    def move(self, from_paths, to_name):
 
 
387
        to_name must exist as a versioned directory.
 
 
389
        If to_name exists and is a directory, the files are moved into
 
 
390
        it, keeping their old names.  If it is a directory, 
 
 
392
        Note that to_name is only the last component of the new name;
 
 
393
        this doesn't change the directory.
 
 
395
        This returns a list of (from_path, to_path) pairs for each
 
 
398
        raise NotImplementedError(self.move)
 
 
400
    def get_parent(self):
 
 
401
        """Return the parent location of the branch.
 
 
403
        This is the default location for push/pull/missing.  The usual
 
 
404
        pattern is that the user can override it by specifying a
 
 
407
        raise NotImplementedError(self.get_parent)
 
 
409
    def get_submit_branch(self):
 
 
410
        """Return the submit location of the branch.
 
 
412
        This is the default location for bundle.  The usual
 
 
413
        pattern is that the user can override it by specifying a
 
 
416
        return self.get_config().get_user_option('submit_branch')
 
 
418
    def set_submit_branch(self, location):
 
 
419
        """Return the submit location of the branch.
 
 
421
        This is the default location for bundle.  The usual
 
 
422
        pattern is that the user can override it by specifying a
 
 
425
        self.get_config().set_user_option('submit_branch', location)
 
 
427
    def get_push_location(self):
 
 
428
        """Return the None or the location to push this branch to."""
 
 
429
        raise NotImplementedError(self.get_push_location)
 
 
431
    def set_push_location(self, location):
 
 
432
        """Set a new push location for this branch."""
 
 
433
        raise NotImplementedError(self.set_push_location)
 
 
435
    def set_parent(self, url):
 
 
436
        raise NotImplementedError(self.set_parent)
 
 
440
        """Synchronise this branch with the master branch if any. 
 
 
442
        :return: None or the last_revision pivoted out during the update.
 
 
446
    def check_revno(self, revno):
 
 
448
        Check whether a revno corresponds to any revision.
 
 
449
        Zero (the NULL revision) is considered valid.
 
 
452
            self.check_real_revno(revno)
 
 
454
    def check_real_revno(self, revno):
 
 
456
        Check whether a revno corresponds to a real revision.
 
 
457
        Zero (the NULL revision) is considered invalid
 
 
459
        if revno < 1 or revno > self.revno():
 
 
460
            raise InvalidRevisionNumber(revno)
 
 
463
    def clone(self, *args, **kwargs):
 
 
464
        """Clone this branch into to_bzrdir preserving all semantic values.
 
 
466
        revision_id: if not None, the revision history in the new branch will
 
 
467
                     be truncated to end with revision_id.
 
 
469
        # for API compatibility, until 0.8 releases we provide the old api:
 
 
470
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
 
 
471
        # after 0.8 releases, the *args and **kwargs should be changed:
 
 
472
        # def clone(self, to_bzrdir, revision_id=None):
 
 
473
        if (kwargs.get('to_location', None) or
 
 
474
            kwargs.get('revision', None) or
 
 
475
            kwargs.get('basis_branch', None) or
 
 
476
            (len(args) and isinstance(args[0], basestring))):
 
 
477
            # backwards compatibility api:
 
 
478
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
 
 
479
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
 
 
482
                basis_branch = args[2]
 
 
484
                basis_branch = kwargs.get('basis_branch', None)
 
 
486
                basis = basis_branch.bzrdir
 
 
491
                revision_id = args[1]
 
 
493
                revision_id = kwargs.get('revision', None)
 
 
498
                # no default to raise if not provided.
 
 
499
                url = kwargs.get('to_location')
 
 
500
            return self.bzrdir.clone(url,
 
 
501
                                     revision_id=revision_id,
 
 
502
                                     basis=basis).open_branch()
 
 
504
        # generate args by hand 
 
 
506
            revision_id = args[1]
 
 
508
            revision_id = kwargs.get('revision_id', None)
 
 
512
            # no default to raise if not provided.
 
 
513
            to_bzrdir = kwargs.get('to_bzrdir')
 
 
514
        result = self._format.initialize(to_bzrdir)
 
 
515
        self.copy_content_into(result, revision_id=revision_id)
 
 
519
    def sprout(self, to_bzrdir, revision_id=None):
 
 
520
        """Create a new line of development from the branch, into to_bzrdir.
 
 
522
        revision_id: if not None, the revision history in the new branch will
 
 
523
                     be truncated to end with revision_id.
 
 
525
        result = self._format.initialize(to_bzrdir)
 
 
526
        self.copy_content_into(result, revision_id=revision_id)
 
 
527
        result.set_parent(self.bzrdir.root_transport.base)
 
 
531
    def copy_content_into(self, destination, revision_id=None):
 
 
532
        """Copy the content of self into destination.
 
 
534
        revision_id: if not None, the revision history in the new branch will
 
 
535
                     be truncated to end with revision_id.
 
 
537
        new_history = self.revision_history()
 
 
538
        if revision_id is not None:
 
 
540
                new_history = new_history[:new_history.index(revision_id) + 1]
 
 
542
                rev = self.repository.get_revision(revision_id)
 
 
543
                new_history = rev.get_history(self.repository)[1:]
 
 
544
        destination.set_revision_history(new_history)
 
 
546
            parent = self.get_parent()
 
 
547
        except errors.InaccessibleParent, e:
 
 
548
            mutter('parent was not accessible to copy: %s', e)
 
 
551
                destination.set_parent(parent)
 
 
555
        """Check consistency of the branch.
 
 
557
        In particular this checks that revisions given in the revision-history
 
 
558
        do actually match up in the revision graph, and that they're all 
 
 
559
        present in the repository.
 
 
561
        Callers will typically also want to check the repository.
 
 
563
        :return: A BranchCheckResult.
 
 
565
        mainline_parent_id = None
 
 
566
        for revision_id in self.revision_history():
 
 
568
                revision = self.repository.get_revision(revision_id)
 
 
569
            except errors.NoSuchRevision, e:
 
 
570
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
 
 
572
            # In general the first entry on the revision history has no parents.
 
 
573
            # But it's not illegal for it to have parents listed; this can happen
 
 
574
            # in imports from Arch when the parents weren't reachable.
 
 
575
            if mainline_parent_id is not None:
 
 
576
                if mainline_parent_id not in revision.parent_ids:
 
 
577
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
 
 
579
                                        % (mainline_parent_id, revision_id))
 
 
580
            mainline_parent_id = revision_id
 
 
581
        return BranchCheckResult(self)
 
 
583
    def create_checkout(self, to_location, revision_id=None, 
 
 
585
        """Create a checkout of a branch.
 
 
587
        :param to_location: The url to produce the checkout at
 
 
588
        :param revision_id: The revision to check out
 
 
589
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
 
590
        produce a bound branch (heavyweight checkout)
 
 
591
        :return: The tree of the created checkout
 
 
594
            t = transport.get_transport(to_location)
 
 
597
            except errors.FileExists:
 
 
599
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
 
600
            BranchReferenceFormat().initialize(checkout, self)
 
 
602
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
 
603
                to_location, force_new_tree=False)
 
 
604
            checkout = checkout_branch.bzrdir
 
 
605
            checkout_branch.bind(self)
 
 
606
            if revision_id is not None:
 
 
607
                rh = checkout_branch.revision_history()
 
 
608
                new_rh = rh[:rh.index(revision_id) + 1]
 
 
609
                checkout_branch.set_revision_history(new_rh)
 
 
610
        return checkout.create_workingtree(revision_id)
 
 
613
class BranchFormat(object):
 
 
614
    """An encapsulation of the initialization and open routines for a format.
 
 
616
    Formats provide three things:
 
 
617
     * An initialization routine,
 
 
621
    Formats are placed in an dict by their format string for reference 
 
 
622
    during branch opening. Its not required that these be instances, they
 
 
623
    can be classes themselves with class methods - it simply depends on 
 
 
624
    whether state is needed for a given format or not.
 
 
626
    Once a format is deprecated, just deprecate the initialize and open
 
 
627
    methods on the format class. Do not deprecate the object, as the 
 
 
628
    object will be created every time regardless.
 
 
631
    _default_format = None
 
 
632
    """The default format used for new branches."""
 
 
635
    """The known formats."""
 
 
638
    def find_format(klass, a_bzrdir):
 
 
639
        """Return the format for the branch object in a_bzrdir."""
 
 
641
            transport = a_bzrdir.get_branch_transport(None)
 
 
642
            format_string = transport.get("format").read()
 
 
643
            return klass._formats[format_string]
 
 
645
            raise NotBranchError(path=transport.base)
 
 
647
            raise errors.UnknownFormatError(format=format_string)
 
 
650
    def get_default_format(klass):
 
 
651
        """Return the current default format."""
 
 
652
        return klass._default_format
 
 
654
    def get_format_string(self):
 
 
655
        """Return the ASCII format string that identifies this format."""
 
 
656
        raise NotImplementedError(self.get_format_string)
 
 
658
    def get_format_description(self):
 
 
659
        """Return the short format description for this format."""
 
 
660
        raise NotImplementedError(self.get_format_string)
 
 
662
    def initialize(self, a_bzrdir):
 
 
663
        """Create a branch of this format in a_bzrdir."""
 
 
664
        raise NotImplementedError(self.initialize)
 
 
666
    def is_supported(self):
 
 
667
        """Is this format supported?
 
 
669
        Supported formats can be initialized and opened.
 
 
670
        Unsupported formats may not support initialization or committing or 
 
 
671
        some other features depending on the reason for not being supported.
 
 
675
    def open(self, a_bzrdir, _found=False):
 
 
676
        """Return the branch object for a_bzrdir
 
 
678
        _found is a private parameter, do not use it. It is used to indicate
 
 
679
               if format probing has already be done.
 
 
681
        raise NotImplementedError(self.open)
 
 
684
    def register_format(klass, format):
 
 
685
        klass._formats[format.get_format_string()] = format
 
 
688
    def set_default_format(klass, format):
 
 
689
        klass._default_format = format
 
 
692
    def unregister_format(klass, format):
 
 
693
        assert klass._formats[format.get_format_string()] is format
 
 
694
        del klass._formats[format.get_format_string()]
 
 
697
        return self.get_format_string().rstrip()
 
 
700
class BzrBranchFormat4(BranchFormat):
 
 
701
    """Bzr branch format 4.
 
 
704
     - a revision-history file.
 
 
705
     - a branch-lock lock file [ to be shared with the bzrdir ]
 
 
708
    def get_format_description(self):
 
 
709
        """See BranchFormat.get_format_description()."""
 
 
710
        return "Branch format 4"
 
 
712
    def initialize(self, a_bzrdir):
 
 
713
        """Create a branch of this format in a_bzrdir."""
 
 
714
        mutter('creating branch in %s', a_bzrdir.transport.base)
 
 
715
        branch_transport = a_bzrdir.get_branch_transport(self)
 
 
716
        utf8_files = [('revision-history', ''),
 
 
719
        control_files = LockableFiles(branch_transport, 'branch-lock',
 
 
721
        control_files.create_lock()
 
 
722
        control_files.lock_write()
 
 
724
            for file, content in utf8_files:
 
 
725
                control_files.put_utf8(file, content)
 
 
727
            control_files.unlock()
 
 
728
        return self.open(a_bzrdir, _found=True)
 
 
731
        super(BzrBranchFormat4, self).__init__()
 
 
732
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
 
734
    def open(self, a_bzrdir, _found=False):
 
 
735
        """Return the branch object for a_bzrdir
 
 
737
        _found is a private parameter, do not use it. It is used to indicate
 
 
738
               if format probing has already be done.
 
 
741
            # we are being called directly and must probe.
 
 
742
            raise NotImplementedError
 
 
743
        return BzrBranch(_format=self,
 
 
744
                         _control_files=a_bzrdir._control_files,
 
 
746
                         _repository=a_bzrdir.open_repository())
 
 
749
        return "Bazaar-NG branch format 4"
 
 
752
class BzrBranchFormat5(BranchFormat):
 
 
753
    """Bzr branch format 5.
 
 
756
     - a revision-history file.
 
 
758
     - a lock dir guarding the branch itself
 
 
759
     - all of this stored in a branch/ subdirectory
 
 
760
     - works with shared repositories.
 
 
762
    This format is new in bzr 0.8.
 
 
765
    def get_format_string(self):
 
 
766
        """See BranchFormat.get_format_string()."""
 
 
767
        return "Bazaar-NG branch format 5\n"
 
 
769
    def get_format_description(self):
 
 
770
        """See BranchFormat.get_format_description()."""
 
 
771
        return "Branch format 5"
 
 
773
    def initialize(self, a_bzrdir):
 
 
774
        """Create a branch of this format in a_bzrdir."""
 
 
775
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
 
776
        branch_transport = a_bzrdir.get_branch_transport(self)
 
 
777
        utf8_files = [('revision-history', ''),
 
 
780
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
 
 
781
        control_files.create_lock()
 
 
782
        control_files.lock_write()
 
 
783
        control_files.put_utf8('format', self.get_format_string())
 
 
785
            for file, content in utf8_files:
 
 
786
                control_files.put_utf8(file, content)
 
 
788
            control_files.unlock()
 
 
789
        return self.open(a_bzrdir, _found=True, )
 
 
792
        super(BzrBranchFormat5, self).__init__()
 
 
793
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
 
795
    def open(self, a_bzrdir, _found=False):
 
 
796
        """Return the branch object for a_bzrdir
 
 
798
        _found is a private parameter, do not use it. It is used to indicate
 
 
799
               if format probing has already be done.
 
 
802
            format = BranchFormat.find_format(a_bzrdir)
 
 
803
            assert format.__class__ == self.__class__
 
 
804
        transport = a_bzrdir.get_branch_transport(None)
 
 
805
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
 
 
806
        return BzrBranch5(_format=self,
 
 
807
                          _control_files=control_files,
 
 
809
                          _repository=a_bzrdir.find_repository())
 
 
812
        return "Bazaar-NG Metadir branch format 5"
 
 
815
class BranchReferenceFormat(BranchFormat):
 
 
816
    """Bzr branch reference format.
 
 
818
    Branch references are used in implementing checkouts, they
 
 
819
    act as an alias to the real branch which is at some other url.
 
 
826
    def get_format_string(self):
 
 
827
        """See BranchFormat.get_format_string()."""
 
 
828
        return "Bazaar-NG Branch Reference Format 1\n"
 
 
830
    def get_format_description(self):
 
 
831
        """See BranchFormat.get_format_description()."""
 
 
832
        return "Checkout reference format 1"
 
 
834
    def initialize(self, a_bzrdir, target_branch=None):
 
 
835
        """Create a branch of this format in a_bzrdir."""
 
 
836
        if target_branch is None:
 
 
837
            # this format does not implement branch itself, thus the implicit
 
 
838
            # creation contract must see it as uninitializable
 
 
839
            raise errors.UninitializableFormat(self)
 
 
840
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
 
 
841
        branch_transport = a_bzrdir.get_branch_transport(self)
 
 
842
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
 
 
843
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
 
 
844
        branch_transport.put('format', StringIO(self.get_format_string()))
 
 
845
        return self.open(a_bzrdir, _found=True)
 
 
848
        super(BranchReferenceFormat, self).__init__()
 
 
849
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
 
851
    def _make_reference_clone_function(format, a_branch):
 
 
852
        """Create a clone() routine for a branch dynamically."""
 
 
853
        def clone(to_bzrdir, revision_id=None):
 
 
854
            """See Branch.clone()."""
 
 
855
            return format.initialize(to_bzrdir, a_branch)
 
 
856
            # cannot obey revision_id limits when cloning a reference ...
 
 
857
            # FIXME RBC 20060210 either nuke revision_id for clone, or
 
 
858
            # emit some sort of warning/error to the caller ?!
 
 
861
    def open(self, a_bzrdir, _found=False):
 
 
862
        """Return the branch that the branch reference in a_bzrdir points at.
 
 
864
        _found is a private parameter, do not use it. It is used to indicate
 
 
865
               if format probing has already be done.
 
 
868
            format = BranchFormat.find_format(a_bzrdir)
 
 
869
            assert format.__class__ == self.__class__
 
 
870
        transport = a_bzrdir.get_branch_transport(None)
 
 
871
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
 
 
872
        result = real_bzrdir.open_branch()
 
 
873
        # this changes the behaviour of result.clone to create a new reference
 
 
874
        # rather than a copy of the content of the branch.
 
 
875
        # I did not use a proxy object because that needs much more extensive
 
 
876
        # testing, and we are only changing one behaviour at the moment.
 
 
877
        # If we decide to alter more behaviours - i.e. the implicit nickname
 
 
878
        # then this should be refactored to introduce a tested proxy branch
 
 
879
        # and a subclass of that for use in overriding clone() and ....
 
 
881
        result.clone = self._make_reference_clone_function(result)
 
 
885
# formats which have no format string are not discoverable
 
 
886
# and not independently creatable, so are not registered.
 
 
887
__default_format = BzrBranchFormat5()
 
 
888
BranchFormat.register_format(__default_format)
 
 
889
BranchFormat.register_format(BranchReferenceFormat())
 
 
890
BranchFormat.set_default_format(__default_format)
 
 
891
_legacy_formats = [BzrBranchFormat4(),
 
 
894
class BzrBranch(Branch):
 
 
895
    """A branch stored in the actual filesystem.
 
 
897
    Note that it's "local" in the context of the filesystem; it doesn't
 
 
898
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
 
899
    it's writable, and can be accessed via the normal filesystem API.
 
 
902
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
 
 
903
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
 
 
904
                 _control_files=None, a_bzrdir=None, _repository=None):
 
 
905
        """Create new branch object at a particular location.
 
 
907
        transport -- A Transport object, defining how to access files.
 
 
909
        init -- If True, create new control files in a previously
 
 
910
             unversioned directory.  If False, the branch must already
 
 
913
        relax_version_check -- If true, the usual check for the branch
 
 
914
            version is not applied.  This is intended only for
 
 
915
            upgrade/recovery type use; it's not guaranteed that
 
 
916
            all operations will work on old format branches.
 
 
919
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
 
 
921
            self.bzrdir = a_bzrdir
 
 
922
        self._transport = self.bzrdir.transport.clone('..')
 
 
923
        self._base = self._transport.base
 
 
924
        self._format = _format
 
 
925
        if _control_files is None:
 
 
926
            raise ValueError('BzrBranch _control_files is None')
 
 
927
        self.control_files = _control_files
 
 
928
        if deprecated_passed(init):
 
 
929
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
 
 
930
                 "deprecated as of bzr 0.8. Please use Branch.create().",
 
 
934
                # this is slower than before deprecation, oh well never mind.
 
 
936
                self._initialize(transport.base)
 
 
937
        self._check_format(_format)
 
 
938
        if deprecated_passed(relax_version_check):
 
 
939
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
 
 
940
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
 
 
941
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
 
 
945
            if (not relax_version_check
 
 
946
                and not self._format.is_supported()):
 
 
947
                raise errors.UnsupportedFormatError(format=fmt)
 
 
948
        if deprecated_passed(transport):
 
 
949
            warn("BzrBranch.__init__(transport=XXX...): The transport "
 
 
950
                 "parameter is deprecated as of bzr 0.8. "
 
 
951
                 "Please use Branch.open, or bzrdir.open_branch().",
 
 
954
        self.repository = _repository
 
 
957
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
 
962
        # TODO: It might be best to do this somewhere else,
 
 
963
        # but it is nice for a Branch object to automatically
 
 
964
        # cache it's information.
 
 
965
        # Alternatively, we could have the Transport objects cache requests
 
 
966
        # See the earlier discussion about how major objects (like Branch)
 
 
967
        # should never expect their __del__ function to run.
 
 
968
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
 
 
969
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
 
971
                osutils.rmtree(self.cache_root)
 
 
974
            self.cache_root = None
 
 
979
    base = property(_get_base, doc="The URL for the root of this branch.")
 
 
981
    def _finish_transaction(self):
 
 
982
        """Exit the current transaction."""
 
 
983
        return self.control_files._finish_transaction()
 
 
985
    def get_transaction(self):
 
 
986
        """Return the current active transaction.
 
 
988
        If no transaction is active, this returns a passthrough object
 
 
989
        for which all data is immediately flushed and no caching happens.
 
 
991
        # this is an explicit function so that we can do tricky stuff
 
 
992
        # when the storage in rev_storage is elsewhere.
 
 
993
        # we probably need to hook the two 'lock a location' and 
 
 
994
        # 'have a transaction' together more delicately, so that
 
 
995
        # we can have two locks (branch and storage) and one transaction
 
 
996
        # ... and finishing the transaction unlocks both, but unlocking
 
 
997
        # does not. - RBC 20051121
 
 
998
        return self.control_files.get_transaction()
 
 
1000
    def _set_transaction(self, transaction):
 
 
1001
        """Set a new active transaction."""
 
 
1002
        return self.control_files._set_transaction(transaction)
 
 
1004
    def abspath(self, name):
 
 
1005
        """See Branch.abspath."""
 
 
1006
        return self.control_files._transport.abspath(name)
 
 
1008
    def _check_format(self, format):
 
 
1009
        """Identify the branch format if needed.
 
 
1011
        The format is stored as a reference to the format object in
 
 
1012
        self._format for code that needs to check it later.
 
 
1014
        The format parameter is either None or the branch format class
 
 
1015
        used to open this branch.
 
 
1017
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
 
 
1020
            format = BranchFormat.find_format(self.bzrdir)
 
 
1021
        self._format = format
 
 
1022
        mutter("got branch format %s", self._format)
 
 
1025
    def get_root_id(self):
 
 
1026
        """See Branch.get_root_id."""
 
 
1027
        tree = self.repository.revision_tree(self.last_revision())
 
 
1028
        return tree.inventory.root.file_id
 
 
1030
    def is_locked(self):
 
 
1031
        return self.control_files.is_locked()
 
 
1033
    def lock_write(self):
 
 
1034
        self.repository.lock_write()
 
 
1036
            self.control_files.lock_write()
 
 
1038
            self.repository.unlock()
 
 
1041
    def lock_read(self):
 
 
1042
        self.repository.lock_read()
 
 
1044
            self.control_files.lock_read()
 
 
1046
            self.repository.unlock()
 
 
1050
        # TODO: test for failed two phase locks. This is known broken.
 
 
1052
            self.control_files.unlock()
 
 
1054
            self.repository.unlock()
 
 
1056
    def peek_lock_mode(self):
 
 
1057
        if self.control_files._lock_count == 0:
 
 
1060
            return self.control_files._lock_mode
 
 
1062
    def get_physical_lock_status(self):
 
 
1063
        return self.control_files.get_physical_lock_status()
 
 
1066
    def print_file(self, file, revision_id):
 
 
1067
        """See Branch.print_file."""
 
 
1068
        return self.repository.print_file(file, revision_id)
 
 
1071
    def append_revision(self, *revision_ids):
 
 
1072
        """See Branch.append_revision."""
 
 
1073
        for revision_id in revision_ids:
 
 
1074
            mutter("add {%s} to revision-history" % revision_id)
 
 
1075
        rev_history = self.revision_history()
 
 
1076
        rev_history.extend(revision_ids)
 
 
1077
        self.set_revision_history(rev_history)
 
 
1080
    def set_revision_history(self, rev_history):
 
 
1081
        """See Branch.set_revision_history."""
 
 
1082
        self.control_files.put_utf8(
 
 
1083
            'revision-history', '\n'.join(rev_history))
 
 
1084
        transaction = self.get_transaction()
 
 
1085
        history = transaction.map.find_revision_history()
 
 
1086
        if history is not None:
 
 
1087
            # update the revision history in the identity map.
 
 
1088
            history[:] = list(rev_history)
 
 
1089
            # this call is disabled because revision_history is 
 
 
1090
            # not really an object yet, and the transaction is for objects.
 
 
1091
            # transaction.register_dirty(history)
 
 
1093
            transaction.map.add_revision_history(rev_history)
 
 
1094
            # this call is disabled because revision_history is 
 
 
1095
            # not really an object yet, and the transaction is for objects.
 
 
1096
            # transaction.register_clean(history)
 
 
1099
    def revision_history(self):
 
 
1100
        """See Branch.revision_history."""
 
 
1101
        transaction = self.get_transaction()
 
 
1102
        history = transaction.map.find_revision_history()
 
 
1103
        if history is not None:
 
 
1104
            # mutter("cache hit for revision-history in %s", self)
 
 
1105
            return list(history)
 
 
1106
        decode_utf8 = cache_utf8.decode
 
 
1107
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
 
1108
                self.control_files.get('revision-history').readlines()]
 
 
1109
        transaction.map.add_revision_history(history)
 
 
1110
        # this call is disabled because revision_history is 
 
 
1111
        # not really an object yet, and the transaction is for objects.
 
 
1112
        # transaction.register_clean(history, precious=True)
 
 
1113
        return list(history)
 
 
1116
    def generate_revision_history(self, revision_id, last_rev=None, 
 
 
1118
        """Create a new revision history that will finish with revision_id.
 
 
1120
        :param revision_id: the new tip to use.
 
 
1121
        :param last_rev: The previous last_revision. If not None, then this
 
 
1122
            must be a ancestory of revision_id, or DivergedBranches is raised.
 
 
1123
        :param other_branch: The other branch that DivergedBranches should
 
 
1124
            raise with respect to.
 
 
1126
        # stop_revision must be a descendant of last_revision
 
 
1127
        stop_graph = self.repository.get_revision_graph(revision_id)
 
 
1128
        if last_rev is not None and last_rev not in stop_graph:
 
 
1129
            # our previous tip is not merged into stop_revision
 
 
1130
            raise errors.DivergedBranches(self, other_branch)
 
 
1131
        # make a new revision history from the graph
 
 
1132
        current_rev_id = revision_id
 
 
1134
        while current_rev_id not in (None, revision.NULL_REVISION):
 
 
1135
            new_history.append(current_rev_id)
 
 
1136
            current_rev_id_parents = stop_graph[current_rev_id]
 
 
1138
                current_rev_id = current_rev_id_parents[0]
 
 
1140
                current_rev_id = None
 
 
1141
        new_history.reverse()
 
 
1142
        self.set_revision_history(new_history)
 
 
1145
    def update_revisions(self, other, stop_revision=None):
 
 
1146
        """See Branch.update_revisions."""
 
 
1149
            if stop_revision is None:
 
 
1150
                stop_revision = other.last_revision()
 
 
1151
                if stop_revision is None:
 
 
1152
                    # if there are no commits, we're done.
 
 
1154
            # whats the current last revision, before we fetch [and change it
 
 
1156
            last_rev = self.last_revision()
 
 
1157
            # we fetch here regardless of whether we need to so that we pickup
 
 
1159
            self.fetch(other, stop_revision)
 
 
1160
            my_ancestry = self.repository.get_ancestry(last_rev)
 
 
1161
            if stop_revision in my_ancestry:
 
 
1162
                # last_revision is a descendant of stop_revision
 
 
1164
            self.generate_revision_history(stop_revision, last_rev=last_rev,
 
 
1169
    def basis_tree(self):
 
 
1170
        """See Branch.basis_tree."""
 
 
1171
        return self.repository.revision_tree(self.last_revision())
 
 
1173
    @deprecated_method(zero_eight)
 
 
1174
    def working_tree(self):
 
 
1175
        """Create a Working tree object for this branch."""
 
 
1177
        from bzrlib.transport.local import LocalTransport
 
 
1178
        if (self.base.find('://') != -1 or 
 
 
1179
            not isinstance(self._transport, LocalTransport)):
 
 
1180
            raise NoWorkingTree(self.base)
 
 
1181
        return self.bzrdir.open_workingtree()
 
 
1184
    def pull(self, source, overwrite=False, stop_revision=None):
 
 
1185
        """See Branch.pull."""
 
 
1188
            old_count = len(self.revision_history())
 
 
1190
                self.update_revisions(source,stop_revision)
 
 
1191
            except DivergedBranches:
 
 
1195
                self.set_revision_history(source.revision_history())
 
 
1196
            new_count = len(self.revision_history())
 
 
1197
            return new_count - old_count
 
 
1201
    def get_parent(self):
 
 
1202
        """See Branch.get_parent."""
 
 
1204
        _locs = ['parent', 'pull', 'x-pull']
 
 
1205
        assert self.base[-1] == '/'
 
 
1208
                parent = self.control_files.get(l).read().strip('\n')
 
 
1211
            # This is an old-format absolute path to a local branch
 
 
1212
            # turn it into a url
 
 
1213
            if parent.startswith('/'):
 
 
1214
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
 
1216
                return urlutils.join(self.base[:-1], parent)
 
 
1217
            except errors.InvalidURLJoin, e:
 
 
1218
                raise errors.InaccessibleParent(parent, self.base)
 
 
1221
    def get_push_location(self):
 
 
1222
        """See Branch.get_push_location."""
 
 
1223
        push_loc = self.get_config().get_user_option('push_location')
 
 
1226
    def set_push_location(self, location):
 
 
1227
        """See Branch.set_push_location."""
 
 
1228
        self.get_config().set_user_option('push_location', location, 
 
 
1232
    def set_parent(self, url):
 
 
1233
        """See Branch.set_parent."""
 
 
1234
        # TODO: Maybe delete old location files?
 
 
1235
        # URLs should never be unicode, even on the local fs,
 
 
1236
        # FIXUP this and get_parent in a future branch format bump:
 
 
1237
        # read and rewrite the file, and have the new format code read
 
 
1238
        # using .get not .get_utf8. RBC 20060125
 
 
1240
            self.control_files._transport.delete('parent')
 
 
1242
            if isinstance(url, unicode):
 
 
1244
                    url = url.encode('ascii')
 
 
1245
                except UnicodeEncodeError:
 
 
1246
                    raise bzrlib.errors.InvalidURL(url,
 
 
1247
                        "Urls must be 7-bit ascii, "
 
 
1248
                        "use bzrlib.urlutils.escape")
 
 
1250
            url = urlutils.relative_url(self.base, url)
 
 
1251
            self.control_files.put('parent', url + '\n')
 
 
1253
    @deprecated_function(zero_nine)
 
 
1254
    def tree_config(self):
 
 
1255
        """DEPRECATED; call get_config instead.  
 
 
1256
        TreeConfig has become part of BranchConfig."""
 
 
1257
        return TreeConfig(self)
 
 
1260
class BzrBranch5(BzrBranch):
 
 
1261
    """A format 5 branch. This supports new features over plan branches.
 
 
1263
    It has support for a master_branch which is the data for bound branches.
 
 
1271
        super(BzrBranch5, self).__init__(_format=_format,
 
 
1272
                                         _control_files=_control_files,
 
 
1274
                                         _repository=_repository)
 
 
1277
    def pull(self, source, overwrite=False, stop_revision=None):
 
 
1278
        """Updates branch.pull to be bound branch aware."""
 
 
1279
        bound_location = self.get_bound_location()
 
 
1280
        if source.base != bound_location:
 
 
1281
            # not pulling from master, so we need to update master.
 
 
1282
            master_branch = self.get_master_branch()
 
 
1284
                master_branch.pull(source)
 
 
1285
                source = master_branch
 
 
1286
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
 
 
1288
    def get_bound_location(self):
 
 
1290
            return self.control_files.get_utf8('bound').read()[:-1]
 
 
1291
        except errors.NoSuchFile:
 
 
1295
    def get_master_branch(self):
 
 
1296
        """Return the branch we are bound to.
 
 
1298
        :return: Either a Branch, or None
 
 
1300
        This could memoise the branch, but if thats done
 
 
1301
        it must be revalidated on each new lock.
 
 
1302
        So for now we just don't memoise it.
 
 
1303
        # RBC 20060304 review this decision.
 
 
1305
        bound_loc = self.get_bound_location()
 
 
1309
            return Branch.open(bound_loc)
 
 
1310
        except (errors.NotBranchError, errors.ConnectionError), e:
 
 
1311
            raise errors.BoundBranchConnectionFailure(
 
 
1315
    def set_bound_location(self, location):
 
 
1316
        """Set the target where this branch is bound to.
 
 
1318
        :param location: URL to the target branch
 
 
1321
            self.control_files.put_utf8('bound', location+'\n')
 
 
1324
                self.control_files._transport.delete('bound')
 
 
1330
    def bind(self, other):
 
 
1331
        """Bind the local branch the other branch.
 
 
1333
        :param other: The branch to bind to
 
 
1336
        # TODO: jam 20051230 Consider checking if the target is bound
 
 
1337
        #       It is debatable whether you should be able to bind to
 
 
1338
        #       a branch which is itself bound.
 
 
1339
        #       Committing is obviously forbidden,
 
 
1340
        #       but binding itself may not be.
 
 
1341
        #       Since we *have* to check at commit time, we don't
 
 
1342
        #       *need* to check here
 
 
1345
        # we are now equal to or a suffix of other.
 
 
1347
        # Since we have 'pulled' from the remote location,
 
 
1348
        # now we should try to pull in the opposite direction
 
 
1349
        # in case the local tree has more revisions than the
 
 
1351
        # There may be a different check you could do here
 
 
1352
        # rather than actually trying to install revisions remotely.
 
 
1353
        # TODO: capture an exception which indicates the remote branch
 
 
1355
        #       If it is up-to-date, this probably should not be a failure
 
 
1357
        # lock other for write so the revision-history syncing cannot race
 
 
1361
            # if this does not error, other now has the same last rev we do
 
 
1362
            # it can only error if the pull from other was concurrent with
 
 
1363
            # a commit to other from someone else.
 
 
1365
            # until we ditch revision-history, we need to sync them up:
 
 
1366
            self.set_revision_history(other.revision_history())
 
 
1367
            # now other and self are up to date with each other and have the
 
 
1368
            # same revision-history.
 
 
1372
        self.set_bound_location(other.base)
 
 
1376
        """If bound, unbind"""
 
 
1377
        return self.set_bound_location(None)
 
 
1381
        """Synchronise this branch with the master branch if any. 
 
 
1383
        :return: None or the last_revision that was pivoted out during the
 
 
1386
        master = self.get_master_branch()
 
 
1387
        if master is not None:
 
 
1388
            old_tip = self.last_revision()
 
 
1389
            self.pull(master, overwrite=True)
 
 
1390
            if old_tip in self.repository.get_ancestry(self.last_revision()):
 
 
1396
class BranchTestProviderAdapter(object):
 
 
1397
    """A tool to generate a suite testing multiple branch formats at once.
 
 
1399
    This is done by copying the test once for each transport and injecting
 
 
1400
    the transport_server, transport_readonly_server, and branch_format
 
 
1401
    classes into each copy. Each copy is also given a new id() to make it
 
 
1405
    def __init__(self, transport_server, transport_readonly_server, formats):
 
 
1406
        self._transport_server = transport_server
 
 
1407
        self._transport_readonly_server = transport_readonly_server
 
 
1408
        self._formats = formats
 
 
1410
    def adapt(self, test):
 
 
1411
        result = TestSuite()
 
 
1412
        for branch_format, bzrdir_format in self._formats:
 
 
1413
            new_test = deepcopy(test)
 
 
1414
            new_test.transport_server = self._transport_server
 
 
1415
            new_test.transport_readonly_server = self._transport_readonly_server
 
 
1416
            new_test.bzrdir_format = bzrdir_format
 
 
1417
            new_test.branch_format = branch_format
 
 
1418
            def make_new_test_id():
 
 
1419
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
 
 
1420
                return lambda: new_id
 
 
1421
            new_test.id = make_new_test_id()
 
 
1422
            result.addTest(new_test)
 
 
1426
class BranchCheckResult(object):
 
 
1427
    """Results of checking branch consistency.
 
 
1432
    def __init__(self, branch):
 
 
1433
        self.branch = branch
 
 
1435
    def report_results(self, verbose):
 
 
1436
        """Report the check results via trace.note.
 
 
1438
        :param verbose: Requests more detailed display of what was checked,
 
 
1441
        note('checked branch %s format %s',
 
 
1443
             self.branch._format)
 
 
1446
######################################################################
 
 
1450
@deprecated_function(zero_eight)
 
 
1451
def is_control_file(*args, **kwargs):
 
 
1452
    """See bzrlib.workingtree.is_control_file."""
 
 
1453
    return bzrlib.workingtree.is_control_file(*args, **kwargs)