~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

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
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
 
18
from cStringIO import StringIO
18
19
import sys
19
20
 
20
21
from bzrlib.lazy_import import lazy_import
28
29
        errors,
29
30
        lockdir,
30
31
        lockable_files,
 
32
        remote,
31
33
        repository,
32
34
        revision as _mod_revision,
 
35
        rio,
33
36
        symbol_versioning,
34
37
        transport,
35
38
        tsort,
44
47
    )
45
48
""")
46
49
 
47
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
50
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
48
51
from bzrlib.hooks import HookPoint, Hooks
49
52
from bzrlib.inter import InterObject
 
53
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
50
54
from bzrlib import registry
51
55
from bzrlib.symbol_versioning import (
52
56
    deprecated_in,
60
64
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
61
65
 
62
66
 
63
 
# TODO: Maybe include checks for common corruption of newlines, etc?
64
 
 
65
 
# TODO: Some operations like log might retrieve the same revisions
66
 
# repeatedly to calculate deltas.  We could perhaps have a weakref
67
 
# cache in memory to make this faster.  In general anything can be
68
 
# cached in memory between lock and unlock operations. .. nb thats
69
 
# what the transaction identity map provides
70
 
 
71
 
 
72
 
######################################################################
73
 
# branch objects
74
 
 
75
 
class Branch(object):
 
67
class Branch(bzrdir.ControlComponent):
76
68
    """Branch holding a history of revisions.
77
69
 
78
 
    base
79
 
        Base directory/url of the branch.
 
70
    :ivar base:
 
71
        Base directory/url of the branch; using control_url and
 
72
        control_transport is more standardized.
80
73
 
81
74
    hooks: An instance of BranchHooks.
82
75
    """
84
77
    # - RBC 20060112
85
78
    base = None
86
79
 
 
80
    @property
 
81
    def control_transport(self):
 
82
        return self._transport
 
83
 
 
84
    @property
 
85
    def user_transport(self):
 
86
        return self.bzrdir.user_transport
 
87
 
87
88
    def __init__(self, *ignored, **ignored_too):
88
89
        self.tags = self._format.make_tags(self)
89
90
        self._revision_history_cache = None
90
91
        self._revision_id_to_revno_cache = None
91
92
        self._partial_revision_id_to_revno_cache = {}
 
93
        self._partial_revision_history_cache = []
92
94
        self._last_revision_info_cache = None
93
95
        self._merge_sorted_revisions_cache = None
94
96
        self._open_hook()
101
103
 
102
104
    def _activate_fallback_location(self, url):
103
105
        """Activate the branch/repository from url as a fallback repository."""
104
 
        self.repository.add_fallback_repository(
105
 
            self._get_fallback_repository(url))
 
106
        repo = self._get_fallback_repository(url)
 
107
        if repo.has_same_location(self.repository):
 
108
            raise errors.UnstackableLocationError(self.user_url, url)
 
109
        self.repository.add_fallback_repository(repo)
106
110
 
107
111
    def break_lock(self):
108
112
        """Break a lock if one is present from another instance.
123
127
            raise errors.UnstackableRepositoryFormat(self.repository._format,
124
128
                self.repository.base)
125
129
 
 
130
    def _extend_partial_history(self, stop_index=None, stop_revision=None):
 
131
        """Extend the partial history to include a given index
 
132
 
 
133
        If a stop_index is supplied, stop when that index has been reached.
 
134
        If a stop_revision is supplied, stop when that revision is
 
135
        encountered.  Otherwise, stop when the beginning of history is
 
136
        reached.
 
137
 
 
138
        :param stop_index: The index which should be present.  When it is
 
139
            present, history extension will stop.
 
140
        :param stop_revision: The revision id which should be present.  When
 
141
            it is encountered, history extension will stop.
 
142
        """
 
143
        if len(self._partial_revision_history_cache) == 0:
 
144
            self._partial_revision_history_cache = [self.last_revision()]
 
145
        repository._iter_for_revno(
 
146
            self.repository, self._partial_revision_history_cache,
 
147
            stop_index=stop_index, stop_revision=stop_revision)
 
148
        if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
 
149
            self._partial_revision_history_cache.pop()
 
150
 
 
151
    def _get_check_refs(self):
 
152
        """Get the references needed for check().
 
153
 
 
154
        See bzrlib.check.
 
155
        """
 
156
        revid = self.last_revision()
 
157
        return [('revision-existence', revid), ('lefthand-distance', revid)]
 
158
 
126
159
    @staticmethod
127
160
    def open(base, _unsupported=False, possible_transports=None):
128
161
        """Open the branch rooted at base.
132
165
        """
133
166
        control = bzrdir.BzrDir.open(base, _unsupported,
134
167
                                     possible_transports=possible_transports)
135
 
        return control.open_branch(_unsupported)
 
168
        return control.open_branch(unsupported=_unsupported)
136
169
 
137
170
    @staticmethod
138
 
    def open_from_transport(transport, _unsupported=False):
 
171
    def open_from_transport(transport, name=None, _unsupported=False):
139
172
        """Open the branch rooted at transport"""
140
173
        control = bzrdir.BzrDir.open_from_transport(transport, _unsupported)
141
 
        return control.open_branch(_unsupported)
 
174
        return control.open_branch(name=name, unsupported=_unsupported)
142
175
 
143
176
    @staticmethod
144
177
    def open_containing(url, possible_transports=None):
165
198
        return self.supports_tags() and self.tags.get_tag_dict()
166
199
 
167
200
    def get_config(self):
 
201
        """Get a bzrlib.config.BranchConfig for this Branch.
 
202
 
 
203
        This can then be used to get and set configuration options for the
 
204
        branch.
 
205
 
 
206
        :return: A bzrlib.config.BranchConfig.
 
207
        """
168
208
        return BranchConfig(self)
169
209
 
170
210
    def _get_config(self):
182
222
    def _get_fallback_repository(self, url):
183
223
        """Get the repository we fallback to at url."""
184
224
        url = urlutils.join(self.base, url)
185
 
        a_bzrdir = bzrdir.BzrDir.open(url,
 
225
        a_branch = Branch.open(url,
186
226
            possible_transports=[self.bzrdir.root_transport])
187
 
        return a_bzrdir.open_branch().repository
 
227
        return a_branch.repository
188
228
 
189
229
    def _get_tags_bytes(self):
190
230
        """Get the bytes of a serialised tags dict.
206
246
        if not local and not config.has_explicit_nickname():
207
247
            try:
208
248
                master = self.get_master_branch(possible_transports)
 
249
                if master and self.user_url == master.user_url:
 
250
                    raise errors.RecursiveBind(self.user_url)
209
251
                if master is not None:
210
252
                    # return the master branch value
211
253
                    return master.nick
 
254
            except errors.RecursiveBind, e:
 
255
                raise e
212
256
            except errors.BzrError, e:
213
257
                # Silently fall back to local implicit nick if the master is
214
258
                # unavailable
251
295
        new_history.reverse()
252
296
        return new_history
253
297
 
254
 
    def lock_write(self):
 
298
    def lock_write(self, token=None):
 
299
        """Lock the branch for write operations.
 
300
 
 
301
        :param token: A token to permit reacquiring a previously held and
 
302
            preserved lock.
 
303
        :return: A BranchWriteLockResult.
 
304
        """
255
305
        raise NotImplementedError(self.lock_write)
256
306
 
257
307
    def lock_read(self):
 
308
        """Lock the branch for read operations.
 
309
 
 
310
        :return: A bzrlib.lock.LogicalLockResult.
 
311
        """
258
312
        raise NotImplementedError(self.lock_read)
259
313
 
260
314
    def unlock(self):
385
439
            * 'include' - the stop revision is the last item in the result
386
440
            * 'with-merges' - include the stop revision and all of its
387
441
              merged revisions in the result
 
442
            * 'with-merges-without-common-ancestry' - filter out revisions 
 
443
              that are in both ancestries
388
444
        :param direction: either 'reverse' or 'forward':
389
445
            * reverse means return the start_revision_id first, i.e.
390
446
              start at the most recent revision and go backwards in history
412
468
        # start_revision_id.
413
469
        if self._merge_sorted_revisions_cache is None:
414
470
            last_revision = self.last_revision()
415
 
            graph = self.repository.get_graph()
416
 
            parent_map = dict(((key, value) for key, value in
417
 
                     graph.iter_ancestry([last_revision]) if value is not None))
418
 
            revision_graph = repository._strip_NULL_ghosts(parent_map)
419
 
            revs = tsort.merge_sort(revision_graph, last_revision, None,
420
 
                generate_revno=True)
421
 
            # Drop the sequence # before caching
422
 
            self._merge_sorted_revisions_cache = [r[1:] for r in revs]
423
 
 
 
471
            known_graph = self.repository.get_known_graph_ancestry(
 
472
                [last_revision])
 
473
            self._merge_sorted_revisions_cache = known_graph.merge_sort(
 
474
                last_revision)
424
475
        filtered = self._filter_merge_sorted_revisions(
425
476
            self._merge_sorted_revisions_cache, start_revision_id,
426
477
            stop_revision_id, stop_rule)
 
478
        # Make sure we don't return revisions that are not part of the
 
479
        # start_revision_id ancestry.
 
480
        filtered = self._filter_start_non_ancestors(filtered)
427
481
        if direction == 'reverse':
428
482
            return filtered
429
483
        if direction == 'forward':
436
490
        """Iterate over an inclusive range of sorted revisions."""
437
491
        rev_iter = iter(merge_sorted_revisions)
438
492
        if start_revision_id is not None:
439
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
 
493
            for node in rev_iter:
 
494
                rev_id = node.key[-1]
440
495
                if rev_id != start_revision_id:
441
496
                    continue
442
497
                else:
443
498
                    # The decision to include the start or not
444
499
                    # depends on the stop_rule if a stop is provided
445
 
                    rev_iter = chain(
446
 
                        iter([(rev_id, depth, revno, end_of_merge)]),
447
 
                        rev_iter)
 
500
                    # so pop this node back into the iterator
 
501
                    rev_iter = chain(iter([node]), rev_iter)
448
502
                    break
449
503
        if stop_revision_id is None:
450
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
451
 
                yield rev_id, depth, revno, end_of_merge
 
504
            # Yield everything
 
505
            for node in rev_iter:
 
506
                rev_id = node.key[-1]
 
507
                yield (rev_id, node.merge_depth, node.revno,
 
508
                       node.end_of_merge)
452
509
        elif stop_rule == 'exclude':
453
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
 
510
            for node in rev_iter:
 
511
                rev_id = node.key[-1]
454
512
                if rev_id == stop_revision_id:
455
513
                    return
456
 
                yield rev_id, depth, revno, end_of_merge
 
514
                yield (rev_id, node.merge_depth, node.revno,
 
515
                       node.end_of_merge)
457
516
        elif stop_rule == 'include':
458
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
459
 
                yield rev_id, depth, revno, end_of_merge
 
517
            for node in rev_iter:
 
518
                rev_id = node.key[-1]
 
519
                yield (rev_id, node.merge_depth, node.revno,
 
520
                       node.end_of_merge)
460
521
                if rev_id == stop_revision_id:
461
522
                    return
 
523
        elif stop_rule == 'with-merges-without-common-ancestry':
 
524
            # We want to exclude all revisions that are already part of the
 
525
            # stop_revision_id ancestry.
 
526
            graph = self.repository.get_graph()
 
527
            ancestors = graph.find_unique_ancestors(start_revision_id,
 
528
                                                    [stop_revision_id])
 
529
            for node in rev_iter:
 
530
                rev_id = node.key[-1]
 
531
                if rev_id not in ancestors:
 
532
                    continue
 
533
                yield (rev_id, node.merge_depth, node.revno,
 
534
                       node.end_of_merge)
462
535
        elif stop_rule == 'with-merges':
463
536
            stop_rev = self.repository.get_revision(stop_revision_id)
464
537
            if stop_rev.parent_ids:
465
538
                left_parent = stop_rev.parent_ids[0]
466
539
            else:
467
540
                left_parent = _mod_revision.NULL_REVISION
468
 
            for rev_id, depth, revno, end_of_merge in rev_iter:
 
541
            # left_parent is the actual revision we want to stop logging at,
 
542
            # since we want to show the merged revisions after the stop_rev too
 
543
            reached_stop_revision_id = False
 
544
            revision_id_whitelist = []
 
545
            for node in rev_iter:
 
546
                rev_id = node.key[-1]
469
547
                if rev_id == left_parent:
 
548
                    # reached the left parent after the stop_revision
470
549
                    return
471
 
                yield rev_id, depth, revno, end_of_merge
 
550
                if (not reached_stop_revision_id or
 
551
                        rev_id in revision_id_whitelist):
 
552
                    yield (rev_id, node.merge_depth, node.revno,
 
553
                       node.end_of_merge)
 
554
                    if reached_stop_revision_id or rev_id == stop_revision_id:
 
555
                        # only do the merged revs of rev_id from now on
 
556
                        rev = self.repository.get_revision(rev_id)
 
557
                        if rev.parent_ids:
 
558
                            reached_stop_revision_id = True
 
559
                            revision_id_whitelist.extend(rev.parent_ids)
472
560
        else:
473
561
            raise ValueError('invalid stop_rule %r' % stop_rule)
474
562
 
 
563
    def _filter_start_non_ancestors(self, rev_iter):
 
564
        # If we started from a dotted revno, we want to consider it as a tip
 
565
        # and don't want to yield revisions that are not part of its
 
566
        # ancestry. Given the order guaranteed by the merge sort, we will see
 
567
        # uninteresting descendants of the first parent of our tip before the
 
568
        # tip itself.
 
569
        first = rev_iter.next()
 
570
        (rev_id, merge_depth, revno, end_of_merge) = first
 
571
        yield first
 
572
        if not merge_depth:
 
573
            # We start at a mainline revision so by definition, all others
 
574
            # revisions in rev_iter are ancestors
 
575
            for node in rev_iter:
 
576
                yield node
 
577
 
 
578
        clean = False
 
579
        whitelist = set()
 
580
        pmap = self.repository.get_parent_map([rev_id])
 
581
        parents = pmap.get(rev_id, [])
 
582
        if parents:
 
583
            whitelist.update(parents)
 
584
        else:
 
585
            # If there is no parents, there is nothing of interest left
 
586
 
 
587
            # FIXME: It's hard to test this scenario here as this code is never
 
588
            # called in that case. -- vila 20100322
 
589
            return
 
590
 
 
591
        for (rev_id, merge_depth, revno, end_of_merge) in rev_iter:
 
592
            if not clean:
 
593
                if rev_id in whitelist:
 
594
                    pmap = self.repository.get_parent_map([rev_id])
 
595
                    parents = pmap.get(rev_id, [])
 
596
                    whitelist.remove(rev_id)
 
597
                    whitelist.update(parents)
 
598
                    if merge_depth == 0:
 
599
                        # We've reached the mainline, there is nothing left to
 
600
                        # filter
 
601
                        clean = True
 
602
                else:
 
603
                    # A revision that is not part of the ancestry of our
 
604
                    # starting revision.
 
605
                    continue
 
606
            yield (rev_id, merge_depth, revno, end_of_merge)
 
607
 
475
608
    def leave_lock_in_place(self):
476
609
        """Tell this branch object not to release the physical lock when this
477
610
        object is unlocked.
494
627
        :param other: The branch to bind to
495
628
        :type other: Branch
496
629
        """
497
 
        raise errors.UpgradeRequired(self.base)
 
630
        raise errors.UpgradeRequired(self.user_url)
 
631
 
 
632
    def set_append_revisions_only(self, enabled):
 
633
        if not self._format.supports_set_append_revisions_only():
 
634
            raise errors.UpgradeRequired(self.user_url)
 
635
        if enabled:
 
636
            value = 'True'
 
637
        else:
 
638
            value = 'False'
 
639
        self.get_config().set_user_option('append_revisions_only', value,
 
640
            warn_masked=True)
 
641
 
 
642
    def set_reference_info(self, file_id, tree_path, branch_location):
 
643
        """Set the branch location to use for a tree reference."""
 
644
        raise errors.UnsupportedOperation(self.set_reference_info, self)
 
645
 
 
646
    def get_reference_info(self, file_id):
 
647
        """Get the tree_path and branch_location for a tree reference."""
 
648
        raise errors.UnsupportedOperation(self.get_reference_info, self)
498
649
 
499
650
    @needs_write_lock
500
651
    def fetch(self, from_branch, last_revision=None, pb=None):
534
685
    def get_old_bound_location(self):
535
686
        """Return the URL of the branch we used to be bound to
536
687
        """
537
 
        raise errors.UpgradeRequired(self.base)
 
688
        raise errors.UpgradeRequired(self.user_url)
538
689
 
539
690
    def get_commit_builder(self, parents, config=None, timestamp=None,
540
691
                           timezone=None, committer=None, revprops=None,
590
741
    def set_revision_history(self, rev_history):
591
742
        raise NotImplementedError(self.set_revision_history)
592
743
 
 
744
    @needs_write_lock
 
745
    def set_parent(self, url):
 
746
        """See Branch.set_parent."""
 
747
        # TODO: Maybe delete old location files?
 
748
        # URLs should never be unicode, even on the local fs,
 
749
        # FIXUP this and get_parent in a future branch format bump:
 
750
        # read and rewrite the file. RBC 20060125
 
751
        if url is not None:
 
752
            if isinstance(url, unicode):
 
753
                try:
 
754
                    url = url.encode('ascii')
 
755
                except UnicodeEncodeError:
 
756
                    raise errors.InvalidURL(url,
 
757
                        "Urls must be 7-bit ascii, "
 
758
                        "use bzrlib.urlutils.escape")
 
759
            url = urlutils.relative_url(self.base, url)
 
760
        self._set_parent_location(url)
 
761
 
 
762
    @needs_write_lock
593
763
    def set_stacked_on_url(self, url):
594
764
        """Set the URL this branch is stacked against.
595
765
 
599
769
            stacking.
600
770
        """
601
771
        if not self._format.supports_stacking():
602
 
            raise errors.UnstackableBranchFormat(self._format, self.base)
 
772
            raise errors.UnstackableBranchFormat(self._format, self.user_url)
 
773
        # XXX: Changing from one fallback repository to another does not check
 
774
        # that all the data you need is present in the new fallback.
 
775
        # Possibly it should.
603
776
        self._check_stackable_repo()
604
777
        if not url:
605
778
            try:
607
780
            except (errors.NotStacked, errors.UnstackableBranchFormat,
608
781
                errors.UnstackableRepositoryFormat):
609
782
                return
610
 
            url = ''
611
 
            # repositories don't offer an interface to remove fallback
612
 
            # repositories today; take the conceptually simpler option and just
613
 
            # reopen it.
614
 
            self.repository = self.bzrdir.find_repository()
615
 
            # for every revision reference the branch has, ensure it is pulled
616
 
            # in.
617
 
            source_repository = self._get_fallback_repository(old_url)
618
 
            for revision_id in chain([self.last_revision()],
619
 
                self.tags.get_reverse_tag_dict()):
620
 
                self.repository.fetch(source_repository, revision_id,
621
 
                    find_ghosts=True)
 
783
            self._unstack()
622
784
        else:
623
785
            self._activate_fallback_location(url)
624
786
        # write this out after the repository is stacked to avoid setting a
625
787
        # stacked config that doesn't work.
626
788
        self._set_config_location('stacked_on_location', url)
627
789
 
 
790
    def _unstack(self):
 
791
        """Change a branch to be unstacked, copying data as needed.
 
792
        
 
793
        Don't call this directly, use set_stacked_on_url(None).
 
794
        """
 
795
        pb = ui.ui_factory.nested_progress_bar()
 
796
        try:
 
797
            pb.update("Unstacking")
 
798
            # The basic approach here is to fetch the tip of the branch,
 
799
            # including all available ghosts, from the existing stacked
 
800
            # repository into a new repository object without the fallbacks. 
 
801
            #
 
802
            # XXX: See <https://launchpad.net/bugs/397286> - this may not be
 
803
            # correct for CHKMap repostiories
 
804
            old_repository = self.repository
 
805
            if len(old_repository._fallback_repositories) != 1:
 
806
                raise AssertionError("can't cope with fallback repositories "
 
807
                    "of %r" % (self.repository,))
 
808
            # Open the new repository object.
 
809
            # Repositories don't offer an interface to remove fallback
 
810
            # repositories today; take the conceptually simpler option and just
 
811
            # reopen it.  We reopen it starting from the URL so that we
 
812
            # get a separate connection for RemoteRepositories and can
 
813
            # stream from one of them to the other.  This does mean doing
 
814
            # separate SSH connection setup, but unstacking is not a
 
815
            # common operation so it's tolerable.
 
816
            new_bzrdir = bzrdir.BzrDir.open(self.bzrdir.root_transport.base)
 
817
            new_repository = new_bzrdir.find_repository()
 
818
            if new_repository._fallback_repositories:
 
819
                raise AssertionError("didn't expect %r to have "
 
820
                    "fallback_repositories"
 
821
                    % (self.repository,))
 
822
            # Replace self.repository with the new repository.
 
823
            # Do our best to transfer the lock state (i.e. lock-tokens and
 
824
            # lock count) of self.repository to the new repository.
 
825
            lock_token = old_repository.lock_write().repository_token
 
826
            self.repository = new_repository
 
827
            if isinstance(self, remote.RemoteBranch):
 
828
                # Remote branches can have a second reference to the old
 
829
                # repository that need to be replaced.
 
830
                if self._real_branch is not None:
 
831
                    self._real_branch.repository = new_repository
 
832
            self.repository.lock_write(token=lock_token)
 
833
            if lock_token is not None:
 
834
                old_repository.leave_lock_in_place()
 
835
            old_repository.unlock()
 
836
            if lock_token is not None:
 
837
                # XXX: self.repository.leave_lock_in_place() before this
 
838
                # function will not be preserved.  Fortunately that doesn't
 
839
                # affect the current default format (2a), and would be a
 
840
                # corner-case anyway.
 
841
                #  - Andrew Bennetts, 2010/06/30
 
842
                self.repository.dont_leave_lock_in_place()
 
843
            old_lock_count = 0
 
844
            while True:
 
845
                try:
 
846
                    old_repository.unlock()
 
847
                except errors.LockNotHeld:
 
848
                    break
 
849
                old_lock_count += 1
 
850
            if old_lock_count == 0:
 
851
                raise AssertionError(
 
852
                    'old_repository should have been locked at least once.')
 
853
            for i in range(old_lock_count-1):
 
854
                self.repository.lock_write()
 
855
            # Fetch from the old repository into the new.
 
856
            old_repository.lock_read()
 
857
            try:
 
858
                # XXX: If you unstack a branch while it has a working tree
 
859
                # with a pending merge, the pending-merged revisions will no
 
860
                # longer be present.  You can (probably) revert and remerge.
 
861
                #
 
862
                # XXX: This only fetches up to the tip of the repository; it
 
863
                # doesn't bring across any tags.  That's fairly consistent
 
864
                # with how branch works, but perhaps not ideal.
 
865
                self.repository.fetch(old_repository,
 
866
                    revision_id=self.last_revision(),
 
867
                    find_ghosts=True)
 
868
            finally:
 
869
                old_repository.unlock()
 
870
        finally:
 
871
            pb.finished()
628
872
 
629
873
    def _set_tags_bytes(self, bytes):
630
874
        """Mirror method for _get_tags_bytes.
666
910
        self._revision_id_to_revno_cache = None
667
911
        self._last_revision_info_cache = None
668
912
        self._merge_sorted_revisions_cache = None
 
913
        self._partial_revision_history_cache = []
 
914
        self._partial_revision_id_to_revno_cache = {}
669
915
 
670
916
    def _gen_revision_history(self):
671
917
        """Return sequence of revision hashes on to this branch.
708
954
 
709
955
    def unbind(self):
710
956
        """Older format branches cannot bind or unbind."""
711
 
        raise errors.UpgradeRequired(self.base)
712
 
 
713
 
    def set_append_revisions_only(self, enabled):
714
 
        """Older format branches are never restricted to append-only"""
715
 
        raise errors.UpgradeRequired(self.base)
 
957
        raise errors.UpgradeRequired(self.user_url)
716
958
 
717
959
    def last_revision(self):
718
960
        """Return last revision id, or NULL_REVISION."""
759
1001
                raise errors.NoSuchRevision(self, stop_revision)
760
1002
        return other_history[self_len:stop_revision]
761
1003
 
762
 
    @needs_write_lock
763
1004
    def update_revisions(self, other, stop_revision=None, overwrite=False,
764
1005
                         graph=None):
765
1006
        """Pull in new perfect-fit revisions.
799
1040
        except ValueError:
800
1041
            raise errors.NoSuchRevision(self, revision_id)
801
1042
 
 
1043
    @needs_read_lock
802
1044
    def get_rev_id(self, revno, history=None):
803
1045
        """Find the revision id of the specified revno."""
804
1046
        if revno == 0:
805
1047
            return _mod_revision.NULL_REVISION
806
 
        if history is None:
807
 
            history = self.revision_history()
808
 
        if revno <= 0 or revno > len(history):
 
1048
        last_revno, last_revid = self.last_revision_info()
 
1049
        if revno == last_revno:
 
1050
            return last_revid
 
1051
        if revno <= 0 or revno > last_revno:
809
1052
            raise errors.NoSuchRevision(self, revno)
810
 
        return history[revno - 1]
 
1053
        distance_from_last = last_revno - revno
 
1054
        if len(self._partial_revision_history_cache) <= distance_from_last:
 
1055
            self._extend_partial_history(distance_from_last)
 
1056
        return self._partial_revision_history_cache[distance_from_last]
811
1057
 
812
1058
    def pull(self, source, overwrite=False, stop_revision=None,
813
 
             possible_transports=None, _override_hook_target=None):
 
1059
             possible_transports=None, *args, **kwargs):
814
1060
        """Mirror source into this branch.
815
1061
 
816
1062
        This branch is considered to be 'local', having low latency.
817
1063
 
818
1064
        :returns: PullResult instance
819
1065
        """
820
 
        raise NotImplementedError(self.pull)
 
1066
        return InterBranch.get(source, self).pull(overwrite=overwrite,
 
1067
            stop_revision=stop_revision,
 
1068
            possible_transports=possible_transports, *args, **kwargs)
821
1069
 
822
 
    def push(self, target, overwrite=False, stop_revision=None):
 
1070
    def push(self, target, overwrite=False, stop_revision=None, *args,
 
1071
        **kwargs):
823
1072
        """Mirror this branch into target.
824
1073
 
825
1074
        This branch is considered to be 'local', having low latency.
826
1075
        """
827
 
        raise NotImplementedError(self.push)
 
1076
        return InterBranch.get(self, target).push(overwrite, stop_revision,
 
1077
            *args, **kwargs)
 
1078
 
 
1079
    def lossy_push(self, target, stop_revision=None):
 
1080
        """Push deltas into another branch.
 
1081
 
 
1082
        :note: This does not, like push, retain the revision ids from 
 
1083
            the source branch and will, rather than adding bzr-specific 
 
1084
            metadata, push only those semantics of the revision that can be 
 
1085
            natively represented by this branch' VCS.
 
1086
 
 
1087
        :param target: Target branch
 
1088
        :param stop_revision: Revision to push, defaults to last revision.
 
1089
        :return: BranchPushResult with an extra member revidmap: 
 
1090
            A dictionary mapping revision ids from the target branch 
 
1091
            to new revision ids in the target branch, for each 
 
1092
            revision that was pushed.
 
1093
        """
 
1094
        inter = InterBranch.get(self, target)
 
1095
        lossy_push = getattr(inter, "lossy_push", None)
 
1096
        if lossy_push is None:
 
1097
            raise errors.LossyPushToSameVCS(self, target)
 
1098
        return lossy_push(stop_revision)
828
1099
 
829
1100
    def basis_tree(self):
830
1101
        """Return `Tree` object for last revision."""
847
1118
        try:
848
1119
            return urlutils.join(self.base[:-1], parent)
849
1120
        except errors.InvalidURLJoin, e:
850
 
            raise errors.InaccessibleParent(parent, self.base)
 
1121
            raise errors.InaccessibleParent(parent, self.user_url)
851
1122
 
852
1123
    def _get_parent_location(self):
853
1124
        raise NotImplementedError(self._get_parent_location)
870
1141
            location = None
871
1142
        return location
872
1143
 
 
1144
    def get_child_submit_format(self):
 
1145
        """Return the preferred format of submissions to this branch."""
 
1146
        return self.get_config().get_user_option("child_submit_format")
 
1147
 
873
1148
    def get_submit_branch(self):
874
1149
        """Return the submit location of the branch.
875
1150
 
934
1209
        params = ChangeBranchTipParams(
935
1210
            self, old_revno, new_revno, old_revid, new_revid)
936
1211
        for hook in hooks:
937
 
            try:
938
 
                hook(params)
939
 
            except errors.TipChangeRejected:
940
 
                raise
941
 
            except Exception:
942
 
                exc_info = sys.exc_info()
943
 
                hook_name = Branch.hooks.get_hook_name(hook)
944
 
                raise errors.HookFailed(
945
 
                    'pre_change_branch_tip', hook_name, exc_info)
946
 
 
947
 
    def set_parent(self, url):
948
 
        raise NotImplementedError(self.set_parent)
 
1212
            hook(params)
949
1213
 
950
1214
    @needs_write_lock
951
1215
    def update(self):
982
1246
                     be truncated to end with revision_id.
983
1247
        """
984
1248
        result = to_bzrdir.create_branch()
985
 
        if repository_policy is not None:
986
 
            repository_policy.configure_branch(result)
987
 
        self.copy_content_into(result, revision_id=revision_id)
988
 
        return  result
 
1249
        result.lock_write()
 
1250
        try:
 
1251
            if repository_policy is not None:
 
1252
                repository_policy.configure_branch(result)
 
1253
            self.copy_content_into(result, revision_id=revision_id)
 
1254
        finally:
 
1255
            result.unlock()
 
1256
        return result
989
1257
 
990
1258
    @needs_read_lock
991
1259
    def sprout(self, to_bzrdir, revision_id=None, repository_policy=None):
996
1264
        revision_id: if not None, the revision history in the new branch will
997
1265
                     be truncated to end with revision_id.
998
1266
        """
 
1267
        if (repository_policy is not None and
 
1268
            repository_policy.requires_stacking()):
 
1269
            to_bzrdir._format.require_stacking(_skip_repo=True)
999
1270
        result = to_bzrdir.create_branch()
1000
 
        if repository_policy is not None:
1001
 
            repository_policy.configure_branch(result)
1002
 
        self.copy_content_into(result, revision_id=revision_id)
1003
 
        result.set_parent(self.bzrdir.root_transport.base)
 
1271
        result.lock_write()
 
1272
        try:
 
1273
            if repository_policy is not None:
 
1274
                repository_policy.configure_branch(result)
 
1275
            self.copy_content_into(result, revision_id=revision_id)
 
1276
            result.set_parent(self.bzrdir.root_transport.base)
 
1277
        finally:
 
1278
            result.unlock()
1004
1279
        return result
1005
1280
 
1006
1281
    def _synchronize_history(self, destination, revision_id):
1018
1293
        source_revno, source_revision_id = self.last_revision_info()
1019
1294
        if revision_id is None:
1020
1295
            revno, revision_id = source_revno, source_revision_id
1021
 
        elif source_revision_id == revision_id:
1022
 
            # we know the revno without needing to walk all of history
1023
 
            revno = source_revno
1024
1296
        else:
1025
 
            # To figure out the revno for a random revision, we need to build
1026
 
            # the revision history, and count its length.
1027
 
            # We don't care about the order, just how long it is.
1028
 
            # Alternatively, we could start at the current location, and count
1029
 
            # backwards. But there is no guarantee that we will find it since
1030
 
            # it may be a merged revision.
1031
 
            revno = len(list(self.repository.iter_reverse_revision_history(
1032
 
                                                                revision_id)))
 
1297
            graph = self.repository.get_graph()
 
1298
            try:
 
1299
                revno = graph.find_distance_to_null(revision_id, 
 
1300
                    [(source_revision_id, source_revno)])
 
1301
            except errors.GhostRevisionsHaveNoRevno:
 
1302
                # Default to 1, if we can't find anything else
 
1303
                revno = 1
1033
1304
        destination.set_last_revision_info(revno, revision_id)
1034
1305
 
1035
 
    @needs_read_lock
1036
1306
    def copy_content_into(self, destination, revision_id=None):
1037
1307
        """Copy the content of self into destination.
1038
1308
 
1039
1309
        revision_id: if not None, the revision history in the new branch will
1040
1310
                     be truncated to end with revision_id.
1041
1311
        """
1042
 
        self._synchronize_history(destination, revision_id)
1043
 
        try:
1044
 
            parent = self.get_parent()
1045
 
        except errors.InaccessibleParent, e:
1046
 
            mutter('parent was not accessible to copy: %s', e)
1047
 
        else:
1048
 
            if parent:
1049
 
                destination.set_parent(parent)
1050
 
        if self._push_should_merge_tags():
1051
 
            self.tags.merge_to(destination.tags)
 
1312
        return InterBranch.get(self, destination).copy_content_into(
 
1313
            revision_id=revision_id)
 
1314
 
 
1315
    def update_references(self, target):
 
1316
        if not getattr(self._format, 'supports_reference_locations', False):
 
1317
            return
 
1318
        reference_dict = self._get_all_reference_info()
 
1319
        if len(reference_dict) == 0:
 
1320
            return
 
1321
        old_base = self.base
 
1322
        new_base = target.base
 
1323
        target_reference_dict = target._get_all_reference_info()
 
1324
        for file_id, (tree_path, branch_location) in (
 
1325
            reference_dict.items()):
 
1326
            branch_location = urlutils.rebase_url(branch_location,
 
1327
                                                  old_base, new_base)
 
1328
            target_reference_dict.setdefault(
 
1329
                file_id, (tree_path, branch_location))
 
1330
        target._set_all_reference_info(target_reference_dict)
1052
1331
 
1053
1332
    @needs_read_lock
1054
 
    def check(self):
 
1333
    def check(self, refs):
1055
1334
        """Check consistency of the branch.
1056
1335
 
1057
1336
        In particular this checks that revisions given in the revision-history
1060
1339
 
1061
1340
        Callers will typically also want to check the repository.
1062
1341
 
 
1342
        :param refs: Calculated refs for this branch as specified by
 
1343
            branch._get_check_refs()
1063
1344
        :return: A BranchCheckResult.
1064
1345
        """
1065
 
        mainline_parent_id = None
 
1346
        result = BranchCheckResult(self)
1066
1347
        last_revno, last_revision_id = self.last_revision_info()
1067
 
        real_rev_history = list(self.repository.iter_reverse_revision_history(
1068
 
                                last_revision_id))
1069
 
        real_rev_history.reverse()
1070
 
        if len(real_rev_history) != last_revno:
1071
 
            raise errors.BzrCheckError('revno does not match len(mainline)'
1072
 
                ' %s != %s' % (last_revno, len(real_rev_history)))
1073
 
        # TODO: We should probably also check that real_rev_history actually
1074
 
        #       matches self.revision_history()
1075
 
        for revision_id in real_rev_history:
1076
 
            try:
1077
 
                revision = self.repository.get_revision(revision_id)
1078
 
            except errors.NoSuchRevision, e:
1079
 
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
1080
 
                            % revision_id)
1081
 
            # In general the first entry on the revision history has no parents.
1082
 
            # But it's not illegal for it to have parents listed; this can happen
1083
 
            # in imports from Arch when the parents weren't reachable.
1084
 
            if mainline_parent_id is not None:
1085
 
                if mainline_parent_id not in revision.parent_ids:
1086
 
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
1087
 
                                        "parents of {%s}"
1088
 
                                        % (mainline_parent_id, revision_id))
1089
 
            mainline_parent_id = revision_id
1090
 
        return BranchCheckResult(self)
 
1348
        actual_revno = refs[('lefthand-distance', last_revision_id)]
 
1349
        if actual_revno != last_revno:
 
1350
            result.errors.append(errors.BzrCheckError(
 
1351
                'revno does not match len(mainline) %s != %s' % (
 
1352
                last_revno, actual_revno)))
 
1353
        # TODO: We should probably also check that self.revision_history
 
1354
        # matches the repository for older branch formats.
 
1355
        # If looking for the code that cross-checks repository parents against
 
1356
        # the iter_reverse_revision_history output, that is now a repository
 
1357
        # specific check.
 
1358
        return result
1091
1359
 
1092
1360
    def _get_checkout_format(self):
1093
1361
        """Return the most suitable metadir for a checkout of this branch.
1103
1371
        return format
1104
1372
 
1105
1373
    def create_clone_on_transport(self, to_transport, revision_id=None,
1106
 
        stacked_on=None):
 
1374
        stacked_on=None, create_prefix=False, use_existing_dir=False):
1107
1375
        """Create a clone of this branch and its bzrdir.
1108
1376
 
1109
1377
        :param to_transport: The transport to clone onto.
1110
1378
        :param revision_id: The revision id to use as tip in the new branch.
1111
1379
            If None the tip is obtained from this branch.
1112
1380
        :param stacked_on: An optional URL to stack the clone on.
 
1381
        :param create_prefix: Create any missing directories leading up to
 
1382
            to_transport.
 
1383
        :param use_existing_dir: Use an existing directory if one exists.
1113
1384
        """
1114
1385
        # XXX: Fix the bzrdir API to allow getting the branch back from the
1115
1386
        # clone call. Or something. 20090224 RBC/spiv.
 
1387
        # XXX: Should this perhaps clone colocated branches as well, 
 
1388
        # rather than just the default branch? 20100319 JRV
 
1389
        if revision_id is None:
 
1390
            revision_id = self.last_revision()
1116
1391
        dir_to = self.bzrdir.clone_on_transport(to_transport,
1117
 
            revision_id=revision_id, stacked_on=stacked_on)
 
1392
            revision_id=revision_id, stacked_on=stacked_on,
 
1393
            create_prefix=create_prefix, use_existing_dir=use_existing_dir)
1118
1394
        return dir_to.open_branch()
1119
1395
 
1120
1396
    def create_checkout(self, to_location, revision_id=None,
1139
1415
        if lightweight:
1140
1416
            format = self._get_checkout_format()
1141
1417
            checkout = format.initialize_on_transport(t)
1142
 
            from_branch = BranchReferenceFormat().initialize(checkout, self)
 
1418
            from_branch = BranchReferenceFormat().initialize(checkout, 
 
1419
                target_branch=self)
1143
1420
        else:
1144
1421
            format = self._get_checkout_format()
1145
1422
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
1174
1451
        reconciler.reconcile()
1175
1452
        return reconciler
1176
1453
 
1177
 
    def reference_parent(self, file_id, path):
 
1454
    def reference_parent(self, file_id, path, possible_transports=None):
1178
1455
        """Return the parent branch for a tree-reference file_id
1179
1456
        :param file_id: The file_id of the tree reference
1180
1457
        :param path: The path of the file_id in the tree
1181
1458
        :return: A branch associated with the file_id
1182
1459
        """
1183
1460
        # FIXME should provide multiple branches, based on config
1184
 
        return Branch.open(self.bzrdir.root_transport.clone(path).base)
 
1461
        return Branch.open(self.bzrdir.root_transport.clone(path).base,
 
1462
                           possible_transports=possible_transports)
1185
1463
 
1186
1464
    def supports_tags(self):
1187
1465
        return self._format.supports_tags()
1188
1466
 
 
1467
    def automatic_tag_name(self, revision_id):
 
1468
        """Try to automatically find the tag name for a revision.
 
1469
 
 
1470
        :param revision_id: Revision id of the revision.
 
1471
        :return: A tag name or None if no tag name could be determined.
 
1472
        """
 
1473
        for hook in Branch.hooks['automatic_tag_name']:
 
1474
            ret = hook(self, revision_id)
 
1475
            if ret is not None:
 
1476
                return ret
 
1477
        return None
 
1478
 
1189
1479
    def _check_if_descendant_or_diverged(self, revision_a, revision_b, graph,
1190
1480
                                         other_branch):
1191
1481
        """Ensure that revision_b is a descendant of revision_a.
1246
1536
    _formats = {}
1247
1537
    """The known formats."""
1248
1538
 
 
1539
    can_set_append_revisions_only = True
 
1540
 
1249
1541
    def __eq__(self, other):
1250
1542
        return self.__class__ is other.__class__
1251
1543
 
1253
1545
        return not (self == other)
1254
1546
 
1255
1547
    @classmethod
1256
 
    def find_format(klass, a_bzrdir):
 
1548
    def find_format(klass, a_bzrdir, name=None):
1257
1549
        """Return the format for the branch object in a_bzrdir."""
1258
1550
        try:
1259
 
            transport = a_bzrdir.get_branch_transport(None)
1260
 
            format_string = transport.get("format").read()
1261
 
            return klass._formats[format_string]
 
1551
            transport = a_bzrdir.get_branch_transport(None, name=name)
 
1552
            format_string = transport.get_bytes("format")
 
1553
            format = klass._formats[format_string]
 
1554
            if isinstance(format, MetaDirBranchFormatFactory):
 
1555
                return format()
 
1556
            return format
1262
1557
        except errors.NoSuchFile:
1263
 
            raise errors.NotBranchError(path=transport.base)
 
1558
            raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1264
1559
        except KeyError:
1265
1560
            raise errors.UnknownFormatError(format=format_string, kind='branch')
1266
1561
 
1269
1564
        """Return the current default format."""
1270
1565
        return klass._default_format
1271
1566
 
1272
 
    def get_reference(self, a_bzrdir):
 
1567
    @classmethod
 
1568
    def get_formats(klass):
 
1569
        """Get all the known formats.
 
1570
 
 
1571
        Warning: This triggers a load of all lazy registered formats: do not
 
1572
        use except when that is desireed.
 
1573
        """
 
1574
        result = []
 
1575
        for fmt in klass._formats.values():
 
1576
            if isinstance(fmt, MetaDirBranchFormatFactory):
 
1577
                fmt = fmt()
 
1578
            result.append(fmt)
 
1579
        return result
 
1580
 
 
1581
    def get_reference(self, a_bzrdir, name=None):
1273
1582
        """Get the target reference of the branch in a_bzrdir.
1274
1583
 
1275
1584
        format probing must have been completed before calling
1277
1586
        in a_bzrdir is correct.
1278
1587
 
1279
1588
        :param a_bzrdir: The bzrdir to get the branch data from.
 
1589
        :param name: Name of the colocated branch to fetch
1280
1590
        :return: None if the branch is not a reference branch.
1281
1591
        """
1282
1592
        return None
1283
1593
 
1284
1594
    @classmethod
1285
 
    def set_reference(self, a_bzrdir, to_branch):
 
1595
    def set_reference(self, a_bzrdir, name, to_branch):
1286
1596
        """Set the target reference of the branch in a_bzrdir.
1287
1597
 
1288
1598
        format probing must have been completed before calling
1290
1600
        in a_bzrdir is correct.
1291
1601
 
1292
1602
        :param a_bzrdir: The bzrdir to set the branch reference for.
 
1603
        :param name: Name of colocated branch to set, None for default
1293
1604
        :param to_branch: branch that the checkout is to reference
1294
1605
        """
1295
1606
        raise NotImplementedError(self.set_reference)
1302
1613
        """Return the short format description for this format."""
1303
1614
        raise NotImplementedError(self.get_format_description)
1304
1615
 
1305
 
    def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
1306
 
                           set_format=True):
 
1616
    def _run_post_branch_init_hooks(self, a_bzrdir, name, branch):
 
1617
        hooks = Branch.hooks['post_branch_init']
 
1618
        if not hooks:
 
1619
            return
 
1620
        params = BranchInitHookParams(self, a_bzrdir, name, branch)
 
1621
        for hook in hooks:
 
1622
            hook(params)
 
1623
 
 
1624
    def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
 
1625
                           lock_type='metadir', set_format=True):
1307
1626
        """Initialize a branch in a bzrdir, with specified files
1308
1627
 
1309
1628
        :param a_bzrdir: The bzrdir to initialize the branch in
1310
1629
        :param utf8_files: The files to create as a list of
1311
1630
            (filename, content) tuples
 
1631
        :param name: Name of colocated branch to create, if any
1312
1632
        :param set_format: If True, set the format with
1313
1633
            self.get_format_string.  (BzrBranch4 has its format set
1314
1634
            elsewhere)
1315
1635
        :return: a branch in this format
1316
1636
        """
1317
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
1318
 
        branch_transport = a_bzrdir.get_branch_transport(self)
 
1637
        mutter('creating branch %r in %s', self, a_bzrdir.user_url)
 
1638
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
1319
1639
        lock_map = {
1320
1640
            'metadir': ('lock', lockdir.LockDir),
1321
1641
            'branch4': ('branch-lock', lockable_files.TransportLock),
1324
1644
        control_files = lockable_files.LockableFiles(branch_transport,
1325
1645
            lock_name, lock_class)
1326
1646
        control_files.create_lock()
1327
 
        control_files.lock_write()
 
1647
        try:
 
1648
            control_files.lock_write()
 
1649
        except errors.LockContention:
 
1650
            if lock_type != 'branch4':
 
1651
                raise
 
1652
            lock_taken = False
 
1653
        else:
 
1654
            lock_taken = True
1328
1655
        if set_format:
1329
1656
            utf8_files += [('format', self.get_format_string())]
1330
1657
        try:
1333
1660
                    filename, content,
1334
1661
                    mode=a_bzrdir._get_file_mode())
1335
1662
        finally:
1336
 
            control_files.unlock()
1337
 
        return self.open(a_bzrdir, _found=True)
 
1663
            if lock_taken:
 
1664
                control_files.unlock()
 
1665
        branch = self.open(a_bzrdir, name, _found=True)
 
1666
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
1667
        return branch
1338
1668
 
1339
 
    def initialize(self, a_bzrdir):
1340
 
        """Create a branch of this format in a_bzrdir."""
 
1669
    def initialize(self, a_bzrdir, name=None):
 
1670
        """Create a branch of this format in a_bzrdir.
 
1671
        
 
1672
        :param name: Name of the colocated branch to create.
 
1673
        """
1341
1674
        raise NotImplementedError(self.initialize)
1342
1675
 
1343
1676
    def is_supported(self):
1373
1706
        """
1374
1707
        raise NotImplementedError(self.network_name)
1375
1708
 
1376
 
    def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
 
1709
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
1377
1710
        """Return the branch object for a_bzrdir
1378
1711
 
1379
1712
        :param a_bzrdir: A BzrDir that contains a branch.
 
1713
        :param name: Name of colocated branch to open
1380
1714
        :param _found: a private parameter, do not use it. It is used to
1381
1715
            indicate if format probing has already be done.
1382
1716
        :param ignore_fallbacks: when set, no fallback branches will be opened
1386
1720
 
1387
1721
    @classmethod
1388
1722
    def register_format(klass, format):
1389
 
        """Register a metadir format."""
 
1723
        """Register a metadir format.
 
1724
        
 
1725
        See MetaDirBranchFormatFactory for the ability to register a format
 
1726
        without loading the code the format needs until it is actually used.
 
1727
        """
1390
1728
        klass._formats[format.get_format_string()] = format
1391
1729
        # Metadir formats have a network name of their format string, and get
1392
 
        # registered as class factories.
1393
 
        network_format_registry.register(format.get_format_string(), format.__class__)
 
1730
        # registered as factories.
 
1731
        if isinstance(format, MetaDirBranchFormatFactory):
 
1732
            network_format_registry.register(format.get_format_string(), format)
 
1733
        else:
 
1734
            network_format_registry.register(format.get_format_string(),
 
1735
                format.__class__)
1394
1736
 
1395
1737
    @classmethod
1396
1738
    def set_default_format(klass, format):
1397
1739
        klass._default_format = format
1398
1740
 
 
1741
    def supports_set_append_revisions_only(self):
 
1742
        """True if this format supports set_append_revisions_only."""
 
1743
        return False
 
1744
 
1399
1745
    def supports_stacking(self):
1400
1746
        """True if this format records a stacked-on branch."""
1401
1747
        return False
1412
1758
        return False  # by default
1413
1759
 
1414
1760
 
 
1761
class MetaDirBranchFormatFactory(registry._LazyObjectGetter):
 
1762
    """A factory for a BranchFormat object, permitting simple lazy registration.
 
1763
    
 
1764
    While none of the built in BranchFormats are lazy registered yet,
 
1765
    bzrlib.tests.test_branch.TestMetaDirBranchFormatFactory demonstrates how to
 
1766
    use it, and the bzr-loom plugin uses it as well (see
 
1767
    bzrlib.plugins.loom.formats).
 
1768
    """
 
1769
 
 
1770
    def __init__(self, format_string, module_name, member_name):
 
1771
        """Create a MetaDirBranchFormatFactory.
 
1772
 
 
1773
        :param format_string: The format string the format has.
 
1774
        :param module_name: Module to load the format class from.
 
1775
        :param member_name: Attribute name within the module for the format class.
 
1776
        """
 
1777
        registry._LazyObjectGetter.__init__(self, module_name, member_name)
 
1778
        self._format_string = format_string
 
1779
        
 
1780
    def get_format_string(self):
 
1781
        """See BranchFormat.get_format_string."""
 
1782
        return self._format_string
 
1783
 
 
1784
    def __call__(self):
 
1785
        """Used for network_format_registry support."""
 
1786
        return self.get_obj()()
 
1787
 
 
1788
 
1415
1789
class BranchHooks(Hooks):
1416
1790
    """A dictionary mapping hook name to a list of callables for branch hooks.
1417
1791
 
1486
1860
            "multiple hooks installed for transform_fallback_location, "
1487
1861
            "all are called with the url returned from the previous hook."
1488
1862
            "The order is however undefined.", (1, 9), None))
 
1863
        self.create_hook(HookPoint('automatic_tag_name',
 
1864
            "Called to determine an automatic tag name for a revision."
 
1865
            "automatic_tag_name is called with (branch, revision_id) and "
 
1866
            "should return a tag name or None if no tag name could be "
 
1867
            "determined. The first non-None tag name returned will be used.",
 
1868
            (2, 2), None))
 
1869
        self.create_hook(HookPoint('post_branch_init',
 
1870
            "Called after new branch initialization completes. "
 
1871
            "post_branch_init is called with a "
 
1872
            "bzrlib.branch.BranchInitHookParams. "
 
1873
            "Note that init, branch and checkout (both heavyweight and "
 
1874
            "lightweight) will all trigger this hook.", (2, 2), None))
 
1875
        self.create_hook(HookPoint('post_switch',
 
1876
            "Called after a checkout switches branch. "
 
1877
            "post_switch is called with a "
 
1878
            "bzrlib.branch.SwitchHookParams.", (2, 2), None))
 
1879
 
1489
1880
 
1490
1881
 
1491
1882
# install the default hooks into the Branch class.
1530
1921
            self.old_revno, self.old_revid, self.new_revno, self.new_revid)
1531
1922
 
1532
1923
 
 
1924
class BranchInitHookParams(object):
 
1925
    """Object holding parameters passed to *_branch_init hooks.
 
1926
 
 
1927
    There are 4 fields that hooks may wish to access:
 
1928
 
 
1929
    :ivar format: the branch format
 
1930
    :ivar bzrdir: the BzrDir where the branch will be/has been initialized
 
1931
    :ivar name: name of colocated branch, if any (or None)
 
1932
    :ivar branch: the branch created
 
1933
 
 
1934
    Note that for lightweight checkouts, the bzrdir and format fields refer to
 
1935
    the checkout, hence they are different from the corresponding fields in
 
1936
    branch, which refer to the original branch.
 
1937
    """
 
1938
 
 
1939
    def __init__(self, format, a_bzrdir, name, branch):
 
1940
        """Create a group of BranchInitHook parameters.
 
1941
 
 
1942
        :param format: the branch format
 
1943
        :param a_bzrdir: the BzrDir where the branch will be/has been
 
1944
            initialized
 
1945
        :param name: name of colocated branch, if any (or None)
 
1946
        :param branch: the branch created
 
1947
 
 
1948
        Note that for lightweight checkouts, the bzrdir and format fields refer
 
1949
        to the checkout, hence they are different from the corresponding fields
 
1950
        in branch, which refer to the original branch.
 
1951
        """
 
1952
        self.format = format
 
1953
        self.bzrdir = a_bzrdir
 
1954
        self.name = name
 
1955
        self.branch = branch
 
1956
 
 
1957
    def __eq__(self, other):
 
1958
        return self.__dict__ == other.__dict__
 
1959
 
 
1960
    def __repr__(self):
 
1961
        if self.branch:
 
1962
            return "<%s of %s>" % (self.__class__.__name__, self.branch)
 
1963
        else:
 
1964
            return "<%s of format:%s bzrdir:%s>" % (
 
1965
                self.__class__.__name__, self.branch,
 
1966
                self.format, self.bzrdir)
 
1967
 
 
1968
 
 
1969
class SwitchHookParams(object):
 
1970
    """Object holding parameters passed to *_switch hooks.
 
1971
 
 
1972
    There are 4 fields that hooks may wish to access:
 
1973
 
 
1974
    :ivar control_dir: BzrDir of the checkout to change
 
1975
    :ivar to_branch: branch that the checkout is to reference
 
1976
    :ivar force: skip the check for local commits in a heavy checkout
 
1977
    :ivar revision_id: revision ID to switch to (or None)
 
1978
    """
 
1979
 
 
1980
    def __init__(self, control_dir, to_branch, force, revision_id):
 
1981
        """Create a group of SwitchHook parameters.
 
1982
 
 
1983
        :param control_dir: BzrDir of the checkout to change
 
1984
        :param to_branch: branch that the checkout is to reference
 
1985
        :param force: skip the check for local commits in a heavy checkout
 
1986
        :param revision_id: revision ID to switch to (or None)
 
1987
        """
 
1988
        self.control_dir = control_dir
 
1989
        self.to_branch = to_branch
 
1990
        self.force = force
 
1991
        self.revision_id = revision_id
 
1992
 
 
1993
    def __eq__(self, other):
 
1994
        return self.__dict__ == other.__dict__
 
1995
 
 
1996
    def __repr__(self):
 
1997
        return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
 
1998
            self.control_dir, self.to_branch,
 
1999
            self.revision_id)
 
2000
 
 
2001
 
1533
2002
class BzrBranchFormat4(BranchFormat):
1534
2003
    """Bzr branch format 4.
1535
2004
 
1542
2011
        """See BranchFormat.get_format_description()."""
1543
2012
        return "Branch format 4"
1544
2013
 
1545
 
    def initialize(self, a_bzrdir):
 
2014
    def initialize(self, a_bzrdir, name=None):
1546
2015
        """Create a branch of this format in a_bzrdir."""
1547
2016
        utf8_files = [('revision-history', ''),
1548
2017
                      ('branch-name', ''),
1549
2018
                      ]
1550
 
        return self._initialize_helper(a_bzrdir, utf8_files,
 
2019
        return self._initialize_helper(a_bzrdir, utf8_files, name=name,
1551
2020
                                       lock_type='branch4', set_format=False)
1552
2021
 
1553
2022
    def __init__(self):
1558
2027
        """The network name for this format is the control dirs disk label."""
1559
2028
        return self._matchingbzrdir.get_format_string()
1560
2029
 
1561
 
    def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
 
2030
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
1562
2031
        """See BranchFormat.open()."""
1563
2032
        if not _found:
1564
2033
            # we are being called directly and must probe.
1566
2035
        return BzrBranch(_format=self,
1567
2036
                         _control_files=a_bzrdir._control_files,
1568
2037
                         a_bzrdir=a_bzrdir,
 
2038
                         name=name,
1569
2039
                         _repository=a_bzrdir.open_repository())
1570
2040
 
1571
2041
    def __str__(self):
1586
2056
        """
1587
2057
        return self.get_format_string()
1588
2058
 
1589
 
    def open(self, a_bzrdir, _found=False, ignore_fallbacks=False):
 
2059
    def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):
1590
2060
        """See BranchFormat.open()."""
1591
2061
        if not _found:
1592
 
            format = BranchFormat.find_format(a_bzrdir)
 
2062
            format = BranchFormat.find_format(a_bzrdir, name=name)
1593
2063
            if format.__class__ != self.__class__:
1594
2064
                raise AssertionError("wrong format %r found for %r" %
1595
2065
                    (format, self))
 
2066
        transport = a_bzrdir.get_branch_transport(None, name=name)
1596
2067
        try:
1597
 
            transport = a_bzrdir.get_branch_transport(None)
1598
2068
            control_files = lockable_files.LockableFiles(transport, 'lock',
1599
2069
                                                         lockdir.LockDir)
1600
2070
            return self._branch_class()(_format=self,
1601
2071
                              _control_files=control_files,
 
2072
                              name=name,
1602
2073
                              a_bzrdir=a_bzrdir,
1603
2074
                              _repository=a_bzrdir.find_repository(),
1604
2075
                              ignore_fallbacks=ignore_fallbacks)
1605
2076
        except errors.NoSuchFile:
1606
 
            raise errors.NotBranchError(path=transport.base)
 
2077
            raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
1607
2078
 
1608
2079
    def __init__(self):
1609
2080
        super(BranchFormatMetadir, self).__init__()
1638
2109
        """See BranchFormat.get_format_description()."""
1639
2110
        return "Branch format 5"
1640
2111
 
1641
 
    def initialize(self, a_bzrdir):
 
2112
    def initialize(self, a_bzrdir, name=None):
1642
2113
        """Create a branch of this format in a_bzrdir."""
1643
2114
        utf8_files = [('revision-history', ''),
1644
2115
                      ('branch-name', ''),
1645
2116
                      ]
1646
 
        return self._initialize_helper(a_bzrdir, utf8_files)
 
2117
        return self._initialize_helper(a_bzrdir, utf8_files, name)
1647
2118
 
1648
2119
    def supports_tags(self):
1649
2120
        return False
1671
2142
        """See BranchFormat.get_format_description()."""
1672
2143
        return "Branch format 6"
1673
2144
 
1674
 
    def initialize(self, a_bzrdir):
1675
 
        """Create a branch of this format in a_bzrdir."""
1676
 
        utf8_files = [('last-revision', '0 null:\n'),
1677
 
                      ('branch.conf', ''),
1678
 
                      ('tags', ''),
1679
 
                      ]
1680
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1681
 
 
1682
 
    def make_tags(self, branch):
1683
 
        """See bzrlib.branch.BranchFormat.make_tags()."""
1684
 
        return BasicTags(branch)
1685
 
 
1686
 
 
1687
 
 
1688
 
class BzrBranchFormat7(BranchFormatMetadir):
 
2145
    def initialize(self, a_bzrdir, name=None):
 
2146
        """Create a branch of this format in a_bzrdir."""
 
2147
        utf8_files = [('last-revision', '0 null:\n'),
 
2148
                      ('branch.conf', ''),
 
2149
                      ('tags', ''),
 
2150
                      ]
 
2151
        return self._initialize_helper(a_bzrdir, utf8_files, name)
 
2152
 
 
2153
    def make_tags(self, branch):
 
2154
        """See bzrlib.branch.BranchFormat.make_tags()."""
 
2155
        return BasicTags(branch)
 
2156
 
 
2157
    def supports_set_append_revisions_only(self):
 
2158
        return True
 
2159
 
 
2160
 
 
2161
class BzrBranchFormat8(BranchFormatMetadir):
 
2162
    """Metadir format supporting storing locations of subtree branches."""
 
2163
 
 
2164
    def _branch_class(self):
 
2165
        return BzrBranch8
 
2166
 
 
2167
    def get_format_string(self):
 
2168
        """See BranchFormat.get_format_string()."""
 
2169
        return "Bazaar Branch Format 8 (needs bzr 1.15)\n"
 
2170
 
 
2171
    def get_format_description(self):
 
2172
        """See BranchFormat.get_format_description()."""
 
2173
        return "Branch format 8"
 
2174
 
 
2175
    def initialize(self, a_bzrdir, name=None):
 
2176
        """Create a branch of this format in a_bzrdir."""
 
2177
        utf8_files = [('last-revision', '0 null:\n'),
 
2178
                      ('branch.conf', ''),
 
2179
                      ('tags', ''),
 
2180
                      ('references', '')
 
2181
                      ]
 
2182
        return self._initialize_helper(a_bzrdir, utf8_files, name)
 
2183
 
 
2184
    def __init__(self):
 
2185
        super(BzrBranchFormat8, self).__init__()
 
2186
        self._matchingbzrdir.repository_format = \
 
2187
            RepositoryFormatKnitPack5RichRoot()
 
2188
 
 
2189
    def make_tags(self, branch):
 
2190
        """See bzrlib.branch.BranchFormat.make_tags()."""
 
2191
        return BasicTags(branch)
 
2192
 
 
2193
    def supports_set_append_revisions_only(self):
 
2194
        return True
 
2195
 
 
2196
    def supports_stacking(self):
 
2197
        return True
 
2198
 
 
2199
    supports_reference_locations = True
 
2200
 
 
2201
 
 
2202
class BzrBranchFormat7(BzrBranchFormat8):
1689
2203
    """Branch format with last-revision, tags, and a stacked location pointer.
1690
2204
 
1691
2205
    The stacked location pointer is passed down to the repository and requires
1694
2208
    This format was introduced in bzr 1.6.
1695
2209
    """
1696
2210
 
 
2211
    def initialize(self, a_bzrdir, name=None):
 
2212
        """Create a branch of this format in a_bzrdir."""
 
2213
        utf8_files = [('last-revision', '0 null:\n'),
 
2214
                      ('branch.conf', ''),
 
2215
                      ('tags', ''),
 
2216
                      ]
 
2217
        return self._initialize_helper(a_bzrdir, utf8_files, name)
 
2218
 
1697
2219
    def _branch_class(self):
1698
2220
        return BzrBranch7
1699
2221
 
1705
2227
        """See BranchFormat.get_format_description()."""
1706
2228
        return "Branch format 7"
1707
2229
 
1708
 
    def initialize(self, a_bzrdir):
1709
 
        """Create a branch of this format in a_bzrdir."""
1710
 
        utf8_files = [('last-revision', '0 null:\n'),
1711
 
                      ('branch.conf', ''),
1712
 
                      ('tags', ''),
1713
 
                      ]
1714
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1715
 
 
1716
 
    def __init__(self):
1717
 
        super(BzrBranchFormat7, self).__init__()
1718
 
        self._matchingbzrdir.repository_format = \
1719
 
            RepositoryFormatKnitPack5RichRoot()
1720
 
 
1721
 
    def make_tags(self, branch):
1722
 
        """See bzrlib.branch.BranchFormat.make_tags()."""
1723
 
        return BasicTags(branch)
1724
 
 
1725
 
    def supports_stacking(self):
 
2230
    def supports_set_append_revisions_only(self):
1726
2231
        return True
1727
2232
 
 
2233
    supports_reference_locations = False
 
2234
 
1728
2235
 
1729
2236
class BranchReferenceFormat(BranchFormat):
1730
2237
    """Bzr branch reference format.
1745
2252
        """See BranchFormat.get_format_description()."""
1746
2253
        return "Checkout reference format 1"
1747
2254
 
1748
 
    def get_reference(self, a_bzrdir):
 
2255
    def get_reference(self, a_bzrdir, name=None):
1749
2256
        """See BranchFormat.get_reference()."""
1750
 
        transport = a_bzrdir.get_branch_transport(None)
1751
 
        return transport.get('location').read()
 
2257
        transport = a_bzrdir.get_branch_transport(None, name=name)
 
2258
        return transport.get_bytes('location')
1752
2259
 
1753
 
    def set_reference(self, a_bzrdir, to_branch):
 
2260
    def set_reference(self, a_bzrdir, name, to_branch):
1754
2261
        """See BranchFormat.set_reference()."""
1755
 
        transport = a_bzrdir.get_branch_transport(None)
 
2262
        transport = a_bzrdir.get_branch_transport(None, name=name)
1756
2263
        location = transport.put_bytes('location', to_branch.base)
1757
2264
 
1758
 
    def initialize(self, a_bzrdir, target_branch=None):
 
2265
    def initialize(self, a_bzrdir, name=None, target_branch=None):
1759
2266
        """Create a branch of this format in a_bzrdir."""
1760
2267
        if target_branch is None:
1761
2268
            # this format does not implement branch itself, thus the implicit
1762
2269
            # creation contract must see it as uninitializable
1763
2270
            raise errors.UninitializableFormat(self)
1764
 
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
1765
 
        branch_transport = a_bzrdir.get_branch_transport(self)
 
2271
        mutter('creating branch reference in %s', a_bzrdir.user_url)
 
2272
        branch_transport = a_bzrdir.get_branch_transport(self, name=name)
1766
2273
        branch_transport.put_bytes('location',
1767
 
            target_branch.bzrdir.root_transport.base)
 
2274
            target_branch.bzrdir.user_url)
1768
2275
        branch_transport.put_bytes('format', self.get_format_string())
1769
 
        return self.open(
1770
 
            a_bzrdir, _found=True,
 
2276
        branch = self.open(
 
2277
            a_bzrdir, name, _found=True,
1771
2278
            possible_transports=[target_branch.bzrdir.root_transport])
 
2279
        self._run_post_branch_init_hooks(a_bzrdir, name, branch)
 
2280
        return branch
1772
2281
 
1773
2282
    def __init__(self):
1774
2283
        super(BranchReferenceFormat, self).__init__()
1780
2289
        def clone(to_bzrdir, revision_id=None,
1781
2290
            repository_policy=None):
1782
2291
            """See Branch.clone()."""
1783
 
            return format.initialize(to_bzrdir, a_branch)
 
2292
            return format.initialize(to_bzrdir, target_branch=a_branch)
1784
2293
            # cannot obey revision_id limits when cloning a reference ...
1785
2294
            # FIXME RBC 20060210 either nuke revision_id for clone, or
1786
2295
            # emit some sort of warning/error to the caller ?!
1787
2296
        return clone
1788
2297
 
1789
 
    def open(self, a_bzrdir, _found=False, location=None,
 
2298
    def open(self, a_bzrdir, name=None, _found=False, location=None,
1790
2299
             possible_transports=None, ignore_fallbacks=False):
1791
2300
        """Return the branch that the branch reference in a_bzrdir points at.
1792
2301
 
1793
2302
        :param a_bzrdir: A BzrDir that contains a branch.
 
2303
        :param name: Name of colocated branch to open, if any
1794
2304
        :param _found: a private parameter, do not use it. It is used to
1795
2305
            indicate if format probing has already be done.
1796
2306
        :param ignore_fallbacks: when set, no fallback branches will be opened
1801
2311
        :param possible_transports: An optional reusable transports list.
1802
2312
        """
1803
2313
        if not _found:
1804
 
            format = BranchFormat.find_format(a_bzrdir)
 
2314
            format = BranchFormat.find_format(a_bzrdir, name=name)
1805
2315
            if format.__class__ != self.__class__:
1806
2316
                raise AssertionError("wrong format %r found for %r" %
1807
2317
                    (format, self))
1808
2318
        if location is None:
1809
 
            location = self.get_reference(a_bzrdir)
 
2319
            location = self.get_reference(a_bzrdir, name)
1810
2320
        real_bzrdir = bzrdir.BzrDir.open(
1811
2321
            location, possible_transports=possible_transports)
1812
 
        result = real_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
 
2322
        result = real_bzrdir.open_branch(name=name, 
 
2323
            ignore_fallbacks=ignore_fallbacks)
1813
2324
        # this changes the behaviour of result.clone to create a new reference
1814
2325
        # rather than a copy of the content of the branch.
1815
2326
        # I did not use a proxy object because that needs much more extensive
1836
2347
__format5 = BzrBranchFormat5()
1837
2348
__format6 = BzrBranchFormat6()
1838
2349
__format7 = BzrBranchFormat7()
 
2350
__format8 = BzrBranchFormat8()
1839
2351
BranchFormat.register_format(__format5)
1840
2352
BranchFormat.register_format(BranchReferenceFormat())
1841
2353
BranchFormat.register_format(__format6)
1842
2354
BranchFormat.register_format(__format7)
1843
 
BranchFormat.set_default_format(__format6)
 
2355
BranchFormat.register_format(__format8)
 
2356
BranchFormat.set_default_format(__format7)
1844
2357
_legacy_formats = [BzrBranchFormat4(),
1845
2358
    ]
1846
2359
network_format_registry.register(
1847
2360
    _legacy_formats[0].network_name(), _legacy_formats[0].__class__)
1848
2361
 
1849
2362
 
1850
 
class BzrBranch(Branch):
 
2363
class BranchWriteLockResult(LogicalLockResult):
 
2364
    """The result of write locking a branch.
 
2365
 
 
2366
    :ivar branch_token: The token obtained from the underlying branch lock, or
 
2367
        None.
 
2368
    :ivar unlock: A callable which will unlock the lock.
 
2369
    """
 
2370
 
 
2371
    def __init__(self, unlock, branch_token):
 
2372
        LogicalLockResult.__init__(self, unlock)
 
2373
        self.branch_token = branch_token
 
2374
 
 
2375
    def __repr__(self):
 
2376
        return "BranchWriteLockResult(%s, %s)" % (self.branch_token,
 
2377
            self.unlock)
 
2378
 
 
2379
 
 
2380
class BzrBranch(Branch, _RelockDebugMixin):
1851
2381
    """A branch stored in the actual filesystem.
1852
2382
 
1853
2383
    Note that it's "local" in the context of the filesystem; it doesn't
1859
2389
    :ivar repository: Repository for this branch.
1860
2390
    :ivar base: The url of the base directory for this branch; the one
1861
2391
        containing the .bzr directory.
 
2392
    :ivar name: Optional colocated branch name as it exists in the control
 
2393
        directory.
1862
2394
    """
1863
2395
 
1864
2396
    def __init__(self, _format=None,
1865
 
                 _control_files=None, a_bzrdir=None, _repository=None,
1866
 
                 ignore_fallbacks=False):
 
2397
                 _control_files=None, a_bzrdir=None, name=None,
 
2398
                 _repository=None, ignore_fallbacks=False):
1867
2399
        """Create new branch object at a particular location."""
1868
2400
        if a_bzrdir is None:
1869
2401
            raise ValueError('a_bzrdir must be supplied')
1870
2402
        else:
1871
2403
            self.bzrdir = a_bzrdir
1872
2404
        self._base = self.bzrdir.transport.clone('..').base
 
2405
        self.name = name
1873
2406
        # XXX: We should be able to just do
1874
2407
        #   self.base = self.bzrdir.root_transport.base
1875
2408
        # but this does not quite work yet -- mbp 20080522
1882
2415
        Branch.__init__(self)
1883
2416
 
1884
2417
    def __str__(self):
1885
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
2418
        if self.name is None:
 
2419
            return '%s(%s)' % (self.__class__.__name__, self.user_url)
 
2420
        else:
 
2421
            return '%s(%s,%s)' % (self.__class__.__name__, self.user_url,
 
2422
                self.name)
1886
2423
 
1887
2424
    __repr__ = __str__
1888
2425
 
1899
2436
        return self.control_files.is_locked()
1900
2437
 
1901
2438
    def lock_write(self, token=None):
1902
 
        repo_token = self.repository.lock_write()
 
2439
        """Lock the branch for write operations.
 
2440
 
 
2441
        :param token: A token to permit reacquiring a previously held and
 
2442
            preserved lock.
 
2443
        :return: A BranchWriteLockResult.
 
2444
        """
 
2445
        if not self.is_locked():
 
2446
            self._note_lock('w')
 
2447
        # All-in-one needs to always unlock/lock.
 
2448
        repo_control = getattr(self.repository, 'control_files', None)
 
2449
        if self.control_files == repo_control or not self.is_locked():
 
2450
            self.repository._warn_if_deprecated(self)
 
2451
            self.repository.lock_write()
 
2452
            took_lock = True
 
2453
        else:
 
2454
            took_lock = False
1903
2455
        try:
1904
 
            token = self.control_files.lock_write(token=token)
 
2456
            return BranchWriteLockResult(self.unlock,
 
2457
                self.control_files.lock_write(token=token))
1905
2458
        except:
1906
 
            self.repository.unlock()
 
2459
            if took_lock:
 
2460
                self.repository.unlock()
1907
2461
            raise
1908
 
        return token
1909
2462
 
1910
2463
    def lock_read(self):
1911
 
        self.repository.lock_read()
 
2464
        """Lock the branch for read operations.
 
2465
 
 
2466
        :return: A bzrlib.lock.LogicalLockResult.
 
2467
        """
 
2468
        if not self.is_locked():
 
2469
            self._note_lock('r')
 
2470
        # All-in-one needs to always unlock/lock.
 
2471
        repo_control = getattr(self.repository, 'control_files', None)
 
2472
        if self.control_files == repo_control or not self.is_locked():
 
2473
            self.repository._warn_if_deprecated(self)
 
2474
            self.repository.lock_read()
 
2475
            took_lock = True
 
2476
        else:
 
2477
            took_lock = False
1912
2478
        try:
1913
2479
            self.control_files.lock_read()
 
2480
            return LogicalLockResult(self.unlock)
1914
2481
        except:
1915
 
            self.repository.unlock()
 
2482
            if took_lock:
 
2483
                self.repository.unlock()
1916
2484
            raise
1917
2485
 
 
2486
    @only_raises(errors.LockNotHeld, errors.LockBroken)
1918
2487
    def unlock(self):
1919
 
        # TODO: test for failed two phase locks. This is known broken.
1920
2488
        try:
1921
2489
            self.control_files.unlock()
1922
2490
        finally:
1923
 
            self.repository.unlock()
1924
 
        if not self.control_files.is_locked():
1925
 
            # we just released the lock
1926
 
            self._clear_cached_state()
 
2491
            # All-in-one needs to always unlock/lock.
 
2492
            repo_control = getattr(self.repository, 'control_files', None)
 
2493
            if (self.control_files == repo_control or
 
2494
                not self.control_files.is_locked()):
 
2495
                self.repository.unlock()
 
2496
            if not self.control_files.is_locked():
 
2497
                # we just released the lock
 
2498
                self._clear_cached_state()
1927
2499
 
1928
2500
    def peek_lock_mode(self):
1929
2501
        if self.control_files._lock_count == 0:
2048
2620
        """See Branch.basis_tree."""
2049
2621
        return self.repository.revision_tree(self.last_revision())
2050
2622
 
2051
 
    @needs_write_lock
2052
 
    def pull(self, source, overwrite=False, stop_revision=None,
2053
 
             _hook_master=None, run_hooks=True, possible_transports=None,
2054
 
             _override_hook_target=None):
2055
 
        """See Branch.pull.
2056
 
 
2057
 
        :param _hook_master: Private parameter - set the branch to
2058
 
            be supplied as the master to pull hooks.
2059
 
        :param run_hooks: Private parameter - if false, this branch
2060
 
            is being called because it's the master of the primary branch,
2061
 
            so it should not run its hooks.
2062
 
        :param _override_hook_target: Private parameter - set the branch to be
2063
 
            supplied as the target_branch to pull hooks.
2064
 
        """
2065
 
        result = PullResult()
2066
 
        result.source_branch = source
2067
 
        if _override_hook_target is None:
2068
 
            result.target_branch = self
2069
 
        else:
2070
 
            result.target_branch = _override_hook_target
2071
 
        source.lock_read()
2072
 
        try:
2073
 
            # We assume that during 'pull' the local repository is closer than
2074
 
            # the remote one.
2075
 
            graph = self.repository.get_graph(source.repository)
2076
 
            result.old_revno, result.old_revid = self.last_revision_info()
2077
 
            self.update_revisions(source, stop_revision, overwrite=overwrite,
2078
 
                                  graph=graph)
2079
 
            result.tag_conflicts = source.tags.merge_to(self.tags, overwrite)
2080
 
            result.new_revno, result.new_revid = self.last_revision_info()
2081
 
            if _hook_master:
2082
 
                result.master_branch = _hook_master
2083
 
                result.local_branch = result.target_branch
2084
 
            else:
2085
 
                result.master_branch = result.target_branch
2086
 
                result.local_branch = None
2087
 
            if run_hooks:
2088
 
                for hook in Branch.hooks['post_pull']:
2089
 
                    hook(result)
2090
 
        finally:
2091
 
            source.unlock()
2092
 
        return result
2093
 
 
2094
2623
    def _get_parent_location(self):
2095
2624
        _locs = ['parent', 'pull', 'x-pull']
2096
2625
        for l in _locs:
2100
2629
                pass
2101
2630
        return None
2102
2631
 
2103
 
    @needs_read_lock
2104
 
    def push(self, target, overwrite=False, stop_revision=None,
2105
 
             _override_hook_source_branch=None):
2106
 
        """See Branch.push.
2107
 
 
2108
 
        This is the basic concrete implementation of push()
2109
 
 
2110
 
        :param _override_hook_source_branch: If specified, run
2111
 
        the hooks passing this Branch as the source, rather than self.
2112
 
        This is for use of RemoteBranch, where push is delegated to the
2113
 
        underlying vfs-based Branch.
2114
 
        """
2115
 
        # TODO: Public option to disable running hooks - should be trivial but
2116
 
        # needs tests.
2117
 
        return _run_with_write_locked_target(
2118
 
            target, self._push_with_bound_branches, target, overwrite,
2119
 
            stop_revision,
2120
 
            _override_hook_source_branch=_override_hook_source_branch)
2121
 
 
2122
 
    def _push_with_bound_branches(self, target, overwrite,
2123
 
            stop_revision,
2124
 
            _override_hook_source_branch=None):
2125
 
        """Push from self into target, and into target's master if any.
2126
 
 
2127
 
        This is on the base BzrBranch class even though it doesn't support
2128
 
        bound branches because the *target* might be bound.
2129
 
        """
2130
 
        def _run_hooks():
2131
 
            if _override_hook_source_branch:
2132
 
                result.source_branch = _override_hook_source_branch
2133
 
            for hook in Branch.hooks['post_push']:
2134
 
                hook(result)
2135
 
 
2136
 
        bound_location = target.get_bound_location()
2137
 
        if bound_location and target.base != bound_location:
2138
 
            # there is a master branch.
2139
 
            #
2140
 
            # XXX: Why the second check?  Is it even supported for a branch to
2141
 
            # be bound to itself? -- mbp 20070507
2142
 
            master_branch = target.get_master_branch()
2143
 
            master_branch.lock_write()
2144
 
            try:
2145
 
                # push into the master from this branch.
2146
 
                self._basic_push(master_branch, overwrite, stop_revision)
2147
 
                # and push into the target branch from this. Note that we push from
2148
 
                # this branch again, because its considered the highest bandwidth
2149
 
                # repository.
2150
 
                result = self._basic_push(target, overwrite, stop_revision)
2151
 
                result.master_branch = master_branch
2152
 
                result.local_branch = target
2153
 
                _run_hooks()
2154
 
                return result
2155
 
            finally:
2156
 
                master_branch.unlock()
2157
 
        else:
2158
 
            # no master branch
2159
 
            result = self._basic_push(target, overwrite, stop_revision)
2160
 
            # TODO: Why set master_branch and local_branch if there's no
2161
 
            # binding?  Maybe cleaner to just leave them unset? -- mbp
2162
 
            # 20070504
2163
 
            result.master_branch = target
2164
 
            result.local_branch = None
2165
 
            _run_hooks()
2166
 
            return result
2167
 
 
2168
2632
    def _basic_push(self, target, overwrite, stop_revision):
2169
2633
        """Basic implementation of push without bound branches or hooks.
2170
2634
 
2171
 
        Must be called with self read locked and target write locked.
 
2635
        Must be called with source read locked and target write locked.
2172
2636
        """
2173
2637
        result = BranchPushResult()
2174
2638
        result.source_branch = self
2175
2639
        result.target_branch = target
2176
2640
        result.old_revno, result.old_revid = target.last_revision_info()
 
2641
        self.update_references(target)
2177
2642
        if result.old_revid != self.last_revision():
2178
2643
            # We assume that during 'push' this repository is closer than
2179
2644
            # the target.
2180
2645
            graph = self.repository.get_graph(target.repository)
2181
 
            target.update_revisions(self, stop_revision, overwrite=overwrite,
2182
 
                                    graph=graph)
 
2646
            target.update_revisions(self, stop_revision,
 
2647
                overwrite=overwrite, graph=graph)
2183
2648
        if self._push_should_merge_tags():
2184
 
            result.tag_conflicts = self.tags.merge_to(target.tags, overwrite)
 
2649
            result.tag_conflicts = self.tags.merge_to(target.tags,
 
2650
                overwrite)
2185
2651
        result.new_revno, result.new_revid = target.last_revision_info()
2186
2652
        return result
2187
2653
 
2188
2654
    def get_stacked_on_url(self):
2189
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
2655
        raise errors.UnstackableBranchFormat(self._format, self.user_url)
2190
2656
 
2191
2657
    def set_push_location(self, location):
2192
2658
        """See Branch.set_push_location."""
2194
2660
            'push_location', location,
2195
2661
            store=_mod_config.STORE_LOCATION_NORECURSE)
2196
2662
 
2197
 
    @needs_write_lock
2198
 
    def set_parent(self, url):
2199
 
        """See Branch.set_parent."""
2200
 
        # TODO: Maybe delete old location files?
2201
 
        # URLs should never be unicode, even on the local fs,
2202
 
        # FIXUP this and get_parent in a future branch format bump:
2203
 
        # read and rewrite the file. RBC 20060125
2204
 
        if url is not None:
2205
 
            if isinstance(url, unicode):
2206
 
                try:
2207
 
                    url = url.encode('ascii')
2208
 
                except UnicodeEncodeError:
2209
 
                    raise errors.InvalidURL(url,
2210
 
                        "Urls must be 7-bit ascii, "
2211
 
                        "use bzrlib.urlutils.escape")
2212
 
            url = urlutils.relative_url(self.base, url)
2213
 
        self._set_parent_location(url)
2214
 
 
2215
2663
    def _set_parent_location(self, url):
2216
2664
        if url is None:
2217
2665
            self._transport.delete('parent')
2226
2674
    It has support for a master_branch which is the data for bound branches.
2227
2675
    """
2228
2676
 
2229
 
    @needs_write_lock
2230
 
    def pull(self, source, overwrite=False, stop_revision=None,
2231
 
             run_hooks=True, possible_transports=None,
2232
 
             _override_hook_target=None):
2233
 
        """Pull from source into self, updating my master if any.
2234
 
 
2235
 
        :param run_hooks: Private parameter - if false, this branch
2236
 
            is being called because it's the master of the primary branch,
2237
 
            so it should not run its hooks.
2238
 
        """
2239
 
        bound_location = self.get_bound_location()
2240
 
        master_branch = None
2241
 
        if bound_location and source.base != bound_location:
2242
 
            # not pulling from master, so we need to update master.
2243
 
            master_branch = self.get_master_branch(possible_transports)
2244
 
            master_branch.lock_write()
2245
 
        try:
2246
 
            if master_branch:
2247
 
                # pull from source into master.
2248
 
                master_branch.pull(source, overwrite, stop_revision,
2249
 
                    run_hooks=False)
2250
 
            return super(BzrBranch5, self).pull(source, overwrite,
2251
 
                stop_revision, _hook_master=master_branch,
2252
 
                run_hooks=run_hooks,
2253
 
                _override_hook_target=_override_hook_target)
2254
 
        finally:
2255
 
            if master_branch:
2256
 
                master_branch.unlock()
2257
 
 
2258
2677
    def get_bound_location(self):
2259
2678
        try:
2260
2679
            return self._transport.get_bytes('bound')[:-1]
2347
2766
        return None
2348
2767
 
2349
2768
 
2350
 
class BzrBranch7(BzrBranch5):
2351
 
    """A branch with support for a fallback repository."""
 
2769
class BzrBranch8(BzrBranch5):
 
2770
    """A branch that stores tree-reference locations."""
2352
2771
 
2353
2772
    def _open_hook(self):
2354
2773
        if self._ignore_fallbacks:
2370
2789
 
2371
2790
    def __init__(self, *args, **kwargs):
2372
2791
        self._ignore_fallbacks = kwargs.get('ignore_fallbacks', False)
2373
 
        super(BzrBranch7, self).__init__(*args, **kwargs)
 
2792
        super(BzrBranch8, self).__init__(*args, **kwargs)
2374
2793
        self._last_revision_info_cache = None
2375
 
        self._partial_revision_history_cache = []
 
2794
        self._reference_info = None
2376
2795
 
2377
2796
    def _clear_cached_state(self):
2378
 
        super(BzrBranch7, self)._clear_cached_state()
 
2797
        super(BzrBranch8, self)._clear_cached_state()
2379
2798
        self._last_revision_info_cache = None
2380
 
        self._partial_revision_history_cache = []
 
2799
        self._reference_info = None
2381
2800
 
2382
2801
    def _last_revision_info(self):
2383
2802
        revision_string = self._transport.get_bytes('last-revision')
2429
2848
        if _mod_revision.is_null(last_revision):
2430
2849
            return
2431
2850
        if last_revision not in self._lefthand_history(revision_id):
2432
 
            raise errors.AppendRevisionsOnlyViolation(self.base)
 
2851
            raise errors.AppendRevisionsOnlyViolation(self.user_url)
2433
2852
 
2434
2853
    def _gen_revision_history(self):
2435
2854
        """Generate the revision history from last revision
2438
2857
        self._extend_partial_history(stop_index=last_revno-1)
2439
2858
        return list(reversed(self._partial_revision_history_cache))
2440
2859
 
2441
 
    def _extend_partial_history(self, stop_index=None, stop_revision=None):
2442
 
        """Extend the partial history to include a given index
2443
 
 
2444
 
        If a stop_index is supplied, stop when that index has been reached.
2445
 
        If a stop_revision is supplied, stop when that revision is
2446
 
        encountered.  Otherwise, stop when the beginning of history is
2447
 
        reached.
2448
 
 
2449
 
        :param stop_index: The index which should be present.  When it is
2450
 
            present, history extension will stop.
2451
 
        :param revision_id: The revision id which should be present.  When
2452
 
            it is encountered, history extension will stop.
2453
 
        """
2454
 
        repo = self.repository
2455
 
        if len(self._partial_revision_history_cache) == 0:
2456
 
            iterator = repo.iter_reverse_revision_history(self.last_revision())
2457
 
        else:
2458
 
            start_revision = self._partial_revision_history_cache[-1]
2459
 
            iterator = repo.iter_reverse_revision_history(start_revision)
2460
 
            #skip the last revision in the list
2461
 
            next_revision = iterator.next()
2462
 
        for revision_id in iterator:
2463
 
            self._partial_revision_history_cache.append(revision_id)
2464
 
            if (stop_index is not None and
2465
 
                len(self._partial_revision_history_cache) > stop_index):
2466
 
                break
2467
 
            if revision_id == stop_revision:
2468
 
                break
2469
 
 
2470
2860
    def _write_revision_history(self, history):
2471
2861
        """Factored out of set_revision_history.
2472
2862
 
2493
2883
        """Set the parent branch"""
2494
2884
        return self._get_config_location('parent_location')
2495
2885
 
 
2886
    @needs_write_lock
 
2887
    def _set_all_reference_info(self, info_dict):
 
2888
        """Replace all reference info stored in a branch.
 
2889
 
 
2890
        :param info_dict: A dict of {file_id: (tree_path, branch_location)}
 
2891
        """
 
2892
        s = StringIO()
 
2893
        writer = rio.RioWriter(s)
 
2894
        for key, (tree_path, branch_location) in info_dict.iteritems():
 
2895
            stanza = rio.Stanza(file_id=key, tree_path=tree_path,
 
2896
                                branch_location=branch_location)
 
2897
            writer.write_stanza(stanza)
 
2898
        self._transport.put_bytes('references', s.getvalue())
 
2899
        self._reference_info = info_dict
 
2900
 
 
2901
    @needs_read_lock
 
2902
    def _get_all_reference_info(self):
 
2903
        """Return all the reference info stored in a branch.
 
2904
 
 
2905
        :return: A dict of {file_id: (tree_path, branch_location)}
 
2906
        """
 
2907
        if self._reference_info is not None:
 
2908
            return self._reference_info
 
2909
        rio_file = self._transport.get('references')
 
2910
        try:
 
2911
            stanzas = rio.read_stanzas(rio_file)
 
2912
            info_dict = dict((s['file_id'], (s['tree_path'],
 
2913
                             s['branch_location'])) for s in stanzas)
 
2914
        finally:
 
2915
            rio_file.close()
 
2916
        self._reference_info = info_dict
 
2917
        return info_dict
 
2918
 
 
2919
    def set_reference_info(self, file_id, tree_path, branch_location):
 
2920
        """Set the branch location to use for a tree reference.
 
2921
 
 
2922
        :param file_id: The file-id of the tree reference.
 
2923
        :param tree_path: The path of the tree reference in the tree.
 
2924
        :param branch_location: The location of the branch to retrieve tree
 
2925
            references from.
 
2926
        """
 
2927
        info_dict = self._get_all_reference_info()
 
2928
        info_dict[file_id] = (tree_path, branch_location)
 
2929
        if None in (tree_path, branch_location):
 
2930
            if tree_path is not None:
 
2931
                raise ValueError('tree_path must be None when branch_location'
 
2932
                                 ' is None.')
 
2933
            if branch_location is not None:
 
2934
                raise ValueError('branch_location must be None when tree_path'
 
2935
                                 ' is None.')
 
2936
            del info_dict[file_id]
 
2937
        self._set_all_reference_info(info_dict)
 
2938
 
 
2939
    def get_reference_info(self, file_id):
 
2940
        """Get the tree_path and branch_location for a tree reference.
 
2941
 
 
2942
        :return: a tuple of (tree_path, branch_location)
 
2943
        """
 
2944
        return self._get_all_reference_info().get(file_id, (None, None))
 
2945
 
 
2946
    def reference_parent(self, file_id, path, possible_transports=None):
 
2947
        """Return the parent branch for a tree-reference file_id.
 
2948
 
 
2949
        :param file_id: The file_id of the tree reference
 
2950
        :param path: The path of the file_id in the tree
 
2951
        :return: A branch associated with the file_id
 
2952
        """
 
2953
        branch_location = self.get_reference_info(file_id)[1]
 
2954
        if branch_location is None:
 
2955
            return Branch.reference_parent(self, file_id, path,
 
2956
                                           possible_transports)
 
2957
        branch_location = urlutils.join(self.user_url, branch_location)
 
2958
        return Branch.open(branch_location,
 
2959
                           possible_transports=possible_transports)
 
2960
 
2496
2961
    def set_push_location(self, location):
2497
2962
        """See Branch.set_push_location."""
2498
2963
        self._set_config_location('push_location', location)
2540
3005
            raise errors.NotStacked(self)
2541
3006
        return stacked_url
2542
3007
 
2543
 
    def set_append_revisions_only(self, enabled):
2544
 
        if enabled:
2545
 
            value = 'True'
2546
 
        else:
2547
 
            value = 'False'
2548
 
        self.get_config().set_user_option('append_revisions_only', value,
2549
 
            warn_masked=True)
2550
 
 
2551
3008
    def _get_append_revisions_only(self):
2552
 
        value = self.get_config().get_user_option('append_revisions_only')
2553
 
        return value == 'True'
 
3009
        return self.get_config(
 
3010
            ).get_user_option_as_bool('append_revisions_only')
2554
3011
 
2555
3012
    @needs_write_lock
2556
3013
    def generate_revision_history(self, revision_id, last_rev=None,
2596
3053
        return self.revno() - index
2597
3054
 
2598
3055
 
 
3056
class BzrBranch7(BzrBranch8):
 
3057
    """A branch with support for a fallback repository."""
 
3058
 
 
3059
    def set_reference_info(self, file_id, tree_path, branch_location):
 
3060
        Branch.set_reference_info(self, file_id, tree_path, branch_location)
 
3061
 
 
3062
    def get_reference_info(self, file_id):
 
3063
        Branch.get_reference_info(self, file_id)
 
3064
 
 
3065
    def reference_parent(self, file_id, path, possible_transports=None):
 
3066
        return Branch.reference_parent(self, file_id, path,
 
3067
                                       possible_transports)
 
3068
 
 
3069
 
2599
3070
class BzrBranch6(BzrBranch7):
2600
3071
    """See BzrBranchFormat6 for the capabilities of this branch.
2601
3072
 
2604
3075
    """
2605
3076
 
2606
3077
    def get_stacked_on_url(self):
2607
 
        raise errors.UnstackableBranchFormat(self._format, self.base)
 
3078
        raise errors.UnstackableBranchFormat(self._format, self.user_url)
2608
3079
 
2609
3080
 
2610
3081
######################################################################
2689
3160
 
2690
3161
    def __init__(self, branch):
2691
3162
        self.branch = branch
 
3163
        self.errors = []
2692
3164
 
2693
3165
    def report_results(self, verbose):
2694
3166
        """Report the check results via trace.note.
2696
3168
        :param verbose: Requests more detailed display of what was checked,
2697
3169
            if any.
2698
3170
        """
2699
 
        note('checked branch %s format %s',
2700
 
             self.branch.base,
2701
 
             self.branch._format)
 
3171
        note('checked branch %s format %s', self.branch.user_url,
 
3172
            self.branch._format)
 
3173
        for error in self.errors:
 
3174
            note('found error:%s', error)
2702
3175
 
2703
3176
 
2704
3177
class Converter5to6(object):
2742
3215
        branch._transport.put_bytes('format', format.get_format_string())
2743
3216
 
2744
3217
 
 
3218
class Converter7to8(object):
 
3219
    """Perform an in-place upgrade of format 6 to format 7"""
 
3220
 
 
3221
    def convert(self, branch):
 
3222
        format = BzrBranchFormat8()
 
3223
        branch._transport.put_bytes('references', '')
 
3224
        # update target format
 
3225
        branch._transport.put_bytes('format', format.get_format_string())
 
3226
 
2745
3227
 
2746
3228
def _run_with_write_locked_target(target, callable, *args, **kwargs):
2747
3229
    """Run ``callable(*args, **kwargs)``, write-locking target for the
2787
3269
    _optimisers = []
2788
3270
    """The available optimised InterBranch types."""
2789
3271
 
2790
 
    @staticmethod
2791
 
    def _get_branch_formats_to_test():
2792
 
        """Return a tuple with the Branch formats to use when testing."""
2793
 
        raise NotImplementedError(self._get_branch_formats_to_test)
2794
 
 
 
3272
    @classmethod
 
3273
    def _get_branch_formats_to_test(klass):
 
3274
        """Return an iterable of format tuples for testing.
 
3275
        
 
3276
        :return: An iterable of (from_format, to_format) to use when testing
 
3277
            this InterBranch class. Each InterBranch class should define this
 
3278
            method itself.
 
3279
        """
 
3280
        raise NotImplementedError(klass._get_branch_formats_to_test)
 
3281
 
 
3282
    @needs_write_lock
 
3283
    def pull(self, overwrite=False, stop_revision=None,
 
3284
             possible_transports=None, local=False):
 
3285
        """Mirror source into target branch.
 
3286
 
 
3287
        The target branch is considered to be 'local', having low latency.
 
3288
 
 
3289
        :returns: PullResult instance
 
3290
        """
 
3291
        raise NotImplementedError(self.pull)
 
3292
 
 
3293
    @needs_write_lock
2795
3294
    def update_revisions(self, stop_revision=None, overwrite=False,
2796
3295
                         graph=None):
2797
3296
        """Pull in new perfect-fit revisions.
2805
3304
        """
2806
3305
        raise NotImplementedError(self.update_revisions)
2807
3306
 
 
3307
    @needs_write_lock
 
3308
    def push(self, overwrite=False, stop_revision=None,
 
3309
             _override_hook_source_branch=None):
 
3310
        """Mirror the source branch into the target branch.
 
3311
 
 
3312
        The source branch is considered to be 'local', having low latency.
 
3313
        """
 
3314
        raise NotImplementedError(self.push)
 
3315
 
2808
3316
 
2809
3317
class GenericInterBranch(InterBranch):
2810
 
    """InterBranch implementation that uses public Branch functions.
2811
 
    """
2812
 
 
2813
 
    @staticmethod
2814
 
    def _get_branch_formats_to_test():
2815
 
        return BranchFormat._default_format, BranchFormat._default_format
2816
 
 
 
3318
    """InterBranch implementation that uses public Branch functions."""
 
3319
 
 
3320
    @classmethod
 
3321
    def is_compatible(klass, source, target):
 
3322
        # GenericBranch uses the public API, so always compatible
 
3323
        return True
 
3324
 
 
3325
    @classmethod
 
3326
    def _get_branch_formats_to_test(klass):
 
3327
        return [(BranchFormat._default_format, BranchFormat._default_format)]
 
3328
 
 
3329
    @classmethod
 
3330
    def unwrap_format(klass, format):
 
3331
        if isinstance(format, remote.RemoteBranchFormat):
 
3332
            format._ensure_real()
 
3333
            return format._custom_format
 
3334
        return format                                                                                                  
 
3335
 
 
3336
    @needs_write_lock
 
3337
    def copy_content_into(self, revision_id=None):
 
3338
        """Copy the content of source into target
 
3339
 
 
3340
        revision_id: if not None, the revision history in the new branch will
 
3341
                     be truncated to end with revision_id.
 
3342
        """
 
3343
        self.source.update_references(self.target)
 
3344
        self.source._synchronize_history(self.target, revision_id)
 
3345
        try:
 
3346
            parent = self.source.get_parent()
 
3347
        except errors.InaccessibleParent, e:
 
3348
            mutter('parent was not accessible to copy: %s', e)
 
3349
        else:
 
3350
            if parent:
 
3351
                self.target.set_parent(parent)
 
3352
        if self.source._push_should_merge_tags():
 
3353
            self.source.tags.merge_to(self.target.tags)
 
3354
 
 
3355
    @needs_write_lock
2817
3356
    def update_revisions(self, stop_revision=None, overwrite=False,
2818
3357
        graph=None):
2819
3358
        """See InterBranch.update_revisions()."""
2820
 
        self.source.lock_read()
2821
 
        try:
2822
 
            other_revno, other_last_revision = self.source.last_revision_info()
2823
 
            stop_revno = None # unknown
2824
 
            if stop_revision is None:
2825
 
                stop_revision = other_last_revision
2826
 
                if _mod_revision.is_null(stop_revision):
2827
 
                    # if there are no commits, we're done.
2828
 
                    return
2829
 
                stop_revno = other_revno
2830
 
 
2831
 
            # what's the current last revision, before we fetch [and change it
2832
 
            # possibly]
2833
 
            last_rev = _mod_revision.ensure_null(self.target.last_revision())
2834
 
            # we fetch here so that we don't process data twice in the common
2835
 
            # case of having something to pull, and so that the check for
2836
 
            # already merged can operate on the just fetched graph, which will
2837
 
            # be cached in memory.
2838
 
            self.target.fetch(self.source, stop_revision)
2839
 
            # Check to see if one is an ancestor of the other
2840
 
            if not overwrite:
2841
 
                if graph is None:
2842
 
                    graph = self.target.repository.get_graph()
2843
 
                if self.target._check_if_descendant_or_diverged(
2844
 
                        stop_revision, last_rev, graph, self.source):
2845
 
                    # stop_revision is a descendant of last_rev, but we aren't
2846
 
                    # overwriting, so we're done.
2847
 
                    return
2848
 
            if stop_revno is None:
2849
 
                if graph is None:
2850
 
                    graph = self.target.repository.get_graph()
2851
 
                this_revno, this_last_revision = \
2852
 
                        self.target.last_revision_info()
2853
 
                stop_revno = graph.find_distance_to_null(stop_revision,
2854
 
                                [(other_last_revision, other_revno),
2855
 
                                 (this_last_revision, this_revno)])
2856
 
            self.target.set_last_revision_info(stop_revno, stop_revision)
2857
 
        finally:
2858
 
            self.source.unlock()
2859
 
 
2860
 
    @classmethod
2861
 
    def is_compatible(self, source, target):
2862
 
        # GenericBranch uses the public API, so always compatible
2863
 
        return True
 
3359
        other_revno, other_last_revision = self.source.last_revision_info()
 
3360
        stop_revno = None # unknown
 
3361
        if stop_revision is None:
 
3362
            stop_revision = other_last_revision
 
3363
            if _mod_revision.is_null(stop_revision):
 
3364
                # if there are no commits, we're done.
 
3365
                return
 
3366
            stop_revno = other_revno
 
3367
 
 
3368
        # what's the current last revision, before we fetch [and change it
 
3369
        # possibly]
 
3370
        last_rev = _mod_revision.ensure_null(self.target.last_revision())
 
3371
        # we fetch here so that we don't process data twice in the common
 
3372
        # case of having something to pull, and so that the check for
 
3373
        # already merged can operate on the just fetched graph, which will
 
3374
        # be cached in memory.
 
3375
        self.target.fetch(self.source, stop_revision)
 
3376
        # Check to see if one is an ancestor of the other
 
3377
        if not overwrite:
 
3378
            if graph is None:
 
3379
                graph = self.target.repository.get_graph()
 
3380
            if self.target._check_if_descendant_or_diverged(
 
3381
                    stop_revision, last_rev, graph, self.source):
 
3382
                # stop_revision is a descendant of last_rev, but we aren't
 
3383
                # overwriting, so we're done.
 
3384
                return
 
3385
        if stop_revno is None:
 
3386
            if graph is None:
 
3387
                graph = self.target.repository.get_graph()
 
3388
            this_revno, this_last_revision = \
 
3389
                    self.target.last_revision_info()
 
3390
            stop_revno = graph.find_distance_to_null(stop_revision,
 
3391
                            [(other_last_revision, other_revno),
 
3392
                             (this_last_revision, this_revno)])
 
3393
        self.target.set_last_revision_info(stop_revno, stop_revision)
 
3394
 
 
3395
    @needs_write_lock
 
3396
    def pull(self, overwrite=False, stop_revision=None,
 
3397
             possible_transports=None, run_hooks=True,
 
3398
             _override_hook_target=None, local=False):
 
3399
        """Pull from source into self, updating my master if any.
 
3400
 
 
3401
        :param run_hooks: Private parameter - if false, this branch
 
3402
            is being called because it's the master of the primary branch,
 
3403
            so it should not run its hooks.
 
3404
        """
 
3405
        bound_location = self.target.get_bound_location()
 
3406
        if local and not bound_location:
 
3407
            raise errors.LocalRequiresBoundBranch()
 
3408
        master_branch = None
 
3409
        if not local and bound_location and self.source.user_url != bound_location:
 
3410
            # not pulling from master, so we need to update master.
 
3411
            master_branch = self.target.get_master_branch(possible_transports)
 
3412
            master_branch.lock_write()
 
3413
        try:
 
3414
            if master_branch:
 
3415
                # pull from source into master.
 
3416
                master_branch.pull(self.source, overwrite, stop_revision,
 
3417
                    run_hooks=False)
 
3418
            return self._pull(overwrite,
 
3419
                stop_revision, _hook_master=master_branch,
 
3420
                run_hooks=run_hooks,
 
3421
                _override_hook_target=_override_hook_target)
 
3422
        finally:
 
3423
            if master_branch:
 
3424
                master_branch.unlock()
 
3425
 
 
3426
    def push(self, overwrite=False, stop_revision=None,
 
3427
             _override_hook_source_branch=None):
 
3428
        """See InterBranch.push.
 
3429
 
 
3430
        This is the basic concrete implementation of push()
 
3431
 
 
3432
        :param _override_hook_source_branch: If specified, run
 
3433
        the hooks passing this Branch as the source, rather than self.
 
3434
        This is for use of RemoteBranch, where push is delegated to the
 
3435
        underlying vfs-based Branch.
 
3436
        """
 
3437
        # TODO: Public option to disable running hooks - should be trivial but
 
3438
        # needs tests.
 
3439
        self.source.lock_read()
 
3440
        try:
 
3441
            return _run_with_write_locked_target(
 
3442
                self.target, self._push_with_bound_branches, overwrite,
 
3443
                stop_revision,
 
3444
                _override_hook_source_branch=_override_hook_source_branch)
 
3445
        finally:
 
3446
            self.source.unlock()
 
3447
 
 
3448
    def _push_with_bound_branches(self, overwrite, stop_revision,
 
3449
            _override_hook_source_branch=None):
 
3450
        """Push from source into target, and into target's master if any.
 
3451
        """
 
3452
        def _run_hooks():
 
3453
            if _override_hook_source_branch:
 
3454
                result.source_branch = _override_hook_source_branch
 
3455
            for hook in Branch.hooks['post_push']:
 
3456
                hook(result)
 
3457
 
 
3458
        bound_location = self.target.get_bound_location()
 
3459
        if bound_location and self.target.base != bound_location:
 
3460
            # there is a master branch.
 
3461
            #
 
3462
            # XXX: Why the second check?  Is it even supported for a branch to
 
3463
            # be bound to itself? -- mbp 20070507
 
3464
            master_branch = self.target.get_master_branch()
 
3465
            master_branch.lock_write()
 
3466
            try:
 
3467
                # push into the master from the source branch.
 
3468
                self.source._basic_push(master_branch, overwrite, stop_revision)
 
3469
                # and push into the target branch from the source. Note that we
 
3470
                # push from the source branch again, because its considered the
 
3471
                # highest bandwidth repository.
 
3472
                result = self.source._basic_push(self.target, overwrite,
 
3473
                    stop_revision)
 
3474
                result.master_branch = master_branch
 
3475
                result.local_branch = self.target
 
3476
                _run_hooks()
 
3477
                return result
 
3478
            finally:
 
3479
                master_branch.unlock()
 
3480
        else:
 
3481
            # no master branch
 
3482
            result = self.source._basic_push(self.target, overwrite,
 
3483
                stop_revision)
 
3484
            # TODO: Why set master_branch and local_branch if there's no
 
3485
            # binding?  Maybe cleaner to just leave them unset? -- mbp
 
3486
            # 20070504
 
3487
            result.master_branch = self.target
 
3488
            result.local_branch = None
 
3489
            _run_hooks()
 
3490
            return result
 
3491
 
 
3492
    def _pull(self, overwrite=False, stop_revision=None,
 
3493
             possible_transports=None, _hook_master=None, run_hooks=True,
 
3494
             _override_hook_target=None, local=False):
 
3495
        """See Branch.pull.
 
3496
 
 
3497
        This function is the core worker, used by GenericInterBranch.pull to
 
3498
        avoid duplication when pulling source->master and source->local.
 
3499
 
 
3500
        :param _hook_master: Private parameter - set the branch to
 
3501
            be supplied as the master to pull hooks.
 
3502
        :param run_hooks: Private parameter - if false, this branch
 
3503
            is being called because it's the master of the primary branch,
 
3504
            so it should not run its hooks.
 
3505
        :param _override_hook_target: Private parameter - set the branch to be
 
3506
            supplied as the target_branch to pull hooks.
 
3507
        :param local: Only update the local branch, and not the bound branch.
 
3508
        """
 
3509
        # This type of branch can't be bound.
 
3510
        if local:
 
3511
            raise errors.LocalRequiresBoundBranch()
 
3512
        result = PullResult()
 
3513
        result.source_branch = self.source
 
3514
        if _override_hook_target is None:
 
3515
            result.target_branch = self.target
 
3516
        else:
 
3517
            result.target_branch = _override_hook_target
 
3518
        self.source.lock_read()
 
3519
        try:
 
3520
            # We assume that during 'pull' the target repository is closer than
 
3521
            # the source one.
 
3522
            self.source.update_references(self.target)
 
3523
            graph = self.target.repository.get_graph(self.source.repository)
 
3524
            # TODO: Branch formats should have a flag that indicates 
 
3525
            # that revno's are expensive, and pull() should honor that flag.
 
3526
            # -- JRV20090506
 
3527
            result.old_revno, result.old_revid = \
 
3528
                self.target.last_revision_info()
 
3529
            self.target.update_revisions(self.source, stop_revision,
 
3530
                overwrite=overwrite, graph=graph)
 
3531
            # TODO: The old revid should be specified when merging tags, 
 
3532
            # so a tags implementation that versions tags can only 
 
3533
            # pull in the most recent changes. -- JRV20090506
 
3534
            result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
 
3535
                overwrite)
 
3536
            result.new_revno, result.new_revid = self.target.last_revision_info()
 
3537
            if _hook_master:
 
3538
                result.master_branch = _hook_master
 
3539
                result.local_branch = result.target_branch
 
3540
            else:
 
3541
                result.master_branch = result.target_branch
 
3542
                result.local_branch = None
 
3543
            if run_hooks:
 
3544
                for hook in Branch.hooks['post_pull']:
 
3545
                    hook(result)
 
3546
        finally:
 
3547
            self.source.unlock()
 
3548
        return result
2864
3549
 
2865
3550
 
2866
3551
InterBranch.register_optimiser(GenericInterBranch)