/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2018-05-06 11:48:54 UTC
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@jelmer.uk-20180506114854-h4qd9ojaqy8wxjsd
Move .mailmap to root.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
from __future__ import absolute_import
18
18
 
 
19
from . import errors
 
20
 
19
21
from .lazy_import import lazy_import
20
22
lazy_import(globals(), """
21
23
import itertools
23
25
    cleanup,
24
26
    config as _mod_config,
25
27
    debug,
 
28
    fetch,
26
29
    memorytree,
27
30
    repository,
28
31
    revision as _mod_revision,
32
35
    urlutils,
33
36
    )
34
37
from breezy.bzr import (
35
 
    fetch,
36
38
    remote,
37
39
    vf_search,
38
40
    )
41
43
 
42
44
from . import (
43
45
    controldir,
44
 
    errors,
45
46
    registry,
46
47
    )
 
48
from .decorators import (
 
49
    only_raises,
 
50
    )
47
51
from .hooks import Hooks
48
52
from .inter import InterObject
49
53
from .lock import LogicalLockResult
50
54
from .sixish import (
 
55
    BytesIO,
51
56
    text_type,
52
57
    viewitems,
53
58
    )
54
 
from .trace import mutter, mutter_callsite, note, is_quiet, warning
 
59
from .trace import mutter, mutter_callsite, note, is_quiet
55
60
 
56
61
 
57
62
class UnstackableBranchFormat(errors.BzrError):
58
63
 
59
64
    _fmt = ("The branch '%(url)s'(%(format)s) is not a stackable format. "
60
 
            "You will need to upgrade the branch to permit branch stacking.")
 
65
        "You will need to upgrade the branch to permit branch stacking.")
61
66
 
62
67
    def __init__(self, format, url):
63
68
        errors.BzrError.__init__(self)
155
160
        repository._iter_for_revno(
156
161
            self.repository, self._partial_revision_history_cache,
157
162
            stop_index=stop_index, stop_revision=stop_revision)
158
 
        if self._partial_revision_history_cache[-1] == \
159
 
                _mod_revision.NULL_REVISION:
 
163
        if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
160
164
            self._partial_revision_history_cache.pop()
161
165
 
162
166
    def _get_check_refs(self):
174
178
        For instance, if the branch is at URL/.bzr/branch,
175
179
        Branch.open(URL) -> a Branch instance.
176
180
        """
177
 
        control = controldir.ControlDir.open(
178
 
            base, possible_transports=possible_transports,
179
 
            _unsupported=_unsupported)
180
 
        return control.open_branch(
181
 
            unsupported=_unsupported,
 
181
        control = controldir.ControlDir.open(base,
 
182
            possible_transports=possible_transports, _unsupported=_unsupported)
 
183
        return control.open_branch(unsupported=_unsupported,
182
184
            possible_transports=possible_transports)
183
185
 
184
186
    @staticmethod
185
187
    def open_from_transport(transport, name=None, _unsupported=False,
186
 
                            possible_transports=None):
 
188
            possible_transports=None):
187
189
        """Open the branch rooted at transport"""
188
 
        control = controldir.ControlDir.open_from_transport(
189
 
            transport, _unsupported)
190
 
        return control.open_branch(
191
 
            name=name, unsupported=_unsupported,
 
190
        control = controldir.ControlDir.open_from_transport(transport, _unsupported)
 
191
        return control.open_branch(name=name, unsupported=_unsupported,
192
192
            possible_transports=possible_transports)
193
193
 
194
194
    @staticmethod
199
199
 
200
200
        Basically we keep looking up until we find the control directory or
201
201
        run into the root.  If there isn't one, raises NotBranchError.
202
 
        If there is one and it is either an unrecognised format or an
203
 
        unsupported format, UnknownFormatError or UnsupportedFormatError are
204
 
        raised.  If there is one, it is returned, along with the unused portion
205
 
        of url.
 
202
        If there is one and it is either an unrecognised format or an unsupported
 
203
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
204
        If there is one, it is returned, along with the unused portion of url.
206
205
        """
207
 
        control, relpath = controldir.ControlDir.open_containing(
208
 
            url, possible_transports)
 
206
        control, relpath = controldir.ControlDir.open_containing(url,
 
207
                                                         possible_transports)
209
208
        branch = control.open_branch(possible_transports=possible_transports)
210
209
        return (branch, relpath)
211
210
 
277
276
                # Silently fall back to local implicit nick if the master is
278
277
                # unavailable
279
278
                mutter("Could not connect to bound branch, "
280
 
                       "falling back to local nick.\n " + str(e))
 
279
                    "falling back to local nick.\n " + str(e))
281
280
        return config.get_nickname()
282
281
 
283
282
    def _set_nick(self, nick):
306
305
        new_history = []
307
306
        check_not_reserved_id = _mod_revision.check_not_reserved_id
308
307
        # Do not include ghosts or graph origin in revision_history
309
 
        while (current_rev_id in parents_map
310
 
               and len(parents_map[current_rev_id]) > 0):
 
308
        while (current_rev_id in parents_map and
 
309
               len(parents_map[current_rev_id]) > 0):
311
310
            check_not_reserved_id(current_rev_id)
312
311
            new_history.append(current_rev_id)
313
312
            current_rev_id = parents_map[current_rev_id][0]
365
364
        provide a more efficient implementation.
366
365
        """
367
366
        if len(revno) == 1:
368
 
            try:
369
 
                return self.get_rev_id(revno[0])
370
 
            except errors.RevisionNotPresent as e:
371
 
                raise errors.GhostRevisionsHaveNoRevno(revno[0], e.revision_id)
 
367
            return self.get_rev_id(revno[0])
372
368
        revision_id_to_revno = self.get_revision_id_to_revno_map()
373
369
        revision_ids = [revision_id for revision_id, this_revno
374
370
                        in viewitems(revision_id_to_revno)
416
412
        :return: A dictionary mapping revision_id => dotted revno.
417
413
            This dictionary should not be modified by the caller.
418
414
        """
419
 
        if 'evil' in debug.debug_flags:
420
 
            mutter_callsite(
421
 
                3, "get_revision_id_to_revno_map scales with ancestry.")
422
415
        with self.lock_read():
423
416
            if self._revision_id_to_revno_cache is not None:
424
417
                mapping = self._revision_id_to_revno_cache
425
418
            else:
426
419
                mapping = self._gen_revno_map()
427
420
                self._cache_revision_id_to_revno(mapping)
428
 
            # TODO: jam 20070417 Since this is being cached, should we be
429
 
            # returning a copy?
430
 
            # I would rather not, and instead just declare that users should
431
 
            # not modify the return value.
 
421
            # TODO: jam 20070417 Since this is being cached, should we be returning
 
422
            #       a copy?
 
423
            # I would rather not, and instead just declare that users should not
 
424
            # modify the return value.
432
425
            return mapping
433
426
 
434
427
    def _gen_revno_map(self):
441
434
 
442
435
        :return: A dictionary mapping revision_id => dotted revno.
443
436
        """
444
 
        revision_id_to_revno = {
445
 
            rev_id: revno for rev_id, depth, revno, end_of_merge
446
 
            in self.iter_merge_sorted_revisions()}
 
437
        revision_id_to_revno = dict((rev_id, revno)
 
438
            for rev_id, depth, revno, end_of_merge
 
439
             in self.iter_merge_sorted_revisions())
447
440
        return revision_id_to_revno
448
441
 
449
442
    def iter_merge_sorted_revisions(self, start_revision_id=None,
450
 
                                    stop_revision_id=None,
451
 
                                    stop_rule='exclude', direction='reverse'):
 
443
            stop_revision_id=None, stop_rule='exclude', direction='reverse'):
452
444
        """Walk the revisions for a branch in merge sorted order.
453
445
 
454
446
        Merge sorted order is the output from a merge-aware,
466
458
            * 'include' - the stop revision is the last item in the result
467
459
            * 'with-merges' - include the stop revision and all of its
468
460
              merged revisions in the result
469
 
            * 'with-merges-without-common-ancestry' - filter out revisions
 
461
            * 'with-merges-without-common-ancestry' - filter out revisions 
470
462
              that are in both ancestries
471
463
        :param direction: either 'reverse' or 'forward':
472
464
 
515
507
                raise ValueError('invalid direction %r' % direction)
516
508
 
517
509
    def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
518
 
                                       start_revision_id, stop_revision_id,
519
 
                                       stop_rule):
 
510
        start_revision_id, stop_revision_id, stop_rule):
520
511
        """Iterate over an inclusive range of sorted revisions."""
521
512
        rev_iter = iter(merge_sorted_revisions)
522
513
        if start_revision_id is not None:
577
568
                if rev_id == left_parent:
578
569
                    # reached the left parent after the stop_revision
579
570
                    return
580
 
                if (not reached_stop_revision_id
581
 
                        or rev_id in revision_id_whitelist):
 
571
                if (not reached_stop_revision_id or
 
572
                        rev_id in revision_id_whitelist):
582
573
                    yield (rev_id, node.merge_depth, node.revno,
583
 
                           node.end_of_merge)
 
574
                       node.end_of_merge)
584
575
                    if reached_stop_revision_id or rev_id == stop_revision_id:
585
576
                        # only do the merged revs of rev_id from now on
586
577
                        rev = self.repository.get_revision(rev_id)
596
587
        # ancestry. Given the order guaranteed by the merge sort, we will see
597
588
        # uninteresting descendants of the first parent of our tip before the
598
589
        # tip itself.
599
 
        try:
600
 
            first = next(rev_iter)
601
 
        except StopIteration:
602
 
            return
 
590
        first = next(rev_iter)
603
591
        (rev_id, merge_depth, revno, end_of_merge) = first
604
592
        yield first
605
593
        if not merge_depth:
642
630
        """Tell this branch object not to release the physical lock when this
643
631
        object is unlocked.
644
632
 
645
 
        If lock_write doesn't return a token, then this method is not
646
 
        supported.
 
633
        If lock_write doesn't return a token, then this method is not supported.
647
634
        """
648
635
        self.control_files.leave_in_place()
649
636
 
651
638
        """Tell this branch object to release the physical lock when this
652
639
        object is unlocked, even if it didn't originally acquire it.
653
640
 
654
 
        If lock_write doesn't return a token, then this method is not
655
 
        supported.
 
641
        If lock_write doesn't return a token, then this method is not supported.
656
642
        """
657
643
        self.control_files.dont_leave_in_place()
658
644
 
676
662
            raise errors.UpgradeRequired(self.user_url)
677
663
        self.get_config_stack().set('append_revisions_only', enabled)
678
664
 
679
 
    def fetch(self, from_branch, stop_revision=None, limit=None, lossy=False):
 
665
    def set_reference_info(self, tree_path, branch_location, file_id=None):
 
666
        """Set the branch location to use for a tree reference."""
 
667
        raise errors.UnsupportedOperation(self.set_reference_info, self)
 
668
 
 
669
    def get_reference_info(self, path):
 
670
        """Get the tree_path and branch_location for a tree reference."""
 
671
        raise errors.UnsupportedOperation(self.get_reference_info, self)
 
672
 
 
673
    def fetch(self, from_branch, last_revision=None, limit=None):
680
674
        """Copy revisions from from_branch into this branch.
681
675
 
682
676
        :param from_branch: Where to copy from.
683
 
        :param stop_revision: What revision to stop at (None for at the end
 
677
        :param last_revision: What revision to stop at (None for at the end
684
678
                              of the branch.
685
679
        :param limit: Optional rough limit of revisions to fetch
686
680
        :return: None
687
681
        """
688
682
        with self.lock_write():
689
683
            return InterBranch.get(from_branch, self).fetch(
690
 
                stop_revision, limit=limit, lossy=lossy)
 
684
                    last_revision, limit=limit)
691
685
 
692
686
    def get_bound_location(self):
693
687
        """Return the URL of the branch we are bound to.
715
709
        :param revprops: Optional dictionary of revision properties.
716
710
        :param revision_id: Optional revision id.
717
711
        :param lossy: Whether to discard data that can not be natively
718
 
            represented, when pushing to a foreign VCS
 
712
            represented, when pushing to a foreign VCS 
719
713
        """
720
714
 
721
715
        if config_stack is None:
722
716
            config_stack = self.get_config_stack()
723
717
 
724
 
        return self.repository.get_commit_builder(
725
 
            self, parents, config_stack, timestamp, timezone, committer,
726
 
            revprops, revision_id, lossy)
 
718
        return self.repository.get_commit_builder(self, parents, config_stack,
 
719
            timestamp, timezone, committer, revprops, revision_id,
 
720
            lossy)
727
721
 
728
722
    def get_master_branch(self, possible_transports=None):
729
723
        """Return the branch we are bound to.
768
762
                if not graph.is_ancestor(last_rev, revision_id):
769
763
                    # our previous tip is not merged into stop_revision
770
764
                    raise errors.DivergedBranches(self, other_branch)
771
 
            revno = graph.find_distance_to_null(
772
 
                revision_id, known_revision_ids)
 
765
            revno = graph.find_distance_to_null(revision_id, known_revision_ids)
773
766
            self.set_last_revision_info(revno, revision_id)
774
767
 
775
768
    def set_parent(self, url):
781
774
        if url is not None:
782
775
            if isinstance(url, text_type):
783
776
                try:
784
 
                    url.encode('ascii')
 
777
                    url = url.encode('ascii')
785
778
                except UnicodeEncodeError:
786
 
                    raise urlutils.InvalidURL(
787
 
                        url, "Urls must be 7-bit ascii, "
 
779
                    raise urlutils.InvalidURL(url,
 
780
                        "Urls must be 7-bit ascii, "
788
781
                        "use breezy.urlutils.escape")
789
782
            url = urlutils.relative_url(self.base, url)
790
783
        with self.lock_write():
801
794
        if not self._format.supports_stacking():
802
795
            raise UnstackableBranchFormat(self._format, self.user_url)
803
796
        with self.lock_write():
804
 
            # XXX: Changing from one fallback repository to another does not
805
 
            # check that all the data you need is present in the new fallback.
 
797
            # XXX: Changing from one fallback repository to another does not check
 
798
            # that all the data you need is present in the new fallback.
806
799
            # Possibly it should.
807
800
            self._check_stackable_repo()
808
801
            if not url:
809
802
                try:
810
 
                    self.get_stacked_on_url()
 
803
                    old_url = self.get_stacked_on_url()
811
804
                except (errors.NotStacked, UnstackableBranchFormat,
812
 
                        errors.UnstackableRepositoryFormat):
 
805
                    errors.UnstackableRepositoryFormat):
813
806
                    return
814
807
                self._unstack()
815
808
            else:
816
 
                self._activate_fallback_location(
817
 
                    url, possible_transports=[self.controldir.root_transport])
 
809
                self._activate_fallback_location(url,
 
810
                    possible_transports=[self.controldir.root_transport])
818
811
            # write this out after the repository is stacked to avoid setting a
819
812
            # stacked config that doesn't work.
820
813
            self._set_config_location('stacked_on_location', url)
828
821
            pb.update(gettext("Unstacking"))
829
822
            # The basic approach here is to fetch the tip of the branch,
830
823
            # including all available ghosts, from the existing stacked
831
 
            # repository into a new repository object without the fallbacks.
 
824
            # repository into a new repository object without the fallbacks. 
832
825
            #
833
826
            # XXX: See <https://launchpad.net/bugs/397286> - this may not be
834
827
            # correct for CHKMap repostiories
835
828
            old_repository = self.repository
836
829
            if len(old_repository._fallback_repositories) != 1:
837
 
                raise AssertionError(
838
 
                    "can't cope with fallback repositories "
839
 
                    "of %r (fallbacks: %r)" % (
840
 
                        old_repository, old_repository._fallback_repositories))
 
830
                raise AssertionError("can't cope with fallback repositories "
 
831
                    "of %r (fallbacks: %r)" % (old_repository,
 
832
                        old_repository._fallback_repositories))
841
833
            # Open the new repository object.
842
834
            # Repositories don't offer an interface to remove fallback
843
835
            # repositories today; take the conceptually simpler option and just
850
842
                self.controldir.root_transport.base)
851
843
            new_repository = new_bzrdir.find_repository()
852
844
            if new_repository._fallback_repositories:
853
 
                raise AssertionError(
854
 
                    "didn't expect %r to have fallback_repositories"
 
845
                raise AssertionError("didn't expect %r to have "
 
846
                    "fallback_repositories"
855
847
                    % (self.repository,))
856
848
            # Replace self.repository with the new repository.
857
849
            # Do our best to transfer the lock state (i.e. lock-tokens and
884
876
            if old_lock_count == 0:
885
877
                raise AssertionError(
886
878
                    'old_repository should have been locked at least once.')
887
 
            for i in range(old_lock_count - 1):
 
879
            for i in range(old_lock_count-1):
888
880
                self.repository.lock_write()
889
881
            # Fetch from the old repository into the new.
890
882
            with old_repository.lock_read():
895
887
                    tags_to_fetch = set(self.tags.get_reverse_tag_dict())
896
888
                except errors.TagsNotSupported:
897
889
                    tags_to_fetch = set()
898
 
                fetch_spec = vf_search.NotInOtherForRevs(
899
 
                    self.repository, old_repository,
900
 
                    required_ids=[self.last_revision()],
 
890
                fetch_spec = vf_search.NotInOtherForRevs(self.repository,
 
891
                    old_repository, required_ids=[self.last_revision()],
901
892
                    if_present_ids=tags_to_fetch, find_ghosts=True).execute()
902
893
                self.repository.fetch(old_repository, fetch_spec=fetch_spec)
903
894
 
985
976
        """
986
977
        with self.lock_read():
987
978
            if self._last_revision_info_cache is None:
988
 
                self._last_revision_info_cache = (
989
 
                    self._read_last_revision_info())
 
979
                self._last_revision_info_cache = self._read_last_revision_info()
990
980
            return self._last_revision_info_cache
991
981
 
992
982
    def _read_last_revision_info(self):
1046
1036
 
1047
1037
        :returns: PullResult instance
1048
1038
        """
1049
 
        return InterBranch.get(source, self).pull(
1050
 
            overwrite=overwrite, stop_revision=stop_revision,
 
1039
        return InterBranch.get(source, self).pull(overwrite=overwrite,
 
1040
            stop_revision=stop_revision,
1051
1041
            possible_transports=possible_transports, *args, **kwargs)
1052
1042
 
1053
1043
    def push(self, target, overwrite=False, stop_revision=None, lossy=False,
1054
 
             *args, **kwargs):
 
1044
            *args, **kwargs):
1055
1045
        """Mirror this branch into target.
1056
1046
 
1057
1047
        This branch is considered to be 'local', having low latency.
1058
1048
        """
1059
 
        return InterBranch.get(self, target).push(
1060
 
            overwrite, stop_revision, lossy, *args, **kwargs)
 
1049
        return InterBranch.get(self, target).push(overwrite, stop_revision,
 
1050
            lossy, *args, **kwargs)
1061
1051
 
1062
1052
    def basis_tree(self):
1063
1053
        """Return `Tree` object for last revision."""
1076
1066
        # This is an old-format absolute path to a local branch
1077
1067
        # turn it into a url
1078
1068
        if parent.startswith('/'):
1079
 
            parent = urlutils.local_path_to_url(parent)
 
1069
            parent = urlutils.local_path_to_url(parent.decode('utf8'))
1080
1070
        try:
1081
1071
            return urlutils.join(self.base[:-1], parent)
1082
 
        except urlutils.InvalidURLJoin:
 
1072
        except urlutils.InvalidURLJoin as e:
1083
1073
            raise errors.InaccessibleParent(parent, self.user_url)
1084
1074
 
1085
1075
    def _get_parent_location(self):
1194
1184
        if revno < 1 or revno > self.revno():
1195
1185
            raise errors.InvalidRevisionNumber(revno)
1196
1186
 
1197
 
    def clone(self, to_controldir, revision_id=None, name=None,
1198
 
              repository_policy=None, tag_selector=None):
 
1187
    def clone(self, to_controldir, revision_id=None, repository_policy=None):
1199
1188
        """Clone this branch into to_controldir preserving all semantic values.
1200
1189
 
1201
1190
        Most API users will want 'create_clone_on_transport', which creates a
1204
1193
        revision_id: if not None, the revision history in the new branch will
1205
1194
                     be truncated to end with revision_id.
1206
1195
        """
1207
 
        result = to_controldir.create_branch(name=name)
 
1196
        result = to_controldir.create_branch()
1208
1197
        with self.lock_read(), result.lock_write():
1209
1198
            if repository_policy is not None:
1210
1199
                repository_policy.configure_branch(result)
1211
 
            self.copy_content_into(
1212
 
                result, revision_id=revision_id, tag_selector=tag_selector)
 
1200
            self.copy_content_into(result, revision_id=revision_id)
1213
1201
        return result
1214
1202
 
1215
1203
    def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1216
 
               repository=None, lossy=False, tag_selector=None):
 
1204
            repository=None, lossy=False):
1217
1205
        """Create a new line of development from the branch, into to_controldir.
1218
1206
 
1219
1207
        to_controldir controls the branch format.
1221
1209
        revision_id: if not None, the revision history in the new branch will
1222
1210
                     be truncated to end with revision_id.
1223
1211
        """
1224
 
        if (repository_policy is not None
1225
 
                and repository_policy.requires_stacking()):
 
1212
        if (repository_policy is not None and
 
1213
            repository_policy.requires_stacking()):
1226
1214
            to_controldir._format.require_stacking(_skip_repo=True)
1227
1215
        result = to_controldir.create_branch(repository=repository)
1228
1216
        if lossy:
1230
1218
        with self.lock_read(), result.lock_write():
1231
1219
            if repository_policy is not None:
1232
1220
                repository_policy.configure_branch(result)
1233
 
            self.copy_content_into(
1234
 
                result, revision_id=revision_id, tag_selector=tag_selector)
 
1221
            self.copy_content_into(result, revision_id=revision_id)
1235
1222
            master_url = self.get_bound_location()
1236
1223
            if master_url is None:
1237
1224
                result.set_parent(self.user_url)
1257
1244
        else:
1258
1245
            graph = self.repository.get_graph()
1259
1246
            try:
1260
 
                revno = graph.find_distance_to_null(
1261
 
                    revision_id, [(source_revision_id, source_revno)])
 
1247
                revno = graph.find_distance_to_null(revision_id, 
 
1248
                    [(source_revision_id, source_revno)])
1262
1249
            except errors.GhostRevisionsHaveNoRevno:
1263
1250
                # Default to 1, if we can't find anything else
1264
1251
                revno = 1
1265
1252
        destination.set_last_revision_info(revno, revision_id)
1266
1253
 
1267
 
    def copy_content_into(self, destination, revision_id=None, tag_selector=None):
 
1254
    def copy_content_into(self, destination, revision_id=None):
1268
1255
        """Copy the content of self into destination.
1269
1256
 
1270
1257
        revision_id: if not None, the revision history in the new branch will
1271
1258
                     be truncated to end with revision_id.
1272
 
        tag_selector: Optional callback that receives a tag name
1273
 
            and should return a boolean to indicate whether a tag should be copied
1274
1259
        """
1275
1260
        return InterBranch.get(self, destination).copy_content_into(
1276
 
            revision_id=revision_id, tag_selector=tag_selector)
 
1261
            revision_id=revision_id)
1277
1262
 
1278
1263
    def update_references(self, target):
1279
 
        if not self._format.supports_reference_locations:
1280
 
            return
1281
 
        return InterBranch.get(self, target).update_references()
 
1264
        if not getattr(self._format, 'supports_reference_locations', False):
 
1265
            return
 
1266
        reference_dict = self._get_all_reference_info()
 
1267
        if len(reference_dict) == 0:
 
1268
            return
 
1269
        old_base = self.base
 
1270
        new_base = target.base
 
1271
        target_reference_dict = target._get_all_reference_info()
 
1272
        for tree_path, (branch_location, file_id) in viewitems(reference_dict):
 
1273
            branch_location = urlutils.rebase_url(branch_location,
 
1274
                                                  old_base, new_base)
 
1275
            target_reference_dict.setdefault(
 
1276
                tree_path, (branch_location, file_id))
 
1277
        target._set_all_reference_info(target_reference_dict)
1282
1278
 
1283
1279
    def check(self, refs):
1284
1280
        """Check consistency of the branch.
1300
1296
            if actual_revno != last_revno:
1301
1297
                result.errors.append(errors.BzrCheckError(
1302
1298
                    'revno does not match len(mainline) %s != %s' % (
1303
 
                        last_revno, actual_revno)))
 
1299
                    last_revno, actual_revno)))
1304
1300
            # TODO: We should probably also check that self.revision_history
1305
1301
            # matches the repository for older branch formats.
1306
 
            # If looking for the code that cross-checks repository parents
1307
 
            # against the Graph.iter_lefthand_ancestry output, that is now a
1308
 
            # repository specific check.
 
1302
            # If looking for the code that cross-checks repository parents against
 
1303
            # the Graph.iter_lefthand_ancestry output, that is now a repository
 
1304
            # specific check.
1309
1305
            return result
1310
1306
 
1311
1307
    def _get_checkout_format(self, lightweight=False):
1317
1313
        return format
1318
1314
 
1319
1315
    def create_clone_on_transport(self, to_transport, revision_id=None,
1320
 
                                  stacked_on=None, create_prefix=False,
1321
 
                                  use_existing_dir=False, no_tree=None,
1322
 
                                  tag_selector=None):
 
1316
        stacked_on=None, create_prefix=False, use_existing_dir=False,
 
1317
        no_tree=None):
1323
1318
        """Create a clone of this branch and its bzrdir.
1324
1319
 
1325
1320
        :param to_transport: The transport to clone onto.
1332
1327
        """
1333
1328
        # XXX: Fix the bzrdir API to allow getting the branch back from the
1334
1329
        # clone call. Or something. 20090224 RBC/spiv.
1335
 
        # XXX: Should this perhaps clone colocated branches as well,
 
1330
        # XXX: Should this perhaps clone colocated branches as well, 
1336
1331
        # rather than just the default branch? 20100319 JRV
1337
1332
        if revision_id is None:
1338
1333
            revision_id = self.last_revision()
1339
 
        dir_to = self.controldir.clone_on_transport(
1340
 
            to_transport, revision_id=revision_id, stacked_on=stacked_on,
 
1334
        dir_to = self.controldir.clone_on_transport(to_transport,
 
1335
            revision_id=revision_id, stacked_on=stacked_on,
1341
1336
            create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1342
 
            no_tree=no_tree, tag_selector=tag_selector)
 
1337
            no_tree=no_tree)
1343
1338
        return dir_to.open_branch()
1344
1339
 
1345
1340
    def create_checkout(self, to_location, revision_id=None,
1346
1341
                        lightweight=False, accelerator_tree=None,
1347
 
                        hardlink=False, recurse_nested=True):
 
1342
                        hardlink=False):
1348
1343
        """Create a checkout of a branch.
1349
1344
 
1350
1345
        :param to_location: The url to produce the checkout at
1357
1352
            content is different.
1358
1353
        :param hardlink: If true, hard-link files from accelerator_tree,
1359
1354
            where possible.
1360
 
        :param recurse_nested: Whether to recurse into nested trees
1361
1355
        :return: The tree of the created checkout
1362
1356
        """
1363
1357
        t = transport.get_transport(to_location)
1375
1369
                pass
1376
1370
            else:
1377
1371
                raise errors.AlreadyControlDirError(t.base)
1378
 
            if (checkout.control_transport.base
1379
 
                    == self.controldir.control_transport.base):
 
1372
            if checkout.control_transport.base == self.controldir.control_transport.base:
1380
1373
                # When checking out to the same control directory,
1381
1374
                # always create a lightweight checkout
1382
1375
                lightweight = True
1385
1378
            from_branch = checkout.set_branch_reference(target_branch=self)
1386
1379
        else:
1387
1380
            policy = checkout.determine_repository_policy()
1388
 
            policy.acquire_repository()
 
1381
            repo = policy.acquire_repository()[0]
1389
1382
            checkout_branch = checkout.create_branch()
1390
1383
            checkout_branch.bind(self)
1391
1384
            # pull up to the specified revision_id to set the initial
1398
1391
                                           hardlink=hardlink)
1399
1392
        basis_tree = tree.basis_tree()
1400
1393
        with basis_tree.lock_read():
1401
 
            for path in basis_tree.iter_references():
1402
 
                reference_parent = tree.reference_parent(path)
1403
 
                if reference_parent is None:
1404
 
                    warning('Branch location for %s unknown.', path)
1405
 
                    continue
1406
 
                reference_parent.create_checkout(
1407
 
                    tree.abspath(path),
1408
 
                    basis_tree.get_reference_revision(path), lightweight)
 
1394
            for path, file_id in basis_tree.iter_references():
 
1395
                reference_parent = self.reference_parent(path, file_id)
 
1396
                reference_parent.create_checkout(tree.abspath(path),
 
1397
                    basis_tree.get_reference_revision(path, file_id),
 
1398
                    lightweight)
1409
1399
        return tree
1410
1400
 
1411
1401
    def reconcile(self, thorough=True):
1412
 
        """Make sure the data stored in this branch is consistent.
1413
 
 
1414
 
        :return: A `ReconcileResult` object.
 
1402
        """Make sure the data stored in this branch is consistent."""
 
1403
        from breezy.reconcile import BranchReconciler
 
1404
        with self.lock_write():
 
1405
            reconciler = BranchReconciler(self, thorough=thorough)
 
1406
            reconciler.reconcile()
 
1407
            return reconciler
 
1408
 
 
1409
    def reference_parent(self, path, file_id=None, possible_transports=None):
 
1410
        """Return the parent branch for a tree-reference file_id
 
1411
 
 
1412
        :param path: The path of the file_id in the tree
 
1413
        :param file_id: Optional file_id of the tree reference
 
1414
        :return: A branch associated with the file_id
1415
1415
        """
1416
 
        raise NotImplementedError(self.reconcile)
 
1416
        # FIXME should provide multiple branches, based on config
 
1417
        return Branch.open(self.controldir.root_transport.clone(path).base,
 
1418
                           possible_transports=possible_transports)
1417
1419
 
1418
1420
    def supports_tags(self):
1419
1421
        return self._format.supports_tags()
1600
1602
        raise NotImplementedError(self.network_name)
1601
1603
 
1602
1604
    def open(self, controldir, name=None, _found=False, ignore_fallbacks=False,
1603
 
             found_repository=None, possible_transports=None):
 
1605
            found_repository=None, possible_transports=None):
1604
1606
        """Return the branch object for controldir.
1605
1607
 
1606
1608
        :param controldir: A ControlDir that contains a branch.
1622
1624
 
1623
1625
    def supports_leaving_lock(self):
1624
1626
        """True if this format supports leaving locks in place."""
1625
 
        return False  # by default
 
1627
        return False # by default
1626
1628
 
1627
1629
    def __str__(self):
1628
1630
        return self.get_format_description().rstrip()
1643
1645
        """True if uncommitted changes can be stored in this branch."""
1644
1646
        return True
1645
1647
 
1646
 
    def stores_revno(self):
1647
 
        """True if this branch format store revision numbers."""
1648
 
        return True
1649
 
 
1650
1648
 
1651
1649
class BranchHooks(Hooks):
1652
1650
    """A dictionary mapping hook name to a list of callables for branch hooks.
1662
1660
        notified.
1663
1661
        """
1664
1662
        Hooks.__init__(self, "breezy.branch", "Branch.hooks")
1665
 
        self.add_hook(
1666
 
            'open',
 
1663
        self.add_hook('open',
1667
1664
            "Called with the Branch object that has been opened after a "
1668
1665
            "branch is opened.", (1, 8))
1669
 
        self.add_hook(
1670
 
            'post_push',
 
1666
        self.add_hook('post_push',
1671
1667
            "Called after a push operation completes. post_push is called "
1672
 
            "with a breezy.branch.BranchPushResult object and only runs in "
1673
 
            "the bzr client.", (0, 15))
1674
 
        self.add_hook(
1675
 
            'post_pull',
 
1668
            "with a breezy.branch.BranchPushResult object and only runs in the "
 
1669
            "bzr client.", (0, 15))
 
1670
        self.add_hook('post_pull',
1676
1671
            "Called after a pull operation completes. post_pull is called "
1677
1672
            "with a breezy.branch.PullResult object and only runs in the "
1678
1673
            "bzr client.", (0, 15))
1679
 
        self.add_hook(
1680
 
            'pre_commit',
 
1674
        self.add_hook('pre_commit',
1681
1675
            "Called after a commit is calculated but before it is "
1682
1676
            "completed. pre_commit is called with (local, master, old_revno, "
1683
1677
            "old_revid, future_revno, future_revid, tree_delta, future_tree"
1687
1681
            " future_tree is an in-memory tree obtained from "
1688
1682
            "CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1689
1683
            "tree.", (0, 91))
1690
 
        self.add_hook(
1691
 
            'post_commit',
 
1684
        self.add_hook('post_commit',
1692
1685
            "Called in the bzr client after a commit has completed. "
1693
1686
            "post_commit is called with (local, master, old_revno, old_revid, "
1694
1687
            "new_revno, new_revid). old_revid is NULL_REVISION for the first "
1695
1688
            "commit to a branch.", (0, 15))
1696
 
        self.add_hook(
1697
 
            'post_uncommit',
 
1689
        self.add_hook('post_uncommit',
1698
1690
            "Called in the bzr client after an uncommit completes. "
1699
1691
            "post_uncommit is called with (local, master, old_revno, "
1700
1692
            "old_revid, new_revno, new_revid) where local is the local branch "
1701
1693
            "or None, master is the target branch, and an empty branch "
1702
1694
            "receives new_revno of 0, new_revid of None.", (0, 15))
1703
 
        self.add_hook(
1704
 
            'pre_change_branch_tip',
 
1695
        self.add_hook('pre_change_branch_tip',
1705
1696
            "Called in bzr client and server before a change to the tip of a "
1706
1697
            "branch is made. pre_change_branch_tip is called with a "
1707
1698
            "breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1708
1699
            "commit, uncommit will all trigger this hook.", (1, 6))
1709
 
        self.add_hook(
1710
 
            'post_change_branch_tip',
 
1700
        self.add_hook('post_change_branch_tip',
1711
1701
            "Called in bzr client and server after a change to the tip of a "
1712
1702
            "branch is made. post_change_branch_tip is called with a "
1713
1703
            "breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1714
1704
            "commit, uncommit will all trigger this hook.", (1, 4))
1715
 
        self.add_hook(
1716
 
            'transform_fallback_location',
 
1705
        self.add_hook('transform_fallback_location',
1717
1706
            "Called when a stacked branch is activating its fallback "
1718
1707
            "locations. transform_fallback_location is called with (branch, "
1719
1708
            "url), and should return a new url. Returning the same url "
1725
1714
            "multiple hooks installed for transform_fallback_location, "
1726
1715
            "all are called with the url returned from the previous hook."
1727
1716
            "The order is however undefined.", (1, 9))
1728
 
        self.add_hook(
1729
 
            'automatic_tag_name',
 
1717
        self.add_hook('automatic_tag_name',
1730
1718
            "Called to determine an automatic tag name for a revision. "
1731
1719
            "automatic_tag_name is called with (branch, revision_id) and "
1732
1720
            "should return a tag name or None if no tag name could be "
1733
1721
            "determined. The first non-None tag name returned will be used.",
1734
1722
            (2, 2))
1735
 
        self.add_hook(
1736
 
            'post_branch_init',
 
1723
        self.add_hook('post_branch_init',
1737
1724
            "Called after new branch initialization completes. "
1738
1725
            "post_branch_init is called with a "
1739
1726
            "breezy.branch.BranchInitHookParams. "
1740
1727
            "Note that init, branch and checkout (both heavyweight and "
1741
1728
            "lightweight) will all trigger this hook.", (2, 2))
1742
 
        self.add_hook(
1743
 
            'post_switch',
 
1729
        self.add_hook('post_switch',
1744
1730
            "Called after a checkout switches branch. "
1745
1731
            "post_switch is called with a "
1746
1732
            "breezy.branch.SwitchHookParams.", (2, 2))
1747
1733
 
1748
1734
 
 
1735
 
1749
1736
# install the default hooks into the Branch class.
1750
1737
Branch.hooks = BranchHooks()
1751
1738
 
1856
1843
        return self.__dict__ == other.__dict__
1857
1844
 
1858
1845
    def __repr__(self):
1859
 
        return "<%s for %s to (%s, %s)>" % (
1860
 
            self.__class__.__name__, self.control_dir, self.to_branch,
 
1846
        return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
 
1847
            self.control_dir, self.to_branch,
1861
1848
            self.revision_id)
1862
1849
 
1863
1850
 
1871
1858
 
1872
1859
    def get_default(self):
1873
1860
        """Return the current default format."""
1874
 
        if (self._default_format_key is not None
1875
 
                and self._default_format is None):
 
1861
        if (self._default_format_key is not None and
 
1862
            self._default_format is None):
1876
1863
            self._default_format = self.get(self._default_format_key)
1877
1864
        return self._default_format
1878
1865
 
2005
1992
        tag_updates = getattr(self, "tag_updates", None)
2006
1993
        if not is_quiet():
2007
1994
            if self.old_revid != self.new_revid:
2008
 
                if self.new_revno is not None:
2009
 
                    note(gettext('Pushed up to revision %d.'),
2010
 
                         self.new_revno)
2011
 
                else:
2012
 
                    note(gettext('Pushed up to revision id %s.'),
2013
 
                         self.new_revid.decode('utf-8'))
 
1995
                note(gettext('Pushed up to revision %d.') % self.new_revno)
2014
1996
            if tag_updates:
2015
 
                note(ngettext('%d tag updated.', '%d tags updated.',
2016
 
                              len(tag_updates)) % len(tag_updates))
 
1997
                note(ngettext('%d tag updated.', '%d tags updated.', len(tag_updates)) % len(tag_updates))
2017
1998
            if self.old_revid == self.new_revid and not tag_updates:
2018
1999
                if not tag_conflicts:
2019
2000
                    note(gettext('No new revisions or tags to push.'))
2039
2020
            if any.
2040
2021
        """
2041
2022
        note(gettext('checked branch {0} format {1}').format(
2042
 
            self.branch.user_url, self.branch._format))
 
2023
                                self.branch.user_url, self.branch._format))
2043
2024
        for error in self.errors:
2044
2025
            note(gettext('found error:%s'), error)
2045
2026
 
2058
2039
    @classmethod
2059
2040
    def _get_branch_formats_to_test(klass):
2060
2041
        """Return an iterable of format tuples for testing.
2061
 
 
 
2042
        
2062
2043
        :return: An iterable of (from_format, to_format) to use when testing
2063
2044
            this InterBranch class. Each InterBranch class should define this
2064
2045
            method itself.
2066
2047
        raise NotImplementedError(klass._get_branch_formats_to_test)
2067
2048
 
2068
2049
    def pull(self, overwrite=False, stop_revision=None,
2069
 
             possible_transports=None, local=False, tag_selector=None):
 
2050
             possible_transports=None, local=False):
2070
2051
        """Mirror source into target branch.
2071
2052
 
2072
2053
        The target branch is considered to be 'local', having low latency.
2076
2057
        raise NotImplementedError(self.pull)
2077
2058
 
2078
2059
    def push(self, overwrite=False, stop_revision=None, lossy=False,
2079
 
             _override_hook_source_branch=None, tag_selector=None):
 
2060
             _override_hook_source_branch=None):
2080
2061
        """Mirror the source branch into the target branch.
2081
2062
 
2082
2063
        The source branch is considered to be 'local', having low latency.
2083
2064
        """
2084
2065
        raise NotImplementedError(self.push)
2085
2066
 
2086
 
    def copy_content_into(self, revision_id=None, tag_selector=None):
 
2067
    def copy_content_into(self, revision_id=None):
2087
2068
        """Copy the content of source into target
2088
2069
 
2089
 
        :param revision_id:
2090
 
            if not None, the revision history in the new branch will
2091
 
            be truncated to end with revision_id.
2092
 
        :param tag_selector: Optional callback that can decide
2093
 
            to copy or not copy tags.
 
2070
        revision_id: if not None, the revision history in the new branch will
 
2071
                     be truncated to end with revision_id.
2094
2072
        """
2095
2073
        raise NotImplementedError(self.copy_content_into)
2096
2074
 
2097
 
    def fetch(self, stop_revision=None, limit=None, lossy=False):
 
2075
    def fetch(self, stop_revision=None, limit=None):
2098
2076
        """Fetch revisions.
2099
2077
 
2100
2078
        :param stop_revision: Last revision to fetch
2101
2079
        :param limit: Optional rough limit of revisions to fetch
2102
 
        :return: FetchResult object
2103
2080
        """
2104
2081
        raise NotImplementedError(self.fetch)
2105
2082
 
2106
 
    def update_references(self):
2107
 
        """Import reference information from source to target.
2108
 
        """
2109
 
        raise NotImplementedError(self.update_references)
2110
 
 
2111
2083
 
2112
2084
def _fix_overwrite_type(overwrite):
2113
2085
    if isinstance(overwrite, bool):
2137
2109
            return format._custom_format
2138
2110
        return format
2139
2111
 
2140
 
    def copy_content_into(self, revision_id=None, tag_selector=None):
 
2112
    def copy_content_into(self, revision_id=None):
2141
2113
        """Copy the content of source into target
2142
2114
 
2143
2115
        revision_id: if not None, the revision history in the new branch will
2144
2116
                     be truncated to end with revision_id.
2145
2117
        """
2146
2118
        with self.source.lock_read(), self.target.lock_write():
 
2119
            self.source.update_references(self.target)
2147
2120
            self.source._synchronize_history(self.target, revision_id)
2148
 
            self.update_references()
2149
2121
            try:
2150
2122
                parent = self.source.get_parent()
2151
2123
            except errors.InaccessibleParent as e:
2152
 
                mutter('parent was not accessible to copy: %s', str(e))
 
2124
                mutter('parent was not accessible to copy: %s', e)
2153
2125
            else:
2154
2126
                if parent:
2155
2127
                    self.target.set_parent(parent)
2156
2128
            if self.source._push_should_merge_tags():
2157
 
                self.source.tags.merge_to(self.target.tags, selector=tag_selector)
 
2129
                self.source.tags.merge_to(self.target.tags)
2158
2130
 
2159
 
    def fetch(self, stop_revision=None, limit=None, lossy=False):
 
2131
    def fetch(self, stop_revision=None, limit=None):
2160
2132
        if self.target.base == self.source.base:
2161
2133
            return (0, [])
2162
2134
        with self.source.lock_read(), self.target.lock_write():
2165
2137
            fetch_spec_factory.source_branch_stop_revision_id = stop_revision
2166
2138
            fetch_spec_factory.source_repo = self.source.repository
2167
2139
            fetch_spec_factory.target_repo = self.target.repository
2168
 
            fetch_spec_factory.target_repo_kind = (
2169
 
                fetch.TargetRepoKinds.PREEXISTING)
 
2140
            fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
2170
2141
            fetch_spec_factory.limit = limit
2171
2142
            fetch_spec = fetch_spec_factory.make_fetch_spec()
2172
2143
            return self.target.repository.fetch(
2173
 
                self.source.repository,
2174
 
                lossy=lossy,
2175
 
                fetch_spec=fetch_spec)
 
2144
                    self.source.repository,
 
2145
                    fetch_spec=fetch_spec)
2176
2146
 
2177
2147
    def _update_revisions(self, stop_revision=None, overwrite=False,
2178
 
                          graph=None):
 
2148
            graph=None):
2179
2149
        with self.source.lock_read(), self.target.lock_write():
2180
2150
            other_revno, other_last_revision = self.source.last_revision_info()
2181
 
            stop_revno = None  # unknown
 
2151
            stop_revno = None # unknown
2182
2152
            if stop_revision is None:
2183
2153
                stop_revision = other_last_revision
2184
2154
                if _mod_revision.is_null(stop_revision):
2207
2177
                if graph is None:
2208
2178
                    graph = self.target.repository.get_graph()
2209
2179
                this_revno, this_last_revision = \
2210
 
                    self.target.last_revision_info()
2211
 
                stop_revno = graph.find_distance_to_null(
2212
 
                    stop_revision, [(other_last_revision, other_revno),
2213
 
                                    (this_last_revision, this_revno)])
 
2180
                        self.target.last_revision_info()
 
2181
                stop_revno = graph.find_distance_to_null(stop_revision,
 
2182
                                [(other_last_revision, other_revno),
 
2183
                                 (this_last_revision, this_revno)])
2214
2184
            self.target.set_last_revision_info(stop_revno, stop_revision)
2215
2185
 
2216
2186
    def pull(self, overwrite=False, stop_revision=None,
2217
2187
             possible_transports=None, run_hooks=True,
2218
 
             _override_hook_target=None, local=False,
2219
 
             tag_selector=None):
 
2188
             _override_hook_target=None, local=False):
2220
2189
        """Pull from source into self, updating my master if any.
2221
2190
 
2222
2191
        :param run_hooks: Private parameter - if false, this branch
2223
2192
            is being called because it's the master of the primary branch,
2224
2193
            so it should not run its hooks.
2225
2194
        """
2226
 
        with cleanup.ExitStack() as exit_stack:
2227
 
            exit_stack.enter_context(self.target.lock_write())
 
2195
        with self.target.lock_write():
2228
2196
            bound_location = self.target.get_bound_location()
2229
2197
            if local and not bound_location:
2230
2198
                raise errors.LocalRequiresBoundBranch()
2241
2209
                    source_is_master = False
2242
2210
            if not local and bound_location and not source_is_master:
2243
2211
                # not pulling from master, so we need to update master.
2244
 
                master_branch = self.target.get_master_branch(
2245
 
                    possible_transports)
2246
 
                exit_stack.enter_context(master_branch.lock_write())
2247
 
            if master_branch:
2248
 
                # pull from source into master.
2249
 
                master_branch.pull(
2250
 
                    self.source, overwrite, stop_revision, run_hooks=False,
2251
 
                    tag_selector=tag_selector)
2252
 
            return self._pull(
2253
 
                overwrite, stop_revision, _hook_master=master_branch,
2254
 
                run_hooks=run_hooks,
2255
 
                _override_hook_target=_override_hook_target,
2256
 
                merge_tags_to_master=not source_is_master,
2257
 
                tag_selector=tag_selector)
 
2212
                master_branch = self.target.get_master_branch(possible_transports)
 
2213
                master_branch.lock_write()
 
2214
            try:
 
2215
                if master_branch:
 
2216
                    # pull from source into master.
 
2217
                    master_branch.pull(self.source, overwrite, stop_revision,
 
2218
                        run_hooks=False)
 
2219
                return self._pull(overwrite,
 
2220
                    stop_revision, _hook_master=master_branch,
 
2221
                    run_hooks=run_hooks,
 
2222
                    _override_hook_target=_override_hook_target,
 
2223
                    merge_tags_to_master=not source_is_master)
 
2224
            finally:
 
2225
                if master_branch:
 
2226
                    master_branch.unlock()
2258
2227
 
2259
2228
    def push(self, overwrite=False, stop_revision=None, lossy=False,
2260
 
             _override_hook_source_branch=None, tag_selector=None):
 
2229
             _override_hook_source_branch=None):
2261
2230
        """See InterBranch.push.
2262
2231
 
2263
2232
        This is the basic concrete implementation of push()
2272
2241
        # TODO: Public option to disable running hooks - should be trivial but
2273
2242
        # needs tests.
2274
2243
 
2275
 
        def _run_hooks():
2276
 
            if _override_hook_source_branch:
2277
 
                result.source_branch = _override_hook_source_branch
2278
 
            for hook in Branch.hooks['post_push']:
2279
 
                hook(result)
2280
 
 
2281
 
        with self.source.lock_read(), self.target.lock_write():
2282
 
            bound_location = self.target.get_bound_location()
2283
 
            if bound_location and self.target.base != bound_location:
2284
 
                # there is a master branch.
2285
 
                #
2286
 
                # XXX: Why the second check?  Is it even supported for a branch
2287
 
                # to be bound to itself? -- mbp 20070507
2288
 
                master_branch = self.target.get_master_branch()
2289
 
                with master_branch.lock_write():
2290
 
                    # push into the master from the source branch.
2291
 
                    master_inter = InterBranch.get(self.source, master_branch)
2292
 
                    master_inter._basic_push(
2293
 
                        overwrite, stop_revision, tag_selector=tag_selector)
2294
 
                    # and push into the target branch from the source. Note
2295
 
                    # that we push from the source branch again, because it's
2296
 
                    # considered the highest bandwidth repository.
2297
 
                    result = self._basic_push(
2298
 
                        overwrite, stop_revision, tag_selector=tag_selector)
2299
 
                    result.master_branch = master_branch
2300
 
                    result.local_branch = self.target
2301
 
                    _run_hooks()
2302
 
            else:
2303
 
                master_branch = None
2304
 
                # no master branch
2305
 
                result = self._basic_push(
2306
 
                    overwrite, stop_revision, tag_selector=tag_selector)
2307
 
                # TODO: Why set master_branch and local_branch if there's no
2308
 
                # binding?  Maybe cleaner to just leave them unset? -- mbp
2309
 
                # 20070504
2310
 
                result.master_branch = self.target
2311
 
                result.local_branch = None
2312
 
                _run_hooks()
2313
 
            return result
2314
 
 
2315
 
    def _basic_push(self, overwrite, stop_revision, tag_selector=None):
 
2244
        op = cleanup.OperationWithCleanups(self._push_with_bound_branches)
 
2245
        op.add_cleanup(self.source.lock_read().unlock)
 
2246
        op.add_cleanup(self.target.lock_write().unlock)
 
2247
        return op.run(overwrite, stop_revision,
 
2248
            _override_hook_source_branch=_override_hook_source_branch)
 
2249
 
 
2250
    def _basic_push(self, overwrite, stop_revision):
2316
2251
        """Basic implementation of push without bound branches or hooks.
2317
2252
 
2318
2253
        Must be called with source read locked and target write locked.
2321
2256
        result.source_branch = self.source
2322
2257
        result.target_branch = self.target
2323
2258
        result.old_revno, result.old_revid = self.target.last_revision_info()
 
2259
        self.source.update_references(self.target)
2324
2260
        overwrite = _fix_overwrite_type(overwrite)
2325
2261
        if result.old_revid != stop_revision:
2326
2262
            # We assume that during 'push' this repository is closer than
2327
2263
            # the target.
2328
2264
            graph = self.source.repository.get_graph(self.target.repository)
2329
 
            self._update_revisions(
2330
 
                stop_revision, overwrite=("history" in overwrite), graph=graph)
 
2265
            self._update_revisions(stop_revision,
 
2266
                overwrite=("history" in overwrite),
 
2267
                graph=graph)
2331
2268
        if self.source._push_should_merge_tags():
2332
2269
            result.tag_updates, result.tag_conflicts = (
2333
2270
                self.source.tags.merge_to(
2334
 
                    self.target.tags, "tags" in overwrite, selector=tag_selector))
2335
 
        self.update_references()
 
2271
                self.target.tags, "tags" in overwrite))
2336
2272
        result.new_revno, result.new_revid = self.target.last_revision_info()
2337
2273
        return result
2338
2274
 
 
2275
    def _push_with_bound_branches(self, operation, overwrite, stop_revision,
 
2276
            _override_hook_source_branch=None):
 
2277
        """Push from source into target, and into target's master if any.
 
2278
        """
 
2279
        def _run_hooks():
 
2280
            if _override_hook_source_branch:
 
2281
                result.source_branch = _override_hook_source_branch
 
2282
            for hook in Branch.hooks['post_push']:
 
2283
                hook(result)
 
2284
 
 
2285
        bound_location = self.target.get_bound_location()
 
2286
        if bound_location and self.target.base != bound_location:
 
2287
            # there is a master branch.
 
2288
            #
 
2289
            # XXX: Why the second check?  Is it even supported for a branch to
 
2290
            # be bound to itself? -- mbp 20070507
 
2291
            master_branch = self.target.get_master_branch()
 
2292
            master_branch.lock_write()
 
2293
            operation.add_cleanup(master_branch.unlock)
 
2294
            # push into the master from the source branch.
 
2295
            master_inter = InterBranch.get(self.source, master_branch)
 
2296
            master_inter._basic_push(overwrite, stop_revision)
 
2297
            # and push into the target branch from the source. Note that
 
2298
            # we push from the source branch again, because it's considered
 
2299
            # the highest bandwidth repository.
 
2300
            result = self._basic_push(overwrite, stop_revision)
 
2301
            result.master_branch = master_branch
 
2302
            result.local_branch = self.target
 
2303
        else:
 
2304
            master_branch = None
 
2305
            # no master branch
 
2306
            result = self._basic_push(overwrite, stop_revision)
 
2307
            # TODO: Why set master_branch and local_branch if there's no
 
2308
            # binding?  Maybe cleaner to just leave them unset? -- mbp
 
2309
            # 20070504
 
2310
            result.master_branch = self.target
 
2311
            result.local_branch = None
 
2312
        _run_hooks()
 
2313
        return result
 
2314
 
2339
2315
    def _pull(self, overwrite=False, stop_revision=None,
2340
 
              possible_transports=None, _hook_master=None, run_hooks=True,
2341
 
              _override_hook_target=None, local=False,
2342
 
              merge_tags_to_master=True, tag_selector=None):
 
2316
             possible_transports=None, _hook_master=None, run_hooks=True,
 
2317
             _override_hook_target=None, local=False,
 
2318
             merge_tags_to_master=True):
2343
2319
        """See Branch.pull.
2344
2320
 
2345
2321
        This function is the core worker, used by GenericInterBranch.pull to
2368
2344
        with self.source.lock_read():
2369
2345
            # We assume that during 'pull' the target repository is closer than
2370
2346
            # the source one.
 
2347
            self.source.update_references(self.target)
2371
2348
            graph = self.target.repository.get_graph(self.source.repository)
2372
 
            # TODO: Branch formats should have a flag that indicates
 
2349
            # TODO: Branch formats should have a flag that indicates 
2373
2350
            # that revno's are expensive, and pull() should honor that flag.
2374
2351
            # -- JRV20090506
2375
2352
            result.old_revno, result.old_revid = \
2376
2353
                self.target.last_revision_info()
2377
2354
            overwrite = _fix_overwrite_type(overwrite)
2378
 
            self._update_revisions(
2379
 
                stop_revision, overwrite=("history" in overwrite), graph=graph)
2380
 
            # TODO: The old revid should be specified when merging tags,
2381
 
            # so a tags implementation that versions tags can only
 
2355
            self._update_revisions(stop_revision,
 
2356
                overwrite=("history" in overwrite),
 
2357
                graph=graph)
 
2358
            # TODO: The old revid should be specified when merging tags, 
 
2359
            # so a tags implementation that versions tags can only 
2382
2360
            # pull in the most recent changes. -- JRV20090506
2383
2361
            result.tag_updates, result.tag_conflicts = (
2384
 
                self.source.tags.merge_to(
2385
 
                    self.target.tags, "tags" in overwrite,
2386
 
                    ignore_master=not merge_tags_to_master,
2387
 
                    selector=tag_selector))
2388
 
            self.update_references()
2389
 
            result.new_revno, result.new_revid = (
2390
 
                self.target.last_revision_info())
 
2362
                self.source.tags.merge_to(self.target.tags,
 
2363
                    "tags" in overwrite,
 
2364
                    ignore_master=not merge_tags_to_master))
 
2365
            result.new_revno, result.new_revid = self.target.last_revision_info()
2391
2366
            if _hook_master:
2392
2367
                result.master_branch = _hook_master
2393
2368
                result.local_branch = result.target_branch
2399
2374
                    hook(result)
2400
2375
            return result
2401
2376
 
2402
 
    def update_references(self):
2403
 
        if not getattr(self.source._format, 'supports_reference_locations', False):
2404
 
            return
2405
 
        reference_dict = self.source._get_all_reference_info()
2406
 
        if len(reference_dict) == 0:
2407
 
            return
2408
 
        old_base = self.source.base
2409
 
        new_base = self.target.base
2410
 
        target_reference_dict = self.target._get_all_reference_info()
2411
 
        for tree_path, (branch_location, file_id) in viewitems(reference_dict):
2412
 
            try:
2413
 
                branch_location = urlutils.rebase_url(branch_location,
2414
 
                                                      old_base, new_base)
2415
 
            except urlutils.InvalidRebaseURLs:
2416
 
                # Fall back to absolute URL
2417
 
                branch_location = urlutils.join(old_base, branch_location)
2418
 
            target_reference_dict.setdefault(
2419
 
                tree_path, (branch_location, file_id))
2420
 
        self.target._set_all_reference_info(target_reference_dict)
2421
 
 
2422
2377
 
2423
2378
InterBranch.register_optimiser(GenericInterBranch)