/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-06-14 17:59:16 UTC
  • mto: This revision was merged to the branch mainline in revision 7065.
  • Revision ID: jelmer@jelmer.uk-20180614175916-a2e2xh5k533guq1x
Move breezy.plugins.git to breezy.git.

Show diffs side-by-side

added added

removed removed

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