~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Vincent Ladeuil
  • Date: 2010-08-30 07:42:12 UTC
  • mfrom: (5247.2.39 catch-them-all)
  • mto: This revision was merged to the branch mainline in revision 5396.
  • Revision ID: v.ladeuil+lp@free.fr-20100830074212-y0vj6t3sm1zr706y
Fix most of the leaking tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
25
25
        bzrdir,
26
26
        cache_utf8,
27
27
        config as _mod_config,
 
28
        controldir,
28
29
        debug,
29
30
        errors,
30
31
        lockdir,
31
32
        lockable_files,
 
33
        remote,
32
34
        repository,
33
35
        revision as _mod_revision,
34
36
        rio,
46
48
    )
47
49
""")
48
50
 
49
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
51
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
50
52
from bzrlib.hooks import HookPoint, Hooks
51
53
from bzrlib.inter import InterObject
 
54
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
52
55
from bzrlib import registry
53
56
from bzrlib.symbol_versioning import (
54
57
    deprecated_in,
62
65
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
63
66
 
64
67
 
65
 
# TODO: Maybe include checks for common corruption of newlines, etc?
66
 
 
67
 
# TODO: Some operations like log might retrieve the same revisions
68
 
# repeatedly to calculate deltas.  We could perhaps have a weakref
69
 
# cache in memory to make this faster.  In general anything can be
70
 
# cached in memory between lock and unlock operations. .. nb thats
71
 
# what the transaction identity map provides
72
 
 
73
 
 
74
 
######################################################################
75
 
# branch objects
76
 
 
77
 
class Branch(object):
 
68
class Branch(controldir.ControlComponent):
78
69
    """Branch holding a history of revisions.
79
70
 
80
 
    base
81
 
        Base directory/url of the branch.
 
71
    :ivar base:
 
72
        Base directory/url of the branch; using control_url and
 
73
        control_transport is more standardized.
82
74
 
83
75
    hooks: An instance of BranchHooks.
84
76
    """
86
78
    # - RBC 20060112
87
79
    base = None
88
80
 
 
81
    @property
 
82
    def control_transport(self):
 
83
        return self._transport
 
84
 
 
85
    @property
 
86
    def user_transport(self):
 
87
        return self.bzrdir.user_transport
 
88
 
89
89
    def __init__(self, *ignored, **ignored_too):
90
90
        self.tags = self._format.make_tags(self)
91
91
        self._revision_history_cache = None
106
106
        """Activate the branch/repository from url as a fallback repository."""
107
107
        repo = self._get_fallback_repository(url)
108
108
        if repo.has_same_location(self.repository):
109
 
            raise errors.UnstackableLocationError(self.base, url)
 
109
            raise errors.UnstackableLocationError(self.user_url, url)
110
110
        self.repository.add_fallback_repository(repo)
111
111
 
112
112
    def break_lock(self):
166
166
        """
167
167
        control = bzrdir.BzrDir.open(base, _unsupported,
168
168
                                     possible_transports=possible_transports)
169
 
        return control.open_branch(_unsupported)
 
169
        return control.open_branch(unsupported=_unsupported)
170
170
 
171
171
    @staticmethod
172
 
    def open_from_transport(transport, _unsupported=False):
 
172
    def open_from_transport(transport, name=None, _unsupported=False):
173
173
        """Open the branch rooted at transport"""
174
174
        control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
175
 
        return control.open_branch(_unsupported)
 
175
        return control.open_branch(name=name, unsupported=_unsupported)
176
176
 
177
177
    @staticmethod
178
178
    def open_containing(url, possible_transports=None):
199
199
        return self.supports_tags() and self.tags.get_tag_dict()
200
200
 
201
201
    def get_config(self):
 
202
        """Get a bzrlib.config.BranchConfig for this Branch.
 
203
 
 
204
        This can then be used to get and set configuration options for the
 
205
        branch.
 
206
 
 
207
        :return: A bzrlib.config.BranchConfig.
 
208
        """
202
209
        return BranchConfig(self)
203
210
 
204
211
    def _get_config(self):
216
223
    def _get_fallback_repository(self, url):
217
224
        """Get the repository we fallback to at url."""
218
225
        url = urlutils.join(self.base, url)
219
 
        a_bzrdir = bzrdir.BzrDir.open(url,
 
226
        a_branch = Branch.open(url,
220
227
            possible_transports=[self.bzrdir.root_transport])
221
 
        return a_bzrdir.open_branch().repository
 
228
        return a_branch.repository
222
229
 
223
230
    def _get_tags_bytes(self):
224
231
        """Get the bytes of a serialised tags dict.
240
247
        if not local and not config.has_explicit_nickname():
241
248
            try:
242
249
                master = self.get_master_branch(possible_transports)
 
250
                if master and self.user_url == master.user_url:
 
251
                    raise errors.RecursiveBind(self.user_url)
243
252
                if master is not None:
244
253
                    # return the master branch value
245
254
                    return master.nick
 
255
            except errors.RecursiveBind, e:
 
256
                raise e
246
257
            except errors.BzrError, e:
247
258
                # Silently fall back to local implicit nick if the master is
248
259
                # unavailable
285
296
        new_history.reverse()
286
297
        return new_history
287
298
 
288
 
    def lock_write(self):
 
299
    def lock_write(self, token=None):
 
300
        """Lock the branch for write operations.
 
301
 
 
302
        :param token: A token to permit reacquiring a previously held and
 
303
            preserved lock.
 
304
        :return: A BranchWriteLockResult.
 
305
        """
289
306
        raise NotImplementedError(self.lock_write)
290
307
 
291
308
    def lock_read(self):
 
309
        """Lock the branch for read operations.
 
310
 
 
311
        :return: A bzrlib.lock.LogicalLockResult.
 
312
        """
292
313
        raise NotImplementedError(self.lock_read)
293
314
 
294
315
    def unlock(self):
419
440
            * 'include' - the stop revision is the last item in the result
420
441
            * 'with-merges' - include the stop revision and all of its
421
442
              merged revisions in the result
 
443
            * 'with-merges-without-common-ancestry' - filter out revisions 
 
444
              that are in both ancestries
422
445
        :param direction: either 'reverse' or 'forward':
423
446
            * reverse means return the start_revision_id first, i.e.
424
447
              start at the most recent revision and go backwards in history
446
469
        # start_revision_id.
447
470
        if self._merge_sorted_revisions_cache is None:
448
471
            last_revision = self.last_revision()
449
 
            graph = self.repository.get_graph()
450
 
            parent_map = dict(((key, value) for key, value in
451
 
                     graph.iter_ancestry([last_revision]) if value is not None))
452
 
            revision_graph = repository._strip_NULL_ghosts(parent_map)
453
 
            revs = tsort.merge_sort(revision_graph, last_revision, None,
454
 
                generate_revno=True)
455
 
            # Drop the sequence # before caching
456
 
            self._merge_sorted_revisions_cache = [r[1:] for r in revs]
457
 
 
 
472
            known_graph = self.repository.get_known_graph_ancestry(
 
473
                [last_revision])
 
474
            self._merge_sorted_revisions_cache = known_graph.merge_sort(
 
475
                last_revision)
458
476
        filtered = self._filter_merge_sorted_revisions(
459
477
            self._merge_sorted_revisions_cache, start_revision_id,
460
478
            stop_revision_id, stop_rule)
 
479
        # Make sure we don't return revisions that are not part of the
 
480
        # start_revision_id ancestry.
 
481
        filtered = self._filter_start_non_ancestors(filtered)
461
482
        if direction == 'reverse':
462
483
            return filtered
463
484
        if direction == 'forward':
470
491
        """Iterate over an inclusive range of sorted revisions."""
471
492
        rev_iter = iter(merge_sorted_revisions)
472
493
        if start_revision_id is not None:
473
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
 
494
            for node in rev_iter:
 
495
                rev_id = node.key[-1]
474
496
                if rev_id != start_revision_id:
475
497
                    continue
476
498
                else:
477
499
                    # The decision to include the start or not
478
500
                    # depends on the stop_rule if a stop is provided
479
 
                    rev_iter = chain(
480
 
                        iter([(rev_id, depth, revno, end_of_merge)]),
481
 
                        rev_iter)
 
501
                    # so pop this node back into the iterator
 
502
                    rev_iter = chain(iter([node]), rev_iter)
482
503
                    break
483
504
        if stop_revision_id is None:
484
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
485
 
                yield rev_id, depth, revno, end_of_merge
 
505
            # Yield everything
 
506
            for node in rev_iter:
 
507
                rev_id = node.key[-1]
 
508
                yield (rev_id, node.merge_depth, node.revno,
 
509
                       node.end_of_merge)
486
510
        elif stop_rule == 'exclude':
487
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
 
511
            for node in rev_iter:
 
512
                rev_id = node.key[-1]
488
513
                if rev_id == stop_revision_id:
489
514
                    return
490
 
                yield rev_id, depth, revno, end_of_merge
 
515
                yield (rev_id, node.merge_depth, node.revno,
 
516
                       node.end_of_merge)
491
517
        elif stop_rule == 'include':
492
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
493
 
                yield rev_id, depth, revno, end_of_merge
 
518
            for node in rev_iter:
 
519
                rev_id = node.key[-1]
 
520
                yield (rev_id, node.merge_depth, node.revno,
 
521
                       node.end_of_merge)
494
522
                if rev_id == stop_revision_id:
495
523
                    return
 
524
        elif stop_rule == 'with-merges-without-common-ancestry':
 
525
            # We want to exclude all revisions that are already part of the
 
526
            # stop_revision_id ancestry.
 
527
            graph = self.repository.get_graph()
 
528
            ancestors = graph.find_unique_ancestors(start_revision_id,
 
529
                                                    [stop_revision_id])
 
530
            for node in rev_iter:
 
531
                rev_id = node.key[-1]
 
532
                if rev_id not in ancestors:
 
533
                    continue
 
534
                yield (rev_id, node.merge_depth, node.revno,
 
535
                       node.end_of_merge)
496
536
        elif stop_rule == 'with-merges':
497
537
            stop_rev = self.repository.get_revision(stop_revision_id)
498
538
            if stop_rev.parent_ids:
499
539
                left_parent = stop_rev.parent_ids[0]
500
540
            else:
501
541
                left_parent = _mod_revision.NULL_REVISION
502
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
 
542
            # left_parent is the actual revision we want to stop logging at,
 
543
            # since we want to show the merged revisions after the stop_rev too
 
544
            reached_stop_revision_id = False
 
545
            revision_id_whitelist = []
 
546
            for node in rev_iter:
 
547
                rev_id = node.key[-1]
503
548
                if rev_id == left_parent:
 
549
                    # reached the left parent after the stop_revision
504
550
                    return
505
 
                yield rev_id, depth, revno, end_of_merge
 
551
                if (not reached_stop_revision_id or
 
552
                        rev_id in revision_id_whitelist):
 
553
                    yield (rev_id, node.merge_depth, node.revno,
 
554
                       node.end_of_merge)
 
555
                    if reached_stop_revision_id or rev_id == stop_revision_id:
 
556
                        # only do the merged revs of rev_id from now on
 
557
                        rev = self.repository.get_revision(rev_id)
 
558
                        if rev.parent_ids:
 
559
                            reached_stop_revision_id = True
 
560
                            revision_id_whitelist.extend(rev.parent_ids)
506
561
        else:
507
562
            raise ValueError('invalid stop_rule %r' % stop_rule)
508
563
 
 
564
    def _filter_start_non_ancestors(self, rev_iter):
 
565
        # If we started from a dotted revno, we want to consider it as a tip
 
566
        # and don't want to yield revisions that are not part of its
 
567
        # ancestry. Given the order guaranteed by the merge sort, we will see
 
568
        # uninteresting descendants of the first parent of our tip before the
 
569
        # tip itself.
 
570
        first = rev_iter.next()
 
571
        (rev_id, merge_depth, revno, end_of_merge) = first
 
572
        yield first
 
573
        if not merge_depth:
 
574
            # We start at a mainline revision so by definition, all others
 
575
            # revisions in rev_iter are ancestors
 
576
            for node in rev_iter:
 
577
                yield node
 
578
 
 
579
        clean = False
 
580
        whitelist = set()
 
581
        pmap = self.repository.get_parent_map([rev_id])
 
582
        parents = pmap.get(rev_id, [])
 
583
        if parents:
 
584
            whitelist.update(parents)
 
585
        else:
 
586
            # If there is no parents, there is nothing of interest left
 
587
 
 
588
            # FIXME: It's hard to test this scenario here as this code is never
 
589
            # called in that case. -- vila 20100322
 
590
            return
 
591
 
 
592
        for (rev_id, merge_depth, revno, end_of_merge) in rev_iter:
 
593
            if not clean:
 
594
                if rev_id in whitelist:
 
595
                    pmap = self.repository.get_parent_map([rev_id])
 
596
                    parents = pmap.get(rev_id, [])
 
597
                    whitelist.remove(rev_id)
 
598
                    whitelist.update(parents)
 
599
                    if merge_depth == 0:
 
600
                        # We've reached the mainline, there is nothing left to
 
601
                        # filter
 
602
                        clean = True
 
603
                else:
 
604
                    # A revision that is not part of the ancestry of our
 
605
                    # starting revision.
 
606
                    continue
 
607
            yield (rev_id, merge_depth, revno, end_of_merge)
 
608
 
509
609
    def leave_lock_in_place(self):
510
610
        """Tell this branch object not to release the physical lock when this
511
611
        object is unlocked.
528
628
        :param other: The branch to bind to
529
629
        :type other: Branch
530
630
        """
531
 
        raise errors.UpgradeRequired(self.base)
 
631
        raise errors.UpgradeRequired(self.user_url)
532
632
 
533
633
    def set_append_revisions_only(self, enabled):
534
634
        if not self._format.supports_set_append_revisions_only():
535
 
            raise errors.UpgradeRequired(self.base)
 
635
            raise errors.UpgradeRequired(self.user_url)
536
636
        if enabled:
537
637
            value = 'True'
538
638
        else:
586
686
    def get_old_bound_location(self):
587
687
        """Return the URL of the branch we used to be bound to
588
688
        """
589
 
        raise errors.UpgradeRequired(self.base)
 
689
        raise errors.UpgradeRequired(self.user_url)
590
690
 
591
691
    def get_commit_builder(self, parents, config=None, timestamp=None,
592
692
                           timezone=None, committer=None, revprops=None,
670
770
            stacking.
671
771
        """
672
772
        if not self._format.supports_stacking():
673
 
            raise errors.UnstackableBranchFormat(self._format, self.base)
 
773
            raise errors.UnstackableBranchFormat(self._format, self.user_url)
674
774
        # XXX: Changing from one fallback repository to another does not check
675
775
        # that all the data you need is present in the new fallback.
676
776
        # Possibly it should.
706
806
            if len(old_repository._fallback_repositories) != 1:
707
807
                raise AssertionError("can't cope with fallback repositories "
708
808
                    "of %r" % (self.repository,))
709
 
            # unlock it, including unlocking the fallback
 
809
            # Open the new repository object.
 
810
            # Repositories don't offer an interface to remove fallback
 
811
            # repositories today; take the conceptually simpler option and just
 
812
            # reopen it.  We reopen it starting from the URL so that we
 
813
            # get a separate connection for RemoteRepositories and can
 
814
            # stream from one of them to the other.  This does mean doing
 
815
            # separate SSH connection setup, but unstacking is not a
 
816
            # common operation so it's tolerable.
 
817
            new_bzrdir = bzrdir.BzrDir.open(self.bzrdir.root_transport.base)
 
818
            new_repository = new_bzrdir.find_repository()
 
819
            if new_repository._fallback_repositories:
 
820
                raise AssertionError("didn't expect %r to have "
 
821
                    "fallback_repositories"
 
822
                    % (self.repository,))
 
823
            # Replace self.repository with the new repository.
 
824
            # Do our best to transfer the lock state (i.e. lock-tokens and
 
825
            # lock count) of self.repository to the new repository.
 
826
            lock_token = old_repository.lock_write().repository_token
 
827
            self.repository = new_repository
 
828
            if isinstance(self, remote.RemoteBranch):
 
829
                # Remote branches can have a second reference to the old
 
830
                # repository that need to be replaced.
 
831
                if self._real_branch is not None:
 
832
                    self._real_branch.repository = new_repository
 
833
            self.repository.lock_write(token=lock_token)
 
834
            if lock_token is not None:
 
835
                old_repository.leave_lock_in_place()
710
836
            old_repository.unlock()
 
837
            if lock_token is not None:
 
838
                # XXX: self.repository.leave_lock_in_place() before this
 
839
                # function will not be preserved.  Fortunately that doesn't
 
840
                # affect the current default format (2a), and would be a
 
841
                # corner-case anyway.
 
842
                #  - Andrew Bennetts, 2010/06/30
 
843
                self.repository.dont_leave_lock_in_place()
 
844
            old_lock_count = 0
 
845
            while True:
 
846
                try:
 
847
                    old_repository.unlock()
 
848
                except errors.LockNotHeld:
 
849
                    break
 
850
                old_lock_count += 1
 
851
            if old_lock_count == 0:
 
852
                raise AssertionError(
 
853
                    'old_repository should have been locked at least once.')
 
854
            for i in range(old_lock_count-1):
 
855
                self.repository.lock_write()
 
856
            # Fetch from the old repository into the new.
711
857
            old_repository.lock_read()
712
858
            try:
713
 
                # Repositories don't offer an interface to remove fallback
714
 
                # repositories today; take the conceptually simpler option and just
715
 
                # reopen it.  We reopen it starting from the URL so that we
716
 
                # get a separate connection for RemoteRepositories and can
717
 
                # stream from one of them to the other.  This does mean doing
718
 
                # separate SSH connection setup, but unstacking is not a
719
 
                # common operation so it's tolerable.
720
 
                new_bzrdir = bzrdir.BzrDir.open(self.bzrdir.root_transport.base)
721
 
                new_repository = new_bzrdir.find_repository()
722
 
                self.repository = new_repository
723
 
                if self.repository._fallback_repositories:
724
 
                    raise AssertionError("didn't expect %r to have "
725
 
                        "fallback_repositories"
726
 
                        % (self.repository,))
727
 
                # this is not paired with an unlock because it's just restoring
728
 
                # the previous state; the lock's released when set_stacked_on_url
729
 
                # returns
730
 
                self.repository.lock_write()
731
859
                # XXX: If you unstack a branch while it has a working tree
732
860
                # with a pending merge, the pending-merged revisions will no
733
861
                # longer be present.  You can (probably) revert and remerge.
827
955
 
828
956
    def unbind(self):
829
957
        """Older format branches cannot bind or unbind."""
830
 
        raise errors.UpgradeRequired(self.base)
 
958
        raise errors.UpgradeRequired(self.user_url)
831
959
 
832
960
    def last_revision(self):
833
961
        """Return last revision id, or NULL_REVISION."""
874
1002
                raise errors.NoSuchRevision(self, stop_revision)
875
1003
        return other_history[self_len:stop_revision]
876
1004
 
877
 
    @needs_write_lock
878
1005
    def update_revisions(self, other, stop_revision=None, overwrite=False,
879
1006
                         graph=None):
880
1007
        """Pull in new perfect-fit revisions.
929
1056
            self._extend_partial_history(distance_from_last)
930
1057
        return self._partial_revision_history_cache[distance_from_last]
931
1058
 
932
 
    @needs_write_lock
933
1059
    def pull(self, source, overwrite=False, stop_revision=None,
934
1060
             possible_transports=None, *args, **kwargs):
935
1061
        """Mirror source into this branch.
993
1119
        try:
994
1120
            return urlutils.join(self.base[:-1], parent)
995
1121
        except errors.InvalidURLJoin, e:
996
 
            raise errors.InaccessibleParent(parent, self.base)
 
1122
            raise errors.InaccessibleParent(parent, self.user_url)
997
1123
 
998
1124
    def _get_parent_location(self):
999
1125
        raise NotImplementedError(self._get_parent_location)
1084
1210
        params = ChangeBranchTipParams(
1085
1211
            self, old_revno, new_revno, old_revid, new_revid)
1086
1212
        for hook in hooks:
1087
 
            try:
1088
 
                hook(params)
1089
 
            except errors.TipChangeRejected:
1090
 
                raise
1091
 
            except Exception:
1092
 
                exc_info = sys.exc_info()
1093
 
                hook_name = Branch.hooks.get_hook_name(hook)
1094
 
                raise errors.HookFailed(
1095
 
                    'pre_change_branch_tip', hook_name, exc_info)
 
1213
            hook(params)
1096
1214
 
1097
1215
    @needs_write_lock
1098
1216
    def update(self):
1147
1265
        revision_id: if not None, the revision history in the new branch will
1148
1266
                     be truncated to end with revision_id.
1149
1267
        """
 
1268
        if (repository_policy is not None and
 
1269
            repository_policy.requires_stacking()):
 
1270
            to_bzrdir._format.require_stacking(_skip_repo=True)
1150
1271
        result = to_bzrdir.create_branch()
1151
1272
        result.lock_write()
1152
1273
        try:
1183
1304
                revno = 1
1184
1305
        destination.set_last_revision_info(revno, revision_id)
1185
1306
 
1186
 
    @needs_read_lock
1187
1307
    def copy_content_into(self, destination, revision_id=None):
1188
1308
        """Copy the content of self into destination.
1189
1309
 
1190
1310
        revision_id: if not None, the revision history in the new branch will
1191
1311
                     be truncated to end with revision_id.
1192
1312
        """
1193
 
        self.update_references(destination)
1194
 
        self._synchronize_history(destination, revision_id)
1195
 
        try:
1196
 
            parent = self.get_parent()
1197
 
        except errors.InaccessibleParent, e:
1198
 
            mutter('parent was not accessible to copy: %s', e)
1199
 
        else:
1200
 
            if parent:
1201
 
                destination.set_parent(parent)
1202
 
        if self._push_should_merge_tags():
1203
 
            self.tags.merge_to(destination.tags)
 
1313
        return InterBranch.get(self, destination).copy_content_into(
 
1314
            revision_id=revision_id)
1204
1315
 
1205
1316
    def update_references(self, target):
1206
1317
        if not getattr(self._format, 'supports_reference_locations', False):
1274
1385
        """
1275
1386
        # XXX: Fix the bzrdir API to allow getting the branch back from the
1276
1387
        # clone call. Or something. 20090224 RBC/spiv.
 
1388
        # XXX: Should this perhaps clone colocated branches as well, 
 
1389
        # rather than just the default branch? 20100319 JRV
1277
1390
        if revision_id is None:
1278
1391
            revision_id = self.last_revision()
1279
 
        try:
1280
 
            dir_to = self.bzrdir.clone_on_transport(to_transport,
1281
 
                revision_id=revision_id, stacked_on=stacked_on,
1282
 
                create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1283
 
        except errors.FileExists:
1284
 
            if not use_existing_dir:
1285
 
                raise
1286
 
        except errors.NoSuchFile:
1287
 
            if not create_prefix:
1288
 
                raise
 
1392
        dir_to = self.bzrdir.clone_on_transport(to_transport,
 
1393
            revision_id=revision_id, stacked_on=stacked_on,
 
1394
            create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1289
1395
        return dir_to.open_branch()
1290
1396
 
1291
1397
    def create_checkout(self, to_location, revision_id=None,
1310
1416
        if lightweight:
1311
1417
            format = self._get_checkout_format()
1312
1418
            checkout = format.initialize_on_transport(t)
1313
 
            from_branch = BranchReferenceFormat().initialize(checkout, self)
 
1419
            from_branch = BranchReferenceFormat().initialize(checkout, 
 
1420
                target_branch=self)
1314
1421
        else:
1315
1422
            format = self._get_checkout_format()
1316
1423
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
1358
1465
    def supports_tags(self):
1359
1466
        return self._format.supports_tags()
1360
1467
 
 
1468
    def automatic_tag_name(self, revision_id):
 
1469
        """Try to automatically find the tag name for a revision.
 
1470
 
 
1471
        :param revision_id: Revision id of the revision.
 
1472
        :return: A tag name or None if no tag name could be determined.
 
1473
        """
 
1474
        for hook in Branch.hooks['automatic_tag_name']:
 
1475
            ret = hook(self, revision_id)
 
1476
            if ret is not None:
 
1477
                return ret
 
1478
        return None
 
1479
 
1361
1480
    def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
1362
1481
                                         other_branch):
1363
1482
        """Ensure that revision_b is a descendant of revision_a.
1427
1546
        return not (self == other)
1428
1547
 
1429
1548
    @classmethod
1430
 
    def find_format(klass, a_bzrdir):
 
1549
    def find_format(klass, a_bzrdir, name=None):
1431
1550
        """Return the format for the branch object in a_bzrdir."""
1432
1551
        try:
1433
 
            transport = a_bzrdir.get_branch_transport(None)
1434
 
            format_string = transport.get("format").read()
1435
 
            return klass._formats[format_string]
 
1552
            transport = a_bzrdir.get_branch_transport(None, name=name)
 
1553
            format_string = transport.get_bytes("format")
 
1554
            format = klass._formats[format_string]
 
1555
            if isinstance(format, MetaDirBranchFormatFactory):
 
1556
                return format()
 
1557
            return format
1436
1558
        except errors.NoSuchFile:
1437
 
            raise errors.NotBranchError(path=transport.base)
 
1559
            raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1438
1560
        except KeyError:
1439
1561
            raise errors.UnknownFormatError(format=format_string, kind='branch')
1440
1562
 
1443
1565
        """Return the current default format."""
1444
1566
        return klass._default_format
1445
1567
 
1446
 
    def get_reference(self, a_bzrdir):
 
1568
    @classmethod
 
1569
    def get_formats(klass):
 
1570
        """Get all the known formats.
 
1571
 
 
1572
        Warning: This triggers a load of all lazy registered formats: do not
 
1573
        use except when that is desireed.
 
1574
        """
 
1575
        result = []
 
1576
        for fmt in klass._formats.values():
 
1577
            if isinstance(fmt, MetaDirBranchFormatFactory):
 
1578
                fmt = fmt()
 
1579
            result.append(fmt)
 
1580
        return result
 
1581
 
 
1582
    def get_reference(self, a_bzrdir, name=None):
1447
1583
        """Get the target reference of the branch in a_bzrdir.
1448
1584
 
1449
1585
        format probing must have been completed before calling
1451
1587
        in a_bzrdir is correct.
1452
1588
 
1453
1589
        :param a_bzrdir: The bzrdir to get the branch data from.
 
1590
        :param name: Name of the colocated branch to fetch
1454
1591
        :return: None if the branch is not a reference branch.
1455
1592
        """
1456
1593
        return None
1457
1594
 
1458
1595
    @classmethod
1459
 
    def set_reference(self, a_bzrdir, to_branch):
 
1596
    def set_reference(self, a_bzrdir, name, to_branch):
1460
1597
        """Set the target reference of the branch in a_bzrdir.
1461
1598
 
1462
1599
        format probing must have been completed before calling
1464
1601
        in a_bzrdir is correct.
1465
1602
 
1466
1603
        :param a_bzrdir: The bzrdir to set the branch reference for.
 
1604
        :param name: Name of colocated branch to set, None for default
1467
1605
        :param to_branch: branch that the checkout is to reference
1468
1606
        """
1469
1607
        raise NotImplementedError(self.set_reference)
1476
1614
        """Return the short format description for this format."""
1477
1615
        raise NotImplementedError(self.get_format_description)
1478
1616
 
1479
 
    def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
1480
 
                           set_format=True):
 
1617
    def _run_post_branch_init_hooks(self, a_bzrdir, name, branch):
 
1618
        hooks = Branch.hooks['post_branch_init']
 
1619
        if not hooks:
 
1620
            return
 
1621
        params = BranchInitHookParams(self, a_bzrdir, name, branch)
 
1622
        for hook in hooks:
 
1623
            hook(params)
 
1624
 
 
1625
    def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
 
1626
                           lock_type='metadir', set_format=True):
1481
1627
        """Initialize a branch in a bzrdir, with specified files
1482
1628
 
1483
1629
        :param a_bzrdir: The bzrdir to initialize the branch in
1484
1630
        :param utf8_files: The files to create as a list of
1485
1631
            (filename, content) tuples
 
1632
        :param name: Name of colocated branch to create, if any
1486
1633
        :param set_format: If True, set the format with
1487
1634
            self.get_format_string.  (BzrBranch4 has its format set
1488
1635
            elsewhere)
1489
1636
        :return: a branch in this format
1490
1637
        """
1491
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
1492
 
        branch_transport = a_bzrdir.get_branch_transport(self)
 
1638
        mutter('creating branch %r in %s', self, a_bzrdir.user_url)
 
1639
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
1493
1640
        lock_map = {
1494
1641
            'metadir': ('lock', lockdir.LockDir),
1495
1642
            'branch4': ('branch-lock', lockable_files.TransportLock),
1516
1663
        finally:
1517
1664
            if lock_taken:
1518
1665
                control_files.unlock()
1519
 
        return self.open(a_bzrdir, _found=True)
 
1666
        branch = self.open(a_bzrdir, name, _found=True)
 
1667
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
1668
        return branch
1520
1669
 
1521
 
    def initialize(self, a_bzrdir):
1522
 
        """Create a branch of this format in a_bzrdir."""
 
1670
    def initialize(self, a_bzrdir, name=None):
 
1671
        """Create a branch of this format in a_bzrdir.
 
1672
        
 
1673
        :param name: Name of the colocated branch to create.
 
1674
        """
1523
1675
        raise NotImplementedError(self.initialize)
1524
1676
 
1525
1677
    def is_supported(self):
1555
1707
        """
1556
1708
        raise NotImplementedError(self.network_name)
1557
1709
 
1558
 
    def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
 
1710
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
1559
1711
        """Return the branch object for a_bzrdir
1560
1712
 
1561
1713
        :param a_bzrdir: A BzrDir that contains a branch.
 
1714
        :param name: Name of colocated branch to open
1562
1715
        :param _found: a private parameter, do not use it. It is used to
1563
1716
            indicate if format probing has already be done.
1564
1717
        :param ignore_fallbacks: when set, no fallback branches will be opened
1568
1721
 
1569
1722
    @classmethod
1570
1723
    def register_format(klass, format):
1571
 
        """Register a metadir format."""
 
1724
        """Register a metadir format.
 
1725
        
 
1726
        See MetaDirBranchFormatFactory for the ability to register a format
 
1727
        without loading the code the format needs until it is actually used.
 
1728
        """
1572
1729
        klass._formats[format.get_format_string()] = format
1573
1730
        # Metadir formats have a network name of their format string, and get
1574
 
        # registered as class factories.
1575
 
        network_format_registry.register(format.get_format_string(), format.__class__)
 
1731
        # registered as factories.
 
1732
        if isinstance(format, MetaDirBranchFormatFactory):
 
1733
            network_format_registry.register(format.get_format_string(), format)
 
1734
        else:
 
1735
            network_format_registry.register(format.get_format_string(),
 
1736
                format.__class__)
1576
1737
 
1577
1738
    @classmethod
1578
1739
    def set_default_format(klass, format):
1598
1759
        return False  # by default
1599
1760
 
1600
1761
 
 
1762
class MetaDirBranchFormatFactory(registry._LazyObjectGetter):
 
1763
    """A factory for a BranchFormat object, permitting simple lazy registration.
 
1764
    
 
1765
    While none of the built in BranchFormats are lazy registered yet,
 
1766
    bzrlib.tests.test_branch.TestMetaDirBranchFormatFactory demonstrates how to
 
1767
    use it, and the bzr-loom plugin uses it as well (see
 
1768
    bzrlib.plugins.loom.formats).
 
1769
    """
 
1770
 
 
1771
    def __init__(self, format_string, module_name, member_name):
 
1772
        """Create a MetaDirBranchFormatFactory.
 
1773
 
 
1774
        :param format_string: The format string the format has.
 
1775
        :param module_name: Module to load the format class from.
 
1776
        :param member_name: Attribute name within the module for the format class.
 
1777
        """
 
1778
        registry._LazyObjectGetter.__init__(self, module_name, member_name)
 
1779
        self._format_string = format_string
 
1780
        
 
1781
    def get_format_string(self):
 
1782
        """See BranchFormat.get_format_string."""
 
1783
        return self._format_string
 
1784
 
 
1785
    def __call__(self):
 
1786
        """Used for network_format_registry support."""
 
1787
        return self.get_obj()()
 
1788
 
 
1789
 
1601
1790
class BranchHooks(Hooks):
1602
1791
    """A dictionary mapping hook name to a list of callables for branch hooks.
1603
1792
 
1672
1861
            "multiple hooks installed for transform_fallback_location, "
1673
1862
            "all are called with the url returned from the previous hook."
1674
1863
            "The order is however undefined.", (1, 9), None))
 
1864
        self.create_hook(HookPoint('automatic_tag_name',
 
1865
            "Called to determine an automatic tag name for a revision. "
 
1866
            "automatic_tag_name is called with (branch, revision_id) and "
 
1867
            "should return a tag name or None if no tag name could be "
 
1868
            "determined. The first non-None tag name returned will be used.",
 
1869
            (2, 2), None))
 
1870
        self.create_hook(HookPoint('post_branch_init',
 
1871
            "Called after new branch initialization completes. "
 
1872
            "post_branch_init is called with a "
 
1873
            "bzrlib.branch.BranchInitHookParams. "
 
1874
            "Note that init, branch and checkout (both heavyweight and "
 
1875
            "lightweight) will all trigger this hook.", (2, 2), None))
 
1876
        self.create_hook(HookPoint('post_switch',
 
1877
            "Called after a checkout switches branch. "
 
1878
            "post_switch is called with a "
 
1879
            "bzrlib.branch.SwitchHookParams.", (2, 2), None))
 
1880
 
1675
1881
 
1676
1882
 
1677
1883
# install the default hooks into the Branch class.
1716
1922
            self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1717
1923
 
1718
1924
 
 
1925
class BranchInitHookParams(object):
 
1926
    """Object holding parameters passed to *_branch_init hooks.
 
1927
 
 
1928
    There are 4 fields that hooks may wish to access:
 
1929
 
 
1930
    :ivar format: the branch format
 
1931
    :ivar bzrdir: the BzrDir where the branch will be/has been initialized
 
1932
    :ivar name: name of colocated branch, if any (or None)
 
1933
    :ivar branch: the branch created
 
1934
 
 
1935
    Note that for lightweight checkouts, the bzrdir and format fields refer to
 
1936
    the checkout, hence they are different from the corresponding fields in
 
1937
    branch, which refer to the original branch.
 
1938
    """
 
1939
 
 
1940
    def __init__(self, format, a_bzrdir, name, branch):
 
1941
        """Create a group of BranchInitHook parameters.
 
1942
 
 
1943
        :param format: the branch format
 
1944
        :param a_bzrdir: the BzrDir where the branch will be/has been
 
1945
            initialized
 
1946
        :param name: name of colocated branch, if any (or None)
 
1947
        :param branch: the branch created
 
1948
 
 
1949
        Note that for lightweight checkouts, the bzrdir and format fields refer
 
1950
        to the checkout, hence they are different from the corresponding fields
 
1951
        in branch, which refer to the original branch.
 
1952
        """
 
1953
        self.format = format
 
1954
        self.bzrdir = a_bzrdir
 
1955
        self.name = name
 
1956
        self.branch = branch
 
1957
 
 
1958
    def __eq__(self, other):
 
1959
        return self.__dict__ == other.__dict__
 
1960
 
 
1961
    def __repr__(self):
 
1962
        return "<%s of %s>" % (self.__class__.__name__, self.branch)
 
1963
 
 
1964
 
 
1965
class SwitchHookParams(object):
 
1966
    """Object holding parameters passed to *_switch hooks.
 
1967
 
 
1968
    There are 4 fields that hooks may wish to access:
 
1969
 
 
1970
    :ivar control_dir: BzrDir of the checkout to change
 
1971
    :ivar to_branch: branch that the checkout is to reference
 
1972
    :ivar force: skip the check for local commits in a heavy checkout
 
1973
    :ivar revision_id: revision ID to switch to (or None)
 
1974
    """
 
1975
 
 
1976
    def __init__(self, control_dir, to_branch, force, revision_id):
 
1977
        """Create a group of SwitchHook parameters.
 
1978
 
 
1979
        :param control_dir: BzrDir of the checkout to change
 
1980
        :param to_branch: branch that the checkout is to reference
 
1981
        :param force: skip the check for local commits in a heavy checkout
 
1982
        :param revision_id: revision ID to switch to (or None)
 
1983
        """
 
1984
        self.control_dir = control_dir
 
1985
        self.to_branch = to_branch
 
1986
        self.force = force
 
1987
        self.revision_id = revision_id
 
1988
 
 
1989
    def __eq__(self, other):
 
1990
        return self.__dict__ == other.__dict__
 
1991
 
 
1992
    def __repr__(self):
 
1993
        return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
 
1994
            self.control_dir, self.to_branch,
 
1995
            self.revision_id)
 
1996
 
 
1997
 
1719
1998
class BzrBranchFormat4(BranchFormat):
1720
1999
    """Bzr branch format 4.
1721
2000
 
1728
2007
        """See BranchFormat.get_format_description()."""
1729
2008
        return "Branch format 4"
1730
2009
 
1731
 
    def initialize(self, a_bzrdir):
 
2010
    def initialize(self, a_bzrdir, name=None):
1732
2011
        """Create a branch of this format in a_bzrdir."""
1733
2012
        utf8_files = [('revision-history', ''),
1734
2013
                      ('branch-name', ''),
1735
2014
                      ]
1736
 
        return self._initialize_helper(a_bzrdir, utf8_files,
 
2015
        return self._initialize_helper(a_bzrdir, utf8_files, name=name,
1737
2016
                                       lock_type='branch4', set_format=False)
1738
2017
 
1739
2018
    def __init__(self):
1744
2023
        """The network name for this format is the control dirs disk label."""
1745
2024
        return self._matchingbzrdir.get_format_string()
1746
2025
 
1747
 
    def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
 
2026
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
1748
2027
        """See BranchFormat.open()."""
1749
2028
        if not _found:
1750
2029
            # we are being called directly and must probe.
1752
2031
        return BzrBranch(_format=self,
1753
2032
                         _control_files=a_bzrdir._control_files,
1754
2033
                         a_bzrdir=a_bzrdir,
 
2034
                         name=name,
1755
2035
                         _repository=a_bzrdir.open_repository())
1756
2036
 
1757
2037
    def __str__(self):
1772
2052
        """
1773
2053
        return self.get_format_string()
1774
2054
 
1775
 
    def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
 
2055
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
1776
2056
        """See BranchFormat.open()."""
1777
2057
        if not _found:
1778
 
            format = BranchFormat.find_format(a_bzrdir)
 
2058
            format = BranchFormat.find_format(a_bzrdir, name=name)
1779
2059
            if format.__class__ != self.__class__:
1780
2060
                raise AssertionError("wrong format %r found for %r" %
1781
2061
                    (format, self))
 
2062
        transport = a_bzrdir.get_branch_transport(None, name=name)
1782
2063
        try:
1783
 
            transport = a_bzrdir.get_branch_transport(None)
1784
2064
            control_files = lockable_files.LockableFiles(transport, 'lock',
1785
2065
                                                         lockdir.LockDir)
1786
2066
            return self._branch_class()(_format=self,
1787
2067
                              _control_files=control_files,
 
2068
                              name=name,
1788
2069
                              a_bzrdir=a_bzrdir,
1789
2070
                              _repository=a_bzrdir.find_repository(),
1790
2071
                              ignore_fallbacks=ignore_fallbacks)
1791
2072
        except errors.NoSuchFile:
1792
 
            raise errors.NotBranchError(path=transport.base)
 
2073
            raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1793
2074
 
1794
2075
    def __init__(self):
1795
2076
        super(BranchFormatMetadir, self).__init__()
1824
2105
        """See BranchFormat.get_format_description()."""
1825
2106
        return "Branch format 5"
1826
2107
 
1827
 
    def initialize(self, a_bzrdir):
 
2108
    def initialize(self, a_bzrdir, name=None):
1828
2109
        """Create a branch of this format in a_bzrdir."""
1829
2110
        utf8_files = [('revision-history', ''),
1830
2111
                      ('branch-name', ''),
1831
2112
                      ]
1832
 
        return self._initialize_helper(a_bzrdir, utf8_files)
 
2113
        return self._initialize_helper(a_bzrdir, utf8_files, name)
1833
2114
 
1834
2115
    def supports_tags(self):
1835
2116
        return False
1857
2138
        """See BranchFormat.get_format_description()."""
1858
2139
        return "Branch format 6"
1859
2140
 
1860
 
    def initialize(self, a_bzrdir):
 
2141
    def initialize(self, a_bzrdir, name=None):
1861
2142
        """Create a branch of this format in a_bzrdir."""
1862
2143
        utf8_files = [('last-revision', '0 null:\n'),
1863
2144
                      ('branch.conf', ''),
1864
2145
                      ('tags', ''),
1865
2146
                      ]
1866
 
        return self._initialize_helper(a_bzrdir, utf8_files)
 
2147
        return self._initialize_helper(a_bzrdir, utf8_files, name)
1867
2148
 
1868
2149
    def make_tags(self, branch):
1869
2150
        """See bzrlib.branch.BranchFormat.make_tags()."""
1887
2168
        """See BranchFormat.get_format_description()."""
1888
2169
        return "Branch format 8"
1889
2170
 
1890
 
    def initialize(self, a_bzrdir):
 
2171
    def initialize(self, a_bzrdir, name=None):
1891
2172
        """Create a branch of this format in a_bzrdir."""
1892
2173
        utf8_files = [('last-revision', '0 null:\n'),
1893
2174
                      ('branch.conf', ''),
1894
2175
                      ('tags', ''),
1895
2176
                      ('references', '')
1896
2177
                      ]
1897
 
        return self._initialize_helper(a_bzrdir, utf8_files)
 
2178
        return self._initialize_helper(a_bzrdir, utf8_files, name)
1898
2179
 
1899
2180
    def __init__(self):
1900
2181
        super(BzrBranchFormat8, self).__init__()
1923
2204
    This format was introduced in bzr 1.6.
1924
2205
    """
1925
2206
 
1926
 
    def initialize(self, a_bzrdir):
 
2207
    def initialize(self, a_bzrdir, name=None):
1927
2208
        """Create a branch of this format in a_bzrdir."""
1928
2209
        utf8_files = [('last-revision', '0 null:\n'),
1929
2210
                      ('branch.conf', ''),
1930
2211
                      ('tags', ''),
1931
2212
                      ]
1932
 
        return self._initialize_helper(a_bzrdir, utf8_files)
 
2213
        return self._initialize_helper(a_bzrdir, utf8_files, name)
1933
2214
 
1934
2215
    def _branch_class(self):
1935
2216
        return BzrBranch7
1967
2248
        """See BranchFormat.get_format_description()."""
1968
2249
        return "Checkout reference format 1"
1969
2250
 
1970
 
    def get_reference(self, a_bzrdir):
 
2251
    def get_reference(self, a_bzrdir, name=None):
1971
2252
        """See BranchFormat.get_reference()."""
1972
 
        transport = a_bzrdir.get_branch_transport(None)
1973
 
        return transport.get('location').read()
 
2253
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
2254
        return transport.get_bytes('location')
1974
2255
 
1975
 
    def set_reference(self, a_bzrdir, to_branch):
 
2256
    def set_reference(self, a_bzrdir, name, to_branch):
1976
2257
        """See BranchFormat.set_reference()."""
1977
 
        transport = a_bzrdir.get_branch_transport(None)
 
2258
        transport = a_bzrdir.get_branch_transport(None, name=name)
1978
2259
        location = transport.put_bytes('location', to_branch.base)
1979
2260
 
1980
 
    def initialize(self, a_bzrdir, target_branch=None):
 
2261
    def initialize(self, a_bzrdir, name=None, target_branch=None):
1981
2262
        """Create a branch of this format in a_bzrdir."""
1982
2263
        if target_branch is None:
1983
2264
            # this format does not implement branch itself, thus the implicit
1984
2265
            # creation contract must see it as uninitializable
1985
2266
            raise errors.UninitializableFormat(self)
1986
 
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
1987
 
        branch_transport = a_bzrdir.get_branch_transport(self)
 
2267
        mutter('creating branch reference in %s', a_bzrdir.user_url)
 
2268
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
1988
2269
        branch_transport.put_bytes('location',
1989
 
            target_branch.bzrdir.root_transport.base)
 
2270
            target_branch.bzrdir.user_url)
1990
2271
        branch_transport.put_bytes('format', self.get_format_string())
1991
 
        return self.open(
1992
 
            a_bzrdir, _found=True,
 
2272
        branch = self.open(
 
2273
            a_bzrdir, name, _found=True,
1993
2274
            possible_transports=[target_branch.bzrdir.root_transport])
 
2275
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
2276
        return branch
1994
2277
 
1995
2278
    def __init__(self):
1996
2279
        super(BranchReferenceFormat, self).__init__()
2002
2285
        def clone(to_bzrdir, revision_id=None,
2003
2286
            repository_policy=None):
2004
2287
            """See Branch.clone()."""
2005
 
            return format.initialize(to_bzrdir, a_branch)
 
2288
            return format.initialize(to_bzrdir, target_branch=a_branch)
2006
2289
            # cannot obey revision_id limits when cloning a reference ...
2007
2290
            # FIXME RBC 20060210 either nuke revision_id for clone, or
2008
2291
            # emit some sort of warning/error to the caller ?!
2009
2292
        return clone
2010
2293
 
2011
 
    def open(self, a_bzrdir, _found=False, location=None,
 
2294
    def open(self, a_bzrdir, name=None, _found=False, location=None,
2012
2295
             possible_transports=None, ignore_fallbacks=False):
2013
2296
        """Return the branch that the branch reference in a_bzrdir points at.
2014
2297
 
2015
2298
        :param a_bzrdir: A BzrDir that contains a branch.
 
2299
        :param name: Name of colocated branch to open, if any
2016
2300
        :param _found: a private parameter, do not use it. It is used to
2017
2301
            indicate if format probing has already be done.
2018
2302
        :param ignore_fallbacks: when set, no fallback branches will be opened
2023
2307
        :param possible_transports: An optional reusable transports list.
2024
2308
        """
2025
2309
        if not _found:
2026
 
            format = BranchFormat.find_format(a_bzrdir)
 
2310
            format = BranchFormat.find_format(a_bzrdir, name=name)
2027
2311
            if format.__class__ != self.__class__:
2028
2312
                raise AssertionError("wrong format %r found for %r" %
2029
2313
                    (format, self))
2030
2314
        if location is None:
2031
 
            location = self.get_reference(a_bzrdir)
 
2315
            location = self.get_reference(a_bzrdir, name)
2032
2316
        real_bzrdir = bzrdir.BzrDir.open(
2033
2317
            location, possible_transports=possible_transports)
2034
 
        result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
 
2318
        result = real_bzrdir.open_branch(name=name, 
 
2319
            ignore_fallbacks=ignore_fallbacks)
2035
2320
        # this changes the behaviour of result.clone to create a new reference
2036
2321
        # rather than a copy of the content of the branch.
2037
2322
        # I did not use a proxy object because that needs much more extensive
2064
2349
BranchFormat.register_format(__format6)
2065
2350
BranchFormat.register_format(__format7)
2066
2351
BranchFormat.register_format(__format8)
2067
 
BranchFormat.set_default_format(__format6)
 
2352
BranchFormat.set_default_format(__format7)
2068
2353
_legacy_formats = [BzrBranchFormat4(),
2069
2354
    ]
2070
2355
network_format_registry.register(
2071
2356
    _legacy_formats[0].network_name(), _legacy_formats[0].__class__)
2072
2357
 
2073
2358
 
2074
 
class BzrBranch(Branch):
 
2359
class BranchWriteLockResult(LogicalLockResult):
 
2360
    """The result of write locking a branch.
 
2361
 
 
2362
    :ivar branch_token: The token obtained from the underlying branch lock, or
 
2363
        None.
 
2364
    :ivar unlock: A callable which will unlock the lock.
 
2365
    """
 
2366
 
 
2367
    def __init__(self, unlock, branch_token):
 
2368
        LogicalLockResult.__init__(self, unlock)
 
2369
        self.branch_token = branch_token
 
2370
 
 
2371
    def __repr__(self):
 
2372
        return "BranchWriteLockResult(%s, %s)" % (self.branch_token,
 
2373
            self.unlock)
 
2374
 
 
2375
 
 
2376
class BzrBranch(Branch, _RelockDebugMixin):
2075
2377
    """A branch stored in the actual filesystem.
2076
2378
 
2077
2379
    Note that it's "local" in the context of the filesystem; it doesn't
2083
2385
    :ivar repository: Repository for this branch.
2084
2386
    :ivar base: The url of the base directory for this branch; the one
2085
2387
        containing the .bzr directory.
 
2388
    :ivar name: Optional colocated branch name as it exists in the control
 
2389
        directory.
2086
2390
    """
2087
2391
 
2088
2392
    def __init__(self, _format=None,
2089
 
                 _control_files=None, a_bzrdir=None, _repository=None,
2090
 
                 ignore_fallbacks=False):
 
2393
                 _control_files=None, a_bzrdir=None, name=None,
 
2394
                 _repository=None, ignore_fallbacks=False):
2091
2395
        """Create new branch object at a particular location."""
2092
2396
        if a_bzrdir is None:
2093
2397
            raise ValueError('a_bzrdir must be supplied')
2094
2398
        else:
2095
2399
            self.bzrdir = a_bzrdir
2096
2400
        self._base = self.bzrdir.transport.clone('..').base
 
2401
        self.name = name
2097
2402
        # XXX: We should be able to just do
2098
2403
        #   self.base = self.bzrdir.root_transport.base
2099
2404
        # but this does not quite work yet -- mbp 20080522
2106
2411
        Branch.__init__(self)
2107
2412
 
2108
2413
    def __str__(self):
2109
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
2414
        if self.name is None:
 
2415
            return '%s(%s)' % (self.__class__.__name__, self.user_url)
 
2416
        else:
 
2417
            return '%s(%s,%s)' % (self.__class__.__name__, self.user_url,
 
2418
                self.name)
2110
2419
 
2111
2420
    __repr__ = __str__
2112
2421
 
2123
2432
        return self.control_files.is_locked()
2124
2433
 
2125
2434
    def lock_write(self, token=None):
 
2435
        """Lock the branch for write operations.
 
2436
 
 
2437
        :param token: A token to permit reacquiring a previously held and
 
2438
            preserved lock.
 
2439
        :return: A BranchWriteLockResult.
 
2440
        """
 
2441
        if not self.is_locked():
 
2442
            self._note_lock('w')
2126
2443
        # All-in-one needs to always unlock/lock.
2127
2444
        repo_control = getattr(self.repository, 'control_files', None)
2128
2445
        if self.control_files == repo_control or not self.is_locked():
 
2446
            self.repository._warn_if_deprecated(self)
2129
2447
            self.repository.lock_write()
2130
2448
            took_lock = True
2131
2449
        else:
2132
2450
            took_lock = False
2133
2451
        try:
2134
 
            return self.control_files.lock_write(token=token)
 
2452
            return BranchWriteLockResult(self.unlock,
 
2453
                self.control_files.lock_write(token=token))
2135
2454
        except:
2136
2455
            if took_lock:
2137
2456
                self.repository.unlock()
2138
2457
            raise
2139
2458
 
2140
2459
    def lock_read(self):
 
2460
        """Lock the branch for read operations.
 
2461
 
 
2462
        :return: A bzrlib.lock.LogicalLockResult.
 
2463
        """
 
2464
        if not self.is_locked():
 
2465
            self._note_lock('r')
2141
2466
        # All-in-one needs to always unlock/lock.
2142
2467
        repo_control = getattr(self.repository, 'control_files', None)
2143
2468
        if self.control_files == repo_control or not self.is_locked():
 
2469
            self.repository._warn_if_deprecated(self)
2144
2470
            self.repository.lock_read()
2145
2471
            took_lock = True
2146
2472
        else:
2147
2473
            took_lock = False
2148
2474
        try:
2149
2475
            self.control_files.lock_read()
 
2476
            return LogicalLockResult(self.unlock)
2150
2477
        except:
2151
2478
            if took_lock:
2152
2479
                self.repository.unlock()
2153
2480
            raise
2154
2481
 
 
2482
    @only_raises(errors.LockNotHeld, errors.LockBroken)
2155
2483
    def unlock(self):
2156
2484
        try:
2157
2485
            self.control_files.unlock()
2320
2648
        return result
2321
2649
 
2322
2650
    def get_stacked_on_url(self):
2323
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2651
        raise errors.UnstackableBranchFormat(self._format, self.user_url)
2324
2652
 
2325
2653
    def set_push_location(self, location):
2326
2654
        """See Branch.set_push_location."""
2516
2844
        if _mod_revision.is_null(last_revision):
2517
2845
            return
2518
2846
        if last_revision not in self._lefthand_history(revision_id):
2519
 
            raise errors.AppendRevisionsOnlyViolation(self.base)
 
2847
            raise errors.AppendRevisionsOnlyViolation(self.user_url)
2520
2848
 
2521
2849
    def _gen_revision_history(self):
2522
2850
        """Generate the revision history from last revision
2622
2950
        if branch_location is None:
2623
2951
            return Branch.reference_parent(self, file_id, path,
2624
2952
                                           possible_transports)
2625
 
        branch_location = urlutils.join(self.base, branch_location)
 
2953
        branch_location = urlutils.join(self.user_url, branch_location)
2626
2954
        return Branch.open(branch_location,
2627
2955
                           possible_transports=possible_transports)
2628
2956
 
2674
3002
        return stacked_url
2675
3003
 
2676
3004
    def _get_append_revisions_only(self):
2677
 
        value = self.get_config().get_user_option('append_revisions_only')
2678
 
        return value == 'True'
 
3005
        return self.get_config(
 
3006
            ).get_user_option_as_bool('append_revisions_only')
2679
3007
 
2680
3008
    @needs_write_lock
2681
3009
    def generate_revision_history(self, revision_id, last_rev=None,
2743
3071
    """
2744
3072
 
2745
3073
    def get_stacked_on_url(self):
2746
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
3074
        raise errors.UnstackableBranchFormat(self._format, self.user_url)
2747
3075
 
2748
3076
 
2749
3077
######################################################################
2836
3164
        :param verbose: Requests more detailed display of what was checked,
2837
3165
            if any.
2838
3166
        """
2839
 
        note('checked branch %s format %s', self.branch.base,
 
3167
        note('checked branch %s format %s', self.branch.user_url,
2840
3168
            self.branch._format)
2841
3169
        for error in self.errors:
2842
3170
            note('found error:%s', error)
2937
3265
    _optimisers = []
2938
3266
    """The available optimised InterBranch types."""
2939
3267
 
2940
 
    @staticmethod
2941
 
    def _get_branch_formats_to_test():
2942
 
        """Return a tuple with the Branch formats to use when testing."""
2943
 
        raise NotImplementedError(InterBranch._get_branch_formats_to_test)
 
3268
    @classmethod
 
3269
    def _get_branch_formats_to_test(klass):
 
3270
        """Return an iterable of format tuples for testing.
 
3271
        
 
3272
        :return: An iterable of (from_format, to_format) to use when testing
 
3273
            this InterBranch class. Each InterBranch class should define this
 
3274
            method itself.
 
3275
        """
 
3276
        raise NotImplementedError(klass._get_branch_formats_to_test)
2944
3277
 
 
3278
    @needs_write_lock
2945
3279
    def pull(self, overwrite=False, stop_revision=None,
2946
3280
             possible_transports=None, local=False):
2947
3281
        """Mirror source into target branch.
2952
3286
        """
2953
3287
        raise NotImplementedError(self.pull)
2954
3288
 
 
3289
    @needs_write_lock
2955
3290
    def update_revisions(self, stop_revision=None, overwrite=False,
2956
3291
                         graph=None):
2957
3292
        """Pull in new perfect-fit revisions.
2965
3300
        """
2966
3301
        raise NotImplementedError(self.update_revisions)
2967
3302
 
 
3303
    @needs_write_lock
2968
3304
    def push(self, overwrite=False, stop_revision=None,
2969
3305
             _override_hook_source_branch=None):
2970
3306
        """Mirror the source branch into the target branch.
2973
3309
        """
2974
3310
        raise NotImplementedError(self.push)
2975
3311
 
 
3312
    @needs_write_lock
 
3313
    def copy_content_into(self, revision_id=None):
 
3314
        """Copy the content of source into target
 
3315
 
 
3316
        revision_id: if not None, the revision history in the new branch will
 
3317
                     be truncated to end with revision_id.
 
3318
        """
 
3319
        raise NotImplementedError(self.copy_content_into)
 
3320
 
2976
3321
 
2977
3322
class GenericInterBranch(InterBranch):
2978
 
    """InterBranch implementation that uses public Branch functions.
2979
 
    """
2980
 
 
2981
 
    @staticmethod
2982
 
    def _get_branch_formats_to_test():
2983
 
        return BranchFormat._default_format, BranchFormat._default_format
2984
 
 
 
3323
    """InterBranch implementation that uses public Branch functions."""
 
3324
 
 
3325
    @classmethod
 
3326
    def is_compatible(klass, source, target):
 
3327
        # GenericBranch uses the public API, so always compatible
 
3328
        return True
 
3329
 
 
3330
    @classmethod
 
3331
    def _get_branch_formats_to_test(klass):
 
3332
        return [(BranchFormat._default_format, BranchFormat._default_format)]
 
3333
 
 
3334
    @classmethod
 
3335
    def unwrap_format(klass, format):
 
3336
        if isinstance(format, remote.RemoteBranchFormat):
 
3337
            format._ensure_real()
 
3338
            return format._custom_format
 
3339
        return format                                                                                                  
 
3340
 
 
3341
    @needs_write_lock
 
3342
    def copy_content_into(self, revision_id=None):
 
3343
        """Copy the content of source into target
 
3344
 
 
3345
        revision_id: if not None, the revision history in the new branch will
 
3346
                     be truncated to end with revision_id.
 
3347
        """
 
3348
        self.source.update_references(self.target)
 
3349
        self.source._synchronize_history(self.target, revision_id)
 
3350
        try:
 
3351
            parent = self.source.get_parent()
 
3352
        except errors.InaccessibleParent, e:
 
3353
            mutter('parent was not accessible to copy: %s', e)
 
3354
        else:
 
3355
            if parent:
 
3356
                self.target.set_parent(parent)
 
3357
        if self.source._push_should_merge_tags():
 
3358
            self.source.tags.merge_to(self.target.tags)
 
3359
 
 
3360
    @needs_write_lock
2985
3361
    def update_revisions(self, stop_revision=None, overwrite=False,
2986
3362
        graph=None):
2987
3363
        """See InterBranch.update_revisions()."""
2988
 
        self.source.lock_read()
2989
 
        try:
2990
 
            other_revno, other_last_revision = self.source.last_revision_info()
2991
 
            stop_revno = None # unknown
2992
 
            if stop_revision is None:
2993
 
                stop_revision = other_last_revision
2994
 
                if _mod_revision.is_null(stop_revision):
2995
 
                    # if there are no commits, we're done.
2996
 
                    return
2997
 
                stop_revno = other_revno
2998
 
 
2999
 
            # what's the current last revision, before we fetch [and change it
3000
 
            # possibly]
3001
 
            last_rev = _mod_revision.ensure_null(self.target.last_revision())
3002
 
            # we fetch here so that we don't process data twice in the common
3003
 
            # case of having something to pull, and so that the check for
3004
 
            # already merged can operate on the just fetched graph, which will
3005
 
            # be cached in memory.
3006
 
            self.target.fetch(self.source, stop_revision)
3007
 
            # Check to see if one is an ancestor of the other
3008
 
            if not overwrite:
3009
 
                if graph is None:
3010
 
                    graph = self.target.repository.get_graph()
3011
 
                if self.target._check_if_descendant_or_diverged(
3012
 
                        stop_revision, last_rev, graph, self.source):
3013
 
                    # stop_revision is a descendant of last_rev, but we aren't
3014
 
                    # overwriting, so we're done.
3015
 
                    return
3016
 
            if stop_revno is None:
3017
 
                if graph is None:
3018
 
                    graph = self.target.repository.get_graph()
3019
 
                this_revno, this_last_revision = \
3020
 
                        self.target.last_revision_info()
3021
 
                stop_revno = graph.find_distance_to_null(stop_revision,
3022
 
                                [(other_last_revision, other_revno),
3023
 
                                 (this_last_revision, this_revno)])
3024
 
            self.target.set_last_revision_info(stop_revno, stop_revision)
3025
 
        finally:
3026
 
            self.source.unlock()
3027
 
 
 
3364
        other_revno, other_last_revision = self.source.last_revision_info()
 
3365
        stop_revno = None # unknown
 
3366
        if stop_revision is None:
 
3367
            stop_revision = other_last_revision
 
3368
            if _mod_revision.is_null(stop_revision):
 
3369
                # if there are no commits, we're done.
 
3370
                return
 
3371
            stop_revno = other_revno
 
3372
 
 
3373
        # what's the current last revision, before we fetch [and change it
 
3374
        # possibly]
 
3375
        last_rev = _mod_revision.ensure_null(self.target.last_revision())
 
3376
        # we fetch here so that we don't process data twice in the common
 
3377
        # case of having something to pull, and so that the check for
 
3378
        # already merged can operate on the just fetched graph, which will
 
3379
        # be cached in memory.
 
3380
        self.target.fetch(self.source, stop_revision)
 
3381
        # Check to see if one is an ancestor of the other
 
3382
        if not overwrite:
 
3383
            if graph is None:
 
3384
                graph = self.target.repository.get_graph()
 
3385
            if self.target._check_if_descendant_or_diverged(
 
3386
                    stop_revision, last_rev, graph, self.source):
 
3387
                # stop_revision is a descendant of last_rev, but we aren't
 
3388
                # overwriting, so we're done.
 
3389
                return
 
3390
        if stop_revno is None:
 
3391
            if graph is None:
 
3392
                graph = self.target.repository.get_graph()
 
3393
            this_revno, this_last_revision = \
 
3394
                    self.target.last_revision_info()
 
3395
            stop_revno = graph.find_distance_to_null(stop_revision,
 
3396
                            [(other_last_revision, other_revno),
 
3397
                             (this_last_revision, this_revno)])
 
3398
        self.target.set_last_revision_info(stop_revno, stop_revision)
 
3399
 
 
3400
    @needs_write_lock
3028
3401
    def pull(self, overwrite=False, stop_revision=None,
3029
 
             possible_transports=None, _hook_master=None, run_hooks=True,
 
3402
             possible_transports=None, run_hooks=True,
3030
3403
             _override_hook_target=None, local=False):
3031
 
        """See Branch.pull.
 
3404
        """Pull from source into self, updating my master if any.
3032
3405
 
3033
 
        :param _hook_master: Private parameter - set the branch to
3034
 
            be supplied as the master to pull hooks.
3035
3406
        :param run_hooks: Private parameter - if false, this branch
3036
3407
            is being called because it's the master of the primary branch,
3037
3408
            so it should not run its hooks.
3038
 
        :param _override_hook_target: Private parameter - set the branch to be
3039
 
            supplied as the target_branch to pull hooks.
3040
 
        :param local: Only update the local branch, and not the bound branch.
3041
3409
        """
3042
 
        # This type of branch can't be bound.
3043
 
        if local:
 
3410
        bound_location = self.target.get_bound_location()
 
3411
        if local and not bound_location:
3044
3412
            raise errors.LocalRequiresBoundBranch()
3045
 
        result = PullResult()
3046
 
        result.source_branch = self.source
3047
 
        if _override_hook_target is None:
3048
 
            result.target_branch = self.target
3049
 
        else:
3050
 
            result.target_branch = _override_hook_target
3051
 
        self.source.lock_read()
 
3413
        master_branch = None
 
3414
        if not local and bound_location and self.source.user_url != bound_location:
 
3415
            # not pulling from master, so we need to update master.
 
3416
            master_branch = self.target.get_master_branch(possible_transports)
 
3417
            master_branch.lock_write()
3052
3418
        try:
3053
 
            # We assume that during 'pull' the target repository is closer than
3054
 
            # the source one.
3055
 
            self.source.update_references(self.target)
3056
 
            graph = self.target.repository.get_graph(self.source.repository)
3057
 
            # TODO: Branch formats should have a flag that indicates 
3058
 
            # that revno's are expensive, and pull() should honor that flag.
3059
 
            # -- JRV20090506
3060
 
            result.old_revno, result.old_revid = \
3061
 
                self.target.last_revision_info()
3062
 
            self.target.update_revisions(self.source, stop_revision,
3063
 
                overwrite=overwrite, graph=graph)
3064
 
            # TODO: The old revid should be specified when merging tags, 
3065
 
            # so a tags implementation that versions tags can only 
3066
 
            # pull in the most recent changes. -- JRV20090506
3067
 
            result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
3068
 
                overwrite)
3069
 
            result.new_revno, result.new_revid = self.target.last_revision_info()
3070
 
            if _hook_master:
3071
 
                result.master_branch = _hook_master
3072
 
                result.local_branch = result.target_branch
3073
 
            else:
3074
 
                result.master_branch = result.target_branch
3075
 
                result.local_branch = None
3076
 
            if run_hooks:
3077
 
                for hook in Branch.hooks['post_pull']:
3078
 
                    hook(result)
 
3419
            if master_branch:
 
3420
                # pull from source into master.
 
3421
                master_branch.pull(self.source, overwrite, stop_revision,
 
3422
                    run_hooks=False)
 
3423
            return self._pull(overwrite,
 
3424
                stop_revision, _hook_master=master_branch,
 
3425
                run_hooks=run_hooks,
 
3426
                _override_hook_target=_override_hook_target)
3079
3427
        finally:
3080
 
            self.source.unlock()
3081
 
        return result
 
3428
            if master_branch:
 
3429
                master_branch.unlock()
3082
3430
 
3083
3431
    def push(self, overwrite=False, stop_revision=None,
3084
3432
             _override_hook_source_branch=None):
3146
3494
            _run_hooks()
3147
3495
            return result
3148
3496
 
3149
 
    @classmethod
3150
 
    def is_compatible(self, source, target):
3151
 
        # GenericBranch uses the public API, so always compatible
3152
 
        return True
3153
 
 
3154
 
 
3155
 
class InterToBranch5(GenericInterBranch):
3156
 
 
3157
 
    @staticmethod
3158
 
    def _get_branch_formats_to_test():
3159
 
        return BranchFormat._default_format, BzrBranchFormat5()
3160
 
 
3161
 
    def pull(self, overwrite=False, stop_revision=None,
3162
 
             possible_transports=None, run_hooks=True,
 
3497
    def _pull(self, overwrite=False, stop_revision=None,
 
3498
             possible_transports=None, _hook_master=None, run_hooks=True,
3163
3499
             _override_hook_target=None, local=False):
3164
 
        """Pull from source into self, updating my master if any.
3165
 
 
 
3500
        """See Branch.pull.
 
3501
 
 
3502
        This function is the core worker, used by GenericInterBranch.pull to
 
3503
        avoid duplication when pulling source->master and source->local.
 
3504
 
 
3505
        :param _hook_master: Private parameter - set the branch to
 
3506
            be supplied as the master to pull hooks.
3166
3507
        :param run_hooks: Private parameter - if false, this branch
3167
3508
            is being called because it's the master of the primary branch,
3168
3509
            so it should not run its hooks.
 
3510
        :param _override_hook_target: Private parameter - set the branch to be
 
3511
            supplied as the target_branch to pull hooks.
 
3512
        :param local: Only update the local branch, and not the bound branch.
3169
3513
        """
3170
 
        bound_location = self.target.get_bound_location()
3171
 
        if local and not bound_location:
 
3514
        # This type of branch can't be bound.
 
3515
        if local:
3172
3516
            raise errors.LocalRequiresBoundBranch()
3173
 
        master_branch = None
3174
 
        if not local and bound_location and self.source.base != bound_location:
3175
 
            # not pulling from master, so we need to update master.
3176
 
            master_branch = self.target.get_master_branch(possible_transports)
3177
 
            master_branch.lock_write()
 
3517
        result = PullResult()
 
3518
        result.source_branch = self.source
 
3519
        if _override_hook_target is None:
 
3520
            result.target_branch = self.target
 
3521
        else:
 
3522
            result.target_branch = _override_hook_target
 
3523
        self.source.lock_read()
3178
3524
        try:
3179
 
            if master_branch:
3180
 
                # pull from source into master.
3181
 
                master_branch.pull(self.source, overwrite, stop_revision,
3182
 
                    run_hooks=False)
3183
 
            return super(InterToBranch5, self).pull(overwrite,
3184
 
                stop_revision, _hook_master=master_branch,
3185
 
                run_hooks=run_hooks,
3186
 
                _override_hook_target=_override_hook_target)
 
3525
            # We assume that during 'pull' the target repository is closer than
 
3526
            # the source one.
 
3527
            self.source.update_references(self.target)
 
3528
            graph = self.target.repository.get_graph(self.source.repository)
 
3529
            # TODO: Branch formats should have a flag that indicates 
 
3530
            # that revno's are expensive, and pull() should honor that flag.
 
3531
            # -- JRV20090506
 
3532
            result.old_revno, result.old_revid = \
 
3533
                self.target.last_revision_info()
 
3534
            self.target.update_revisions(self.source, stop_revision,
 
3535
                overwrite=overwrite, graph=graph)
 
3536
            # TODO: The old revid should be specified when merging tags, 
 
3537
            # so a tags implementation that versions tags can only 
 
3538
            # pull in the most recent changes. -- JRV20090506
 
3539
            result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
 
3540
                overwrite)
 
3541
            result.new_revno, result.new_revid = self.target.last_revision_info()
 
3542
            if _hook_master:
 
3543
                result.master_branch = _hook_master
 
3544
                result.local_branch = result.target_branch
 
3545
            else:
 
3546
                result.master_branch = result.target_branch
 
3547
                result.local_branch = None
 
3548
            if run_hooks:
 
3549
                for hook in Branch.hooks['post_pull']:
 
3550
                    hook(result)
3187
3551
        finally:
3188
 
            if master_branch:
3189
 
                master_branch.unlock()
 
3552
            self.source.unlock()
 
3553
        return result
3190
3554
 
3191
3555
 
3192
3556
InterBranch.register_optimiser(GenericInterBranch)
3193
 
InterBranch.register_optimiser(InterToBranch5)