/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

Merge test-run support.

Show diffs side-by-side

added added

removed removed

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