/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: 2020-05-06 02:13:25 UTC
  • mfrom: (7490.7.21 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200506021325-awbmmqu1zyorz7sj
Merge 3.1 branch.

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
 
 
21
17
from .lazy_import import lazy_import
22
18
lazy_import(globals(), """
 
19
import contextlib
23
20
import itertools
24
21
from breezy import (
25
 
    cleanup,
26
22
    config as _mod_config,
27
23
    debug,
28
 
    fetch,
 
24
    memorytree,
29
25
    repository,
30
26
    revision as _mod_revision,
31
27
    tag as _mod_tag,
34
30
    urlutils,
35
31
    )
36
32
from breezy.bzr import (
 
33
    fetch,
37
34
    remote,
38
35
    vf_search,
39
36
    )
42
39
 
43
40
from . import (
44
41
    controldir,
 
42
    errors,
45
43
    registry,
46
44
    )
47
 
from .decorators import (
48
 
    only_raises,
49
 
    )
50
45
from .hooks import Hooks
51
46
from .inter import InterObject
52
47
from .lock import LogicalLockResult
53
 
from .sixish import (
54
 
    BytesIO,
55
 
    viewitems,
56
 
    )
57
 
from .trace import mutter, mutter_callsite, note, is_quiet
 
48
from .trace import mutter, mutter_callsite, note, is_quiet, warning
58
49
 
59
50
 
60
51
class UnstackableBranchFormat(errors.BzrError):
61
52
 
62
53
    _fmt = ("The branch '%(url)s'(%(format)s) is not a stackable format. "
63
 
        "You will need to upgrade the branch to permit branch stacking.")
 
54
            "You will need to upgrade the branch to permit branch stacking.")
64
55
 
65
56
    def __init__(self, format, url):
66
57
        errors.BzrError.__init__(self)
96
87
        self._revision_id_to_revno_cache = None
97
88
        self._partial_revision_id_to_revno_cache = {}
98
89
        self._partial_revision_history_cache = []
99
 
        self._tags_bytes = None
100
90
        self._last_revision_info_cache = None
101
91
        self._master_branch_cache = None
102
92
        self._merge_sorted_revisions_cache = None
159
149
        repository._iter_for_revno(
160
150
            self.repository, self._partial_revision_history_cache,
161
151
            stop_index=stop_index, stop_revision=stop_revision)
162
 
        if self._partial_revision_history_cache[-1] == _mod_revision.NULL_REVISION:
 
152
        if self._partial_revision_history_cache[-1] == \
 
153
                _mod_revision.NULL_REVISION:
163
154
            self._partial_revision_history_cache.pop()
164
155
 
165
156
    def _get_check_refs(self):
177
168
        For instance, if the branch is at URL/.bzr/branch,
178
169
        Branch.open(URL) -> a Branch instance.
179
170
        """
180
 
        control = controldir.ControlDir.open(base,
181
 
            possible_transports=possible_transports, _unsupported=_unsupported)
182
 
        return control.open_branch(unsupported=_unsupported,
 
171
        control = controldir.ControlDir.open(
 
172
            base, possible_transports=possible_transports,
 
173
            _unsupported=_unsupported)
 
174
        return control.open_branch(
 
175
            unsupported=_unsupported,
183
176
            possible_transports=possible_transports)
184
177
 
185
178
    @staticmethod
186
179
    def open_from_transport(transport, name=None, _unsupported=False,
187
 
            possible_transports=None):
 
180
                            possible_transports=None):
188
181
        """Open the branch rooted at transport"""
189
 
        control = controldir.ControlDir.open_from_transport(transport, _unsupported)
190
 
        return control.open_branch(name=name, unsupported=_unsupported,
 
182
        control = controldir.ControlDir.open_from_transport(
 
183
            transport, _unsupported)
 
184
        return control.open_branch(
 
185
            name=name, unsupported=_unsupported,
191
186
            possible_transports=possible_transports)
192
187
 
193
188
    @staticmethod
198
193
 
199
194
        Basically we keep looking up until we find the control directory or
200
195
        run into the root.  If there isn't one, raises NotBranchError.
201
 
        If there is one and it is either an unrecognised format or an unsupported
202
 
        format, UnknownFormatError or UnsupportedFormatError are raised.
203
 
        If there is one, it is returned, along with the unused portion of url.
 
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.
204
200
        """
205
 
        control, relpath = controldir.ControlDir.open_containing(url,
206
 
                                                         possible_transports)
 
201
        control, relpath = controldir.ControlDir.open_containing(
 
202
            url, possible_transports)
207
203
        branch = control.open_branch(possible_transports=possible_transports)
208
204
        return (branch, relpath)
209
205
 
258
254
        a_branch = Branch.open(url, possible_transports=possible_transports)
259
255
        return a_branch.repository
260
256
 
261
 
    def _get_tags_bytes(self):
262
 
        """Get the bytes of a serialised tags dict.
263
 
 
264
 
        Note that not all branches support tags, nor do all use the same tags
265
 
        logic: this method is specific to BasicTags. Other tag implementations
266
 
        may use the same method name and behave differently, safely, because
267
 
        of the double-dispatch via
268
 
        format.make_tags->tags_instance->get_tags_dict.
269
 
 
270
 
        :return: The bytes of the tags file.
271
 
        :seealso: Branch._set_tags_bytes.
272
 
        """
273
 
        with self.lock_read():
274
 
            if self._tags_bytes is None:
275
 
                self._tags_bytes = self._transport.get_bytes('tags')
276
 
            return self._tags_bytes
277
 
 
278
257
    def _get_nick(self, local=False, possible_transports=None):
279
258
        config = self.get_config()
280
259
        # explicit overrides master, but don't look for master if local is True
292
271
                # Silently fall back to local implicit nick if the master is
293
272
                # unavailable
294
273
                mutter("Could not connect to bound branch, "
295
 
                    "falling back to local nick.\n " + str(e))
 
274
                       "falling back to local nick.\n " + str(e))
296
275
        return config.get_nickname()
297
276
 
298
277
    def _set_nick(self, nick):
321
300
        new_history = []
322
301
        check_not_reserved_id = _mod_revision.check_not_reserved_id
323
302
        # Do not include ghosts or graph origin in revision_history
324
 
        while (current_rev_id in parents_map and
325
 
               len(parents_map[current_rev_id]) > 0):
 
303
        while (current_rev_id in parents_map
 
304
               and len(parents_map[current_rev_id]) > 0):
326
305
            check_not_reserved_id(current_rev_id)
327
306
            new_history.append(current_rev_id)
328
307
            current_rev_id = parents_map[current_rev_id][0]
380
359
        provide a more efficient implementation.
381
360
        """
382
361
        if len(revno) == 1:
383
 
            return self.get_rev_id(revno[0])
 
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)
384
366
        revision_id_to_revno = self.get_revision_id_to_revno_map()
385
367
        revision_ids = [revision_id for revision_id, this_revno
386
 
                        in viewitems(revision_id_to_revno)
 
368
                        in revision_id_to_revno.items()
387
369
                        if revno == this_revno]
388
370
        if len(revision_ids) == 1:
389
371
            return revision_ids[0]
428
410
        :return: A dictionary mapping revision_id => dotted revno.
429
411
            This dictionary should not be modified by the caller.
430
412
        """
 
413
        if 'evil' in debug.debug_flags:
 
414
            mutter_callsite(
 
415
                3, "get_revision_id_to_revno_map scales with ancestry.")
431
416
        with self.lock_read():
432
417
            if self._revision_id_to_revno_cache is not None:
433
418
                mapping = self._revision_id_to_revno_cache
434
419
            else:
435
420
                mapping = self._gen_revno_map()
436
421
                self._cache_revision_id_to_revno(mapping)
437
 
            # TODO: jam 20070417 Since this is being cached, should we be returning
438
 
            #       a copy?
439
 
            # I would rather not, and instead just declare that users should not
440
 
            # modify the return value.
 
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.
441
426
            return mapping
442
427
 
443
428
    def _gen_revno_map(self):
450
435
 
451
436
        :return: A dictionary mapping revision_id => dotted revno.
452
437
        """
453
 
        revision_id_to_revno = dict((rev_id, revno)
454
 
            for rev_id, depth, revno, end_of_merge
455
 
             in self.iter_merge_sorted_revisions())
 
438
        revision_id_to_revno = {
 
439
            rev_id: revno for rev_id, depth, revno, end_of_merge
 
440
            in self.iter_merge_sorted_revisions()}
456
441
        return revision_id_to_revno
457
442
 
458
443
    def iter_merge_sorted_revisions(self, start_revision_id=None,
459
 
            stop_revision_id=None, stop_rule='exclude', direction='reverse'):
 
444
                                    stop_revision_id=None,
 
445
                                    stop_rule='exclude', direction='reverse'):
460
446
        """Walk the revisions for a branch in merge sorted order.
461
447
 
462
448
        Merge sorted order is the output from a merge-aware,
474
460
            * 'include' - the stop revision is the last item in the result
475
461
            * 'with-merges' - include the stop revision and all of its
476
462
              merged revisions in the result
477
 
            * 'with-merges-without-common-ancestry' - filter out revisions 
 
463
            * 'with-merges-without-common-ancestry' - filter out revisions
478
464
              that are in both ancestries
479
465
        :param direction: either 'reverse' or 'forward':
480
466
 
523
509
                raise ValueError('invalid direction %r' % direction)
524
510
 
525
511
    def _filter_merge_sorted_revisions(self, merge_sorted_revisions,
526
 
        start_revision_id, stop_revision_id, stop_rule):
 
512
                                       start_revision_id, stop_revision_id,
 
513
                                       stop_rule):
527
514
        """Iterate over an inclusive range of sorted revisions."""
528
515
        rev_iter = iter(merge_sorted_revisions)
529
516
        if start_revision_id is not None:
584
571
                if rev_id == left_parent:
585
572
                    # reached the left parent after the stop_revision
586
573
                    return
587
 
                if (not reached_stop_revision_id or
588
 
                        rev_id in revision_id_whitelist):
 
574
                if (not reached_stop_revision_id
 
575
                        or rev_id in revision_id_whitelist):
589
576
                    yield (rev_id, node.merge_depth, node.revno,
590
 
                       node.end_of_merge)
 
577
                           node.end_of_merge)
591
578
                    if reached_stop_revision_id or rev_id == stop_revision_id:
592
579
                        # only do the merged revs of rev_id from now on
593
580
                        rev = self.repository.get_revision(rev_id)
603
590
        # ancestry. Given the order guaranteed by the merge sort, we will see
604
591
        # uninteresting descendants of the first parent of our tip before the
605
592
        # tip itself.
606
 
        first = next(rev_iter)
 
593
        try:
 
594
            first = next(rev_iter)
 
595
        except StopIteration:
 
596
            return
607
597
        (rev_id, merge_depth, revno, end_of_merge) = first
608
598
        yield first
609
599
        if not merge_depth:
646
636
        """Tell this branch object not to release the physical lock when this
647
637
        object is unlocked.
648
638
 
649
 
        If lock_write doesn't return a token, then this method is not supported.
 
639
        If lock_write doesn't return a token, then this method is not
 
640
        supported.
650
641
        """
651
642
        self.control_files.leave_in_place()
652
643
 
654
645
        """Tell this branch object to release the physical lock when this
655
646
        object is unlocked, even if it didn't originally acquire it.
656
647
 
657
 
        If lock_write doesn't return a token, then this method is not supported.
 
648
        If lock_write doesn't return a token, then this method is not
 
649
        supported.
658
650
        """
659
651
        self.control_files.dont_leave_in_place()
660
652
 
678
670
            raise errors.UpgradeRequired(self.user_url)
679
671
        self.get_config_stack().set('append_revisions_only', enabled)
680
672
 
681
 
    def set_reference_info(self, file_id, tree_path, branch_location):
682
 
        """Set the branch location to use for a tree reference."""
683
 
        raise errors.UnsupportedOperation(self.set_reference_info, self)
684
 
 
685
 
    def get_reference_info(self, file_id):
686
 
        """Get the tree_path and branch_location for a tree reference."""
687
 
        raise errors.UnsupportedOperation(self.get_reference_info, self)
688
 
 
689
 
    def fetch(self, from_branch, last_revision=None, limit=None):
 
673
    def fetch(self, from_branch, stop_revision=None, limit=None, lossy=False):
690
674
        """Copy revisions from from_branch into this branch.
691
675
 
692
676
        :param from_branch: Where to copy from.
693
 
        :param last_revision: What revision to stop at (None for at the end
 
677
        :param stop_revision: What revision to stop at (None for at the end
694
678
                              of the branch.
695
679
        :param limit: Optional rough limit of revisions to fetch
696
680
        :return: None
697
681
        """
698
682
        with self.lock_write():
699
683
            return InterBranch.get(from_branch, self).fetch(
700
 
                    last_revision, limit=limit)
 
684
                stop_revision, limit=limit, lossy=lossy)
701
685
 
702
686
    def get_bound_location(self):
703
687
        """Return the URL of the branch we are bound to.
725
709
        :param revprops: Optional dictionary of revision properties.
726
710
        :param revision_id: Optional revision id.
727
711
        :param lossy: Whether to discard data that can not be natively
728
 
            represented, when pushing to a foreign VCS 
 
712
            represented, when pushing to a foreign VCS
729
713
        """
730
714
 
731
715
        if config_stack is None:
732
716
            config_stack = self.get_config_stack()
733
717
 
734
 
        return self.repository.get_commit_builder(self, parents, config_stack,
735
 
            timestamp, timezone, committer, revprops, revision_id,
736
 
            lossy)
 
718
        return self.repository.get_commit_builder(
 
719
            self, parents, config_stack, timestamp, timezone, committer,
 
720
            revprops, revision_id, lossy)
737
721
 
738
722
    def get_master_branch(self, possible_transports=None):
739
723
        """Return the branch we are bound to.
778
762
                if not graph.is_ancestor(last_rev, revision_id):
779
763
                    # our previous tip is not merged into stop_revision
780
764
                    raise errors.DivergedBranches(self, other_branch)
781
 
            revno = graph.find_distance_to_null(revision_id, known_revision_ids)
 
765
            revno = graph.find_distance_to_null(
 
766
                revision_id, known_revision_ids)
782
767
            self.set_last_revision_info(revno, revision_id)
783
768
 
784
769
    def set_parent(self, url):
788
773
        # FIXUP this and get_parent in a future branch format bump:
789
774
        # read and rewrite the file. RBC 20060125
790
775
        if url is not None:
791
 
            if isinstance(url, unicode):
 
776
            if isinstance(url, str):
792
777
                try:
793
 
                    url = url.encode('ascii')
 
778
                    url.encode('ascii')
794
779
                except UnicodeEncodeError:
795
 
                    raise urlutils.InvalidURL(url,
796
 
                        "Urls must be 7-bit ascii, "
 
780
                    raise urlutils.InvalidURL(
 
781
                        url, "Urls must be 7-bit ascii, "
797
782
                        "use breezy.urlutils.escape")
798
783
            url = urlutils.relative_url(self.base, url)
799
784
        with self.lock_write():
810
795
        if not self._format.supports_stacking():
811
796
            raise UnstackableBranchFormat(self._format, self.user_url)
812
797
        with self.lock_write():
813
 
            # XXX: Changing from one fallback repository to another does not check
814
 
            # that all the data you need is present in the new fallback.
 
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.
815
800
            # Possibly it should.
816
801
            self._check_stackable_repo()
817
802
            if not url:
818
803
                try:
819
 
                    old_url = self.get_stacked_on_url()
 
804
                    self.get_stacked_on_url()
820
805
                except (errors.NotStacked, UnstackableBranchFormat,
821
 
                    errors.UnstackableRepositoryFormat):
 
806
                        errors.UnstackableRepositoryFormat):
822
807
                    return
823
808
                self._unstack()
824
809
            else:
825
 
                self._activate_fallback_location(url,
826
 
                    possible_transports=[self.controldir.root_transport])
 
810
                self._activate_fallback_location(
 
811
                    url, possible_transports=[self.controldir.root_transport])
827
812
            # write this out after the repository is stacked to avoid setting a
828
813
            # stacked config that doesn't work.
829
814
            self._set_config_location('stacked_on_location', url)
833
818
 
834
819
        Don't call this directly, use set_stacked_on_url(None).
835
820
        """
836
 
        pb = ui.ui_factory.nested_progress_bar()
837
 
        try:
 
821
        with ui.ui_factory.nested_progress_bar() as pb:
838
822
            pb.update(gettext("Unstacking"))
839
823
            # The basic approach here is to fetch the tip of the branch,
840
824
            # including all available ghosts, from the existing stacked
841
 
            # repository into a new repository object without the fallbacks. 
 
825
            # repository into a new repository object without the fallbacks.
842
826
            #
843
827
            # XXX: See <https://launchpad.net/bugs/397286> - this may not be
844
828
            # correct for CHKMap repostiories
845
829
            old_repository = self.repository
846
830
            if len(old_repository._fallback_repositories) != 1:
847
 
                raise AssertionError("can't cope with fallback repositories "
848
 
                    "of %r (fallbacks: %r)" % (old_repository,
849
 
                        old_repository._fallback_repositories))
 
831
                raise AssertionError(
 
832
                    "can't cope with fallback repositories "
 
833
                    "of %r (fallbacks: %r)" % (
 
834
                        old_repository, old_repository._fallback_repositories))
850
835
            # Open the new repository object.
851
836
            # Repositories don't offer an interface to remove fallback
852
837
            # repositories today; take the conceptually simpler option and just
859
844
                self.controldir.root_transport.base)
860
845
            new_repository = new_bzrdir.find_repository()
861
846
            if new_repository._fallback_repositories:
862
 
                raise AssertionError("didn't expect %r to have "
863
 
                    "fallback_repositories"
 
847
                raise AssertionError(
 
848
                    "didn't expect %r to have fallback_repositories"
864
849
                    % (self.repository,))
865
850
            # Replace self.repository with the new repository.
866
851
            # Do our best to transfer the lock state (i.e. lock-tokens and
893
878
            if old_lock_count == 0:
894
879
                raise AssertionError(
895
880
                    'old_repository should have been locked at least once.')
896
 
            for i in range(old_lock_count-1):
 
881
            for i in range(old_lock_count - 1):
897
882
                self.repository.lock_write()
898
883
            # Fetch from the old repository into the new.
899
884
            with old_repository.lock_read():
904
889
                    tags_to_fetch = set(self.tags.get_reverse_tag_dict())
905
890
                except errors.TagsNotSupported:
906
891
                    tags_to_fetch = set()
907
 
                fetch_spec = vf_search.NotInOtherForRevs(self.repository,
908
 
                    old_repository, required_ids=[self.last_revision()],
 
892
                fetch_spec = vf_search.NotInOtherForRevs(
 
893
                    self.repository, old_repository,
 
894
                    required_ids=[self.last_revision()],
909
895
                    if_present_ids=tags_to_fetch, find_ghosts=True).execute()
910
896
                self.repository.fetch(old_repository, fetch_spec=fetch_spec)
911
 
        finally:
912
 
            pb.finished()
913
 
 
914
 
    def _set_tags_bytes(self, bytes):
915
 
        """Mirror method for _get_tags_bytes.
916
 
 
917
 
        :seealso: Branch._get_tags_bytes.
918
 
        """
919
 
        with self.lock_write():
920
 
            self._tags_bytes = bytes
921
 
            return self._transport.put_bytes('tags', bytes)
922
897
 
923
898
    def _cache_revision_history(self, rev_history):
924
899
        """Set the cached revision history to rev_history.
955
930
        self._merge_sorted_revisions_cache = None
956
931
        self._partial_revision_history_cache = []
957
932
        self._partial_revision_id_to_revno_cache = {}
958
 
        self._tags_bytes = None
959
933
 
960
934
    def _gen_revision_history(self):
961
935
        """Return sequence of revision hashes on to this branch.
1005
979
        """
1006
980
        with self.lock_read():
1007
981
            if self._last_revision_info_cache is None:
1008
 
                self._last_revision_info_cache = self._read_last_revision_info()
 
982
                self._last_revision_info_cache = (
 
983
                    self._read_last_revision_info())
1009
984
            return self._last_revision_info_cache
1010
985
 
1011
986
    def _read_last_revision_info(self):
1065
1040
 
1066
1041
        :returns: PullResult instance
1067
1042
        """
1068
 
        return InterBranch.get(source, self).pull(overwrite=overwrite,
1069
 
            stop_revision=stop_revision,
 
1043
        return InterBranch.get(source, self).pull(
 
1044
            overwrite=overwrite, stop_revision=stop_revision,
1070
1045
            possible_transports=possible_transports, *args, **kwargs)
1071
1046
 
1072
1047
    def push(self, target, overwrite=False, stop_revision=None, lossy=False,
1073
 
            *args, **kwargs):
 
1048
             *args, **kwargs):
1074
1049
        """Mirror this branch into target.
1075
1050
 
1076
1051
        This branch is considered to be 'local', having low latency.
1077
1052
        """
1078
 
        return InterBranch.get(self, target).push(overwrite, stop_revision,
1079
 
            lossy, *args, **kwargs)
 
1053
        return InterBranch.get(self, target).push(
 
1054
            overwrite, stop_revision, lossy, *args, **kwargs)
1080
1055
 
1081
1056
    def basis_tree(self):
1082
1057
        """Return `Tree` object for last revision."""
1095
1070
        # This is an old-format absolute path to a local branch
1096
1071
        # turn it into a url
1097
1072
        if parent.startswith('/'):
1098
 
            parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
1073
            parent = urlutils.local_path_to_url(parent)
1099
1074
        try:
1100
1075
            return urlutils.join(self.base[:-1], parent)
1101
 
        except urlutils.InvalidURLJoin as e:
 
1076
        except urlutils.InvalidURLJoin:
1102
1077
            raise errors.InaccessibleParent(parent, self.user_url)
1103
1078
 
1104
1079
    def _get_parent_location(self):
1213
1188
        if revno < 1 or revno > self.revno():
1214
1189
            raise errors.InvalidRevisionNumber(revno)
1215
1190
 
1216
 
    def clone(self, to_controldir, revision_id=None, repository_policy=None):
 
1191
    def clone(self, to_controldir, revision_id=None, name=None,
 
1192
              repository_policy=None, tag_selector=None):
1217
1193
        """Clone this branch into to_controldir preserving all semantic values.
1218
1194
 
1219
1195
        Most API users will want 'create_clone_on_transport', which creates a
1222
1198
        revision_id: if not None, the revision history in the new branch will
1223
1199
                     be truncated to end with revision_id.
1224
1200
        """
1225
 
        result = to_controldir.create_branch()
 
1201
        result = to_controldir.create_branch(name=name)
1226
1202
        with self.lock_read(), result.lock_write():
1227
1203
            if repository_policy is not None:
1228
1204
                repository_policy.configure_branch(result)
1229
 
            self.copy_content_into(result, revision_id=revision_id)
 
1205
            self.copy_content_into(
 
1206
                result, revision_id=revision_id, tag_selector=tag_selector)
1230
1207
        return result
1231
1208
 
1232
1209
    def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1233
 
            repository=None):
 
1210
               repository=None, lossy=False, tag_selector=None):
1234
1211
        """Create a new line of development from the branch, into to_controldir.
1235
1212
 
1236
1213
        to_controldir controls the branch format.
1238
1215
        revision_id: if not None, the revision history in the new branch will
1239
1216
                     be truncated to end with revision_id.
1240
1217
        """
1241
 
        if (repository_policy is not None and
1242
 
            repository_policy.requires_stacking()):
 
1218
        if (repository_policy is not None
 
1219
                and repository_policy.requires_stacking()):
1243
1220
            to_controldir._format.require_stacking(_skip_repo=True)
1244
1221
        result = to_controldir.create_branch(repository=repository)
 
1222
        if lossy:
 
1223
            raise errors.LossyPushToSameVCS(self, result)
1245
1224
        with self.lock_read(), result.lock_write():
1246
1225
            if repository_policy is not None:
1247
1226
                repository_policy.configure_branch(result)
1248
 
            self.copy_content_into(result, revision_id=revision_id)
 
1227
            self.copy_content_into(
 
1228
                result, revision_id=revision_id, tag_selector=tag_selector)
1249
1229
            master_url = self.get_bound_location()
1250
1230
            if master_url is None:
1251
 
                result.set_parent(self.controldir.root_transport.base)
 
1231
                result.set_parent(self.user_url)
1252
1232
            else:
1253
1233
                result.set_parent(master_url)
1254
1234
        return result
1271
1251
        else:
1272
1252
            graph = self.repository.get_graph()
1273
1253
            try:
1274
 
                revno = graph.find_distance_to_null(revision_id, 
1275
 
                    [(source_revision_id, source_revno)])
 
1254
                revno = graph.find_distance_to_null(
 
1255
                    revision_id, [(source_revision_id, source_revno)])
1276
1256
            except errors.GhostRevisionsHaveNoRevno:
1277
1257
                # Default to 1, if we can't find anything else
1278
1258
                revno = 1
1279
1259
        destination.set_last_revision_info(revno, revision_id)
1280
1260
 
1281
 
    def copy_content_into(self, destination, revision_id=None):
 
1261
    def copy_content_into(self, destination, revision_id=None, tag_selector=None):
1282
1262
        """Copy the content of self into destination.
1283
1263
 
1284
1264
        revision_id: if not None, the revision history in the new branch will
1285
1265
                     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
1286
1268
        """
1287
1269
        return InterBranch.get(self, destination).copy_content_into(
1288
 
            revision_id=revision_id)
 
1270
            revision_id=revision_id, tag_selector=tag_selector)
1289
1271
 
1290
1272
    def update_references(self, target):
1291
 
        if not getattr(self._format, 'supports_reference_locations', False):
1292
 
            return
1293
 
        reference_dict = self._get_all_reference_info()
1294
 
        if len(reference_dict) == 0:
1295
 
            return
1296
 
        old_base = self.base
1297
 
        new_base = target.base
1298
 
        target_reference_dict = target._get_all_reference_info()
1299
 
        for file_id, (tree_path, branch_location) in viewitems(reference_dict):
1300
 
            branch_location = urlutils.rebase_url(branch_location,
1301
 
                                                  old_base, new_base)
1302
 
            target_reference_dict.setdefault(
1303
 
                file_id, (tree_path, branch_location))
1304
 
        target._set_all_reference_info(target_reference_dict)
 
1273
        if not self._format.supports_reference_locations:
 
1274
            return
 
1275
        return InterBranch.get(self, target).update_references()
1305
1276
 
1306
1277
    def check(self, refs):
1307
1278
        """Check consistency of the branch.
1323
1294
            if actual_revno != last_revno:
1324
1295
                result.errors.append(errors.BzrCheckError(
1325
1296
                    'revno does not match len(mainline) %s != %s' % (
1326
 
                    last_revno, actual_revno)))
 
1297
                        last_revno, actual_revno)))
1327
1298
            # TODO: We should probably also check that self.revision_history
1328
1299
            # matches the repository for older branch formats.
1329
 
            # If looking for the code that cross-checks repository parents against
1330
 
            # the Graph.iter_lefthand_ancestry output, that is now a repository
1331
 
            # specific check.
 
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.
1332
1303
            return result
1333
1304
 
1334
1305
    def _get_checkout_format(self, lightweight=False):
1340
1311
        return format
1341
1312
 
1342
1313
    def create_clone_on_transport(self, to_transport, revision_id=None,
1343
 
        stacked_on=None, create_prefix=False, use_existing_dir=False,
1344
 
        no_tree=None):
 
1314
                                  stacked_on=None, create_prefix=False,
 
1315
                                  use_existing_dir=False, no_tree=None,
 
1316
                                  tag_selector=None):
1345
1317
        """Create a clone of this branch and its bzrdir.
1346
1318
 
1347
1319
        :param to_transport: The transport to clone onto.
1354
1326
        """
1355
1327
        # XXX: Fix the bzrdir API to allow getting the branch back from the
1356
1328
        # clone call. Or something. 20090224 RBC/spiv.
1357
 
        # XXX: Should this perhaps clone colocated branches as well, 
 
1329
        # XXX: Should this perhaps clone colocated branches as well,
1358
1330
        # rather than just the default branch? 20100319 JRV
1359
1331
        if revision_id is None:
1360
1332
            revision_id = self.last_revision()
1361
 
        dir_to = self.controldir.clone_on_transport(to_transport,
1362
 
            revision_id=revision_id, stacked_on=stacked_on,
 
1333
        dir_to = self.controldir.clone_on_transport(
 
1334
            to_transport, revision_id=revision_id, stacked_on=stacked_on,
1363
1335
            create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1364
 
            no_tree=no_tree)
 
1336
            no_tree=no_tree, tag_selector=tag_selector)
1365
1337
        return dir_to.open_branch()
1366
1338
 
1367
1339
    def create_checkout(self, to_location, revision_id=None,
1368
1340
                        lightweight=False, accelerator_tree=None,
1369
 
                        hardlink=False):
 
1341
                        hardlink=False, recurse_nested=True):
1370
1342
        """Create a checkout of a branch.
1371
1343
 
1372
1344
        :param to_location: The url to produce the checkout at
1379
1351
            content is different.
1380
1352
        :param hardlink: If true, hard-link files from accelerator_tree,
1381
1353
            where possible.
 
1354
        :param recurse_nested: Whether to recurse into nested trees
1382
1355
        :return: The tree of the created checkout
1383
1356
        """
1384
1357
        t = transport.get_transport(to_location)
1396
1369
                pass
1397
1370
            else:
1398
1371
                raise errors.AlreadyControlDirError(t.base)
1399
 
            if checkout.control_transport.base == self.controldir.control_transport.base:
 
1372
            if (checkout.control_transport.base
 
1373
                    == self.controldir.control_transport.base):
1400
1374
                # When checking out to the same control directory,
1401
1375
                # always create a lightweight checkout
1402
1376
                lightweight = True
1405
1379
            from_branch = checkout.set_branch_reference(target_branch=self)
1406
1380
        else:
1407
1381
            policy = checkout.determine_repository_policy()
1408
 
            repo = policy.acquire_repository()[0]
 
1382
            policy.acquire_repository()
1409
1383
            checkout_branch = checkout.create_branch()
1410
1384
            checkout_branch.bind(self)
1411
1385
            # pull up to the specified revision_id to set the initial
1418
1392
                                           hardlink=hardlink)
1419
1393
        basis_tree = tree.basis_tree()
1420
1394
        with basis_tree.lock_read():
1421
 
            for path, file_id in basis_tree.iter_references():
1422
 
                reference_parent = self.reference_parent(file_id, path)
1423
 
                reference_parent.create_checkout(tree.abspath(path),
1424
 
                    basis_tree.get_reference_revision(path, file_id),
1425
 
                    lightweight)
 
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)
1426
1403
        return tree
1427
1404
 
1428
1405
    def reconcile(self, thorough=True):
1429
 
        """Make sure the data stored in this branch is consistent."""
1430
 
        from breezy.reconcile import BranchReconciler
1431
 
        with self.lock_write():
1432
 
            reconciler = BranchReconciler(self, thorough=thorough)
1433
 
            reconciler.reconcile()
1434
 
            return reconciler
1435
 
 
1436
 
    def reference_parent(self, file_id, path, possible_transports=None):
1437
 
        """Return the parent branch for a tree-reference file_id
1438
 
 
1439
 
        :param file_id: The file_id of the tree reference
1440
 
        :param path: The path of the file_id in the tree
1441
 
        :return: A branch associated with the file_id
 
1406
        """Make sure the data stored in this branch is consistent.
 
1407
 
 
1408
        :return: A `ReconcileResult` object.
1442
1409
        """
1443
 
        # FIXME should provide multiple branches, based on config
1444
 
        return Branch.open(self.controldir.root_transport.clone(path).base,
1445
 
                           possible_transports=possible_transports)
 
1410
        raise NotImplementedError(self.reconcile)
1446
1411
 
1447
1412
    def supports_tags(self):
1448
1413
        return self._format.supports_tags()
1516
1481
        if_present_fetch.discard(_mod_revision.NULL_REVISION)
1517
1482
        return must_fetch, if_present_fetch
1518
1483
 
 
1484
    def create_memorytree(self):
 
1485
        """Create a memory tree for this branch.
 
1486
 
 
1487
        :return: An in-memory MutableTree instance
 
1488
        """
 
1489
        return memorytree.MemoryTree.create_on_branch(self)
 
1490
 
1519
1491
 
1520
1492
class BranchFormat(controldir.ControlComponentFormat):
1521
1493
    """An encapsulation of the initialization and open routines for a format.
1622
1594
        raise NotImplementedError(self.network_name)
1623
1595
 
1624
1596
    def open(self, controldir, name=None, _found=False, ignore_fallbacks=False,
1625
 
            found_repository=None, possible_transports=None):
 
1597
             found_repository=None, possible_transports=None):
1626
1598
        """Return the branch object for controldir.
1627
1599
 
1628
1600
        :param controldir: A ControlDir that contains a branch.
1644
1616
 
1645
1617
    def supports_leaving_lock(self):
1646
1618
        """True if this format supports leaving locks in place."""
1647
 
        return False # by default
 
1619
        return False  # by default
1648
1620
 
1649
1621
    def __str__(self):
1650
1622
        return self.get_format_description().rstrip()
1665
1637
        """True if uncommitted changes can be stored in this branch."""
1666
1638
        return True
1667
1639
 
 
1640
    def stores_revno(self):
 
1641
        """True if this branch format store revision numbers."""
 
1642
        return True
 
1643
 
1668
1644
 
1669
1645
class BranchHooks(Hooks):
1670
1646
    """A dictionary mapping hook name to a list of callables for branch hooks.
1680
1656
        notified.
1681
1657
        """
1682
1658
        Hooks.__init__(self, "breezy.branch", "Branch.hooks")
1683
 
        self.add_hook('open',
 
1659
        self.add_hook(
 
1660
            'open',
1684
1661
            "Called with the Branch object that has been opened after a "
1685
1662
            "branch is opened.", (1, 8))
1686
 
        self.add_hook('post_push',
 
1663
        self.add_hook(
 
1664
            'post_push',
1687
1665
            "Called after a push operation completes. post_push is called "
1688
 
            "with a breezy.branch.BranchPushResult object and only runs in the "
1689
 
            "bzr client.", (0, 15))
1690
 
        self.add_hook('post_pull',
 
1666
            "with a breezy.branch.BranchPushResult object and only runs in "
 
1667
            "the bzr client.", (0, 15))
 
1668
        self.add_hook(
 
1669
            'post_pull',
1691
1670
            "Called after a pull operation completes. post_pull is called "
1692
1671
            "with a breezy.branch.PullResult object and only runs in the "
1693
1672
            "bzr client.", (0, 15))
1694
 
        self.add_hook('pre_commit',
 
1673
        self.add_hook(
 
1674
            'pre_commit',
1695
1675
            "Called after a commit is calculated but before it is "
1696
1676
            "completed. pre_commit is called with (local, master, old_revno, "
1697
1677
            "old_revid, future_revno, future_revid, tree_delta, future_tree"
1701
1681
            " future_tree is an in-memory tree obtained from "
1702
1682
            "CommitBuilder.revision_tree() and hooks MUST NOT modify this "
1703
1683
            "tree.", (0, 91))
1704
 
        self.add_hook('post_commit',
 
1684
        self.add_hook(
 
1685
            'post_commit',
1705
1686
            "Called in the bzr client after a commit has completed. "
1706
1687
            "post_commit is called with (local, master, old_revno, old_revid, "
1707
1688
            "new_revno, new_revid). old_revid is NULL_REVISION for the first "
1708
1689
            "commit to a branch.", (0, 15))
1709
 
        self.add_hook('post_uncommit',
 
1690
        self.add_hook(
 
1691
            'post_uncommit',
1710
1692
            "Called in the bzr client after an uncommit completes. "
1711
1693
            "post_uncommit is called with (local, master, old_revno, "
1712
1694
            "old_revid, new_revno, new_revid) where local is the local branch "
1713
1695
            "or None, master is the target branch, and an empty branch "
1714
1696
            "receives new_revno of 0, new_revid of None.", (0, 15))
1715
 
        self.add_hook('pre_change_branch_tip',
 
1697
        self.add_hook(
 
1698
            'pre_change_branch_tip',
1716
1699
            "Called in bzr client and server before a change to the tip of a "
1717
1700
            "branch is made. pre_change_branch_tip is called with a "
1718
1701
            "breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1719
1702
            "commit, uncommit will all trigger this hook.", (1, 6))
1720
 
        self.add_hook('post_change_branch_tip',
 
1703
        self.add_hook(
 
1704
            'post_change_branch_tip',
1721
1705
            "Called in bzr client and server after a change to the tip of a "
1722
1706
            "branch is made. post_change_branch_tip is called with a "
1723
1707
            "breezy.branch.ChangeBranchTipParams. Note that push, pull, "
1724
1708
            "commit, uncommit will all trigger this hook.", (1, 4))
1725
 
        self.add_hook('transform_fallback_location',
 
1709
        self.add_hook(
 
1710
            'transform_fallback_location',
1726
1711
            "Called when a stacked branch is activating its fallback "
1727
1712
            "locations. transform_fallback_location is called with (branch, "
1728
1713
            "url), and should return a new url. Returning the same url "
1734
1719
            "multiple hooks installed for transform_fallback_location, "
1735
1720
            "all are called with the url returned from the previous hook."
1736
1721
            "The order is however undefined.", (1, 9))
1737
 
        self.add_hook('automatic_tag_name',
 
1722
        self.add_hook(
 
1723
            'automatic_tag_name',
1738
1724
            "Called to determine an automatic tag name for a revision. "
1739
1725
            "automatic_tag_name is called with (branch, revision_id) and "
1740
1726
            "should return a tag name or None if no tag name could be "
1741
1727
            "determined. The first non-None tag name returned will be used.",
1742
1728
            (2, 2))
1743
 
        self.add_hook('post_branch_init',
 
1729
        self.add_hook(
 
1730
            'post_branch_init',
1744
1731
            "Called after new branch initialization completes. "
1745
1732
            "post_branch_init is called with a "
1746
1733
            "breezy.branch.BranchInitHookParams. "
1747
1734
            "Note that init, branch and checkout (both heavyweight and "
1748
1735
            "lightweight) will all trigger this hook.", (2, 2))
1749
 
        self.add_hook('post_switch',
 
1736
        self.add_hook(
 
1737
            'post_switch',
1750
1738
            "Called after a checkout switches branch. "
1751
1739
            "post_switch is called with a "
1752
1740
            "breezy.branch.SwitchHookParams.", (2, 2))
1753
1741
 
1754
1742
 
1755
 
 
1756
1743
# install the default hooks into the Branch class.
1757
1744
Branch.hooks = BranchHooks()
1758
1745
 
1863
1850
        return self.__dict__ == other.__dict__
1864
1851
 
1865
1852
    def __repr__(self):
1866
 
        return "<%s for %s to (%s, %s)>" % (self.__class__.__name__,
1867
 
            self.control_dir, self.to_branch,
 
1853
        return "<%s for %s to (%s, %s)>" % (
 
1854
            self.__class__.__name__, self.control_dir, self.to_branch,
1868
1855
            self.revision_id)
1869
1856
 
1870
1857
 
1878
1865
 
1879
1866
    def get_default(self):
1880
1867
        """Return the current default format."""
1881
 
        if (self._default_format_key is not None and
1882
 
            self._default_format is None):
 
1868
        if (self._default_format_key is not None
 
1869
                and self._default_format is None):
1883
1870
            self._default_format = self.get(self._default_format_key)
1884
1871
        return self._default_format
1885
1872
 
1908
1895
# formats which have no format string are not discoverable
1909
1896
# and not independently creatable, so are not registered.
1910
1897
format_registry.register_lazy(
1911
 
    "Bazaar-NG branch format 5\n", "breezy.bzr.fullhistory",
 
1898
    b"Bazaar-NG branch format 5\n", "breezy.bzr.fullhistory",
1912
1899
    "BzrBranchFormat5")
1913
1900
format_registry.register_lazy(
1914
 
    "Bazaar Branch Format 6 (bzr 0.15)\n",
 
1901
    b"Bazaar Branch Format 6 (bzr 0.15)\n",
1915
1902
    "breezy.bzr.branch", "BzrBranchFormat6")
1916
1903
format_registry.register_lazy(
1917
 
    "Bazaar Branch Format 7 (needs bzr 1.6)\n",
 
1904
    b"Bazaar Branch Format 7 (needs bzr 1.6)\n",
1918
1905
    "breezy.bzr.branch", "BzrBranchFormat7")
1919
1906
format_registry.register_lazy(
1920
 
    "Bazaar Branch Format 8 (needs bzr 1.15)\n",
 
1907
    b"Bazaar Branch Format 8 (needs bzr 1.15)\n",
1921
1908
    "breezy.bzr.branch", "BzrBranchFormat8")
1922
1909
format_registry.register_lazy(
1923
 
    "Bazaar-NG Branch Reference Format 1\n",
 
1910
    b"Bazaar-NG Branch Reference Format 1\n",
1924
1911
    "breezy.bzr.branch", "BranchReferenceFormat")
1925
1912
 
1926
 
format_registry.set_default_key("Bazaar Branch Format 7 (needs bzr 1.6)\n")
 
1913
format_registry.set_default_key(b"Bazaar Branch Format 7 (needs bzr 1.6)\n")
1927
1914
 
1928
1915
 
1929
1916
class BranchWriteLockResult(LogicalLockResult):
2012
1999
        tag_updates = getattr(self, "tag_updates", None)
2013
2000
        if not is_quiet():
2014
2001
            if self.old_revid != self.new_revid:
2015
 
                note(gettext('Pushed up to revision %d.') % self.new_revno)
 
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'))
2016
2008
            if tag_updates:
2017
 
                note(ngettext('%d tag updated.', '%d tags updated.', len(tag_updates)) % len(tag_updates))
 
2009
                note(ngettext('%d tag updated.', '%d tags updated.',
 
2010
                              len(tag_updates)) % len(tag_updates))
2018
2011
            if self.old_revid == self.new_revid and not tag_updates:
2019
2012
                if not tag_conflicts:
2020
2013
                    note(gettext('No new revisions or tags to push.'))
2040
2033
            if any.
2041
2034
        """
2042
2035
        note(gettext('checked branch {0} format {1}').format(
2043
 
                                self.branch.user_url, self.branch._format))
 
2036
            self.branch.user_url, self.branch._format))
2044
2037
        for error in self.errors:
2045
2038
            note(gettext('found error:%s'), error)
2046
2039
 
2059
2052
    @classmethod
2060
2053
    def _get_branch_formats_to_test(klass):
2061
2054
        """Return an iterable of format tuples for testing.
2062
 
        
 
2055
 
2063
2056
        :return: An iterable of (from_format, to_format) to use when testing
2064
2057
            this InterBranch class. Each InterBranch class should define this
2065
2058
            method itself.
2067
2060
        raise NotImplementedError(klass._get_branch_formats_to_test)
2068
2061
 
2069
2062
    def pull(self, overwrite=False, stop_revision=None,
2070
 
             possible_transports=None, local=False):
 
2063
             possible_transports=None, local=False, tag_selector=None):
2071
2064
        """Mirror source into target branch.
2072
2065
 
2073
2066
        The target branch is considered to be 'local', having low latency.
2077
2070
        raise NotImplementedError(self.pull)
2078
2071
 
2079
2072
    def push(self, overwrite=False, stop_revision=None, lossy=False,
2080
 
             _override_hook_source_branch=None):
 
2073
             _override_hook_source_branch=None, tag_selector=None):
2081
2074
        """Mirror the source branch into the target branch.
2082
2075
 
2083
2076
        The source branch is considered to be 'local', having low latency.
2084
2077
        """
2085
2078
        raise NotImplementedError(self.push)
2086
2079
 
2087
 
    def copy_content_into(self, revision_id=None):
 
2080
    def copy_content_into(self, revision_id=None, tag_selector=None):
2088
2081
        """Copy the content of source into target
2089
2082
 
2090
 
        revision_id: if not None, the revision history in the new branch will
2091
 
                     be truncated to end with revision_id.
 
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.
2092
2088
        """
2093
2089
        raise NotImplementedError(self.copy_content_into)
2094
2090
 
2095
 
    def fetch(self, stop_revision=None, limit=None):
 
2091
    def fetch(self, stop_revision=None, limit=None, lossy=False):
2096
2092
        """Fetch revisions.
2097
2093
 
2098
2094
        :param stop_revision: Last revision to fetch
2099
2095
        :param limit: Optional rough limit of revisions to fetch
 
2096
        :return: FetchResult object
2100
2097
        """
2101
2098
        raise NotImplementedError(self.fetch)
2102
2099
 
 
2100
    def update_references(self):
 
2101
        """Import reference information from source to target.
 
2102
        """
 
2103
        raise NotImplementedError(self.update_references)
 
2104
 
2103
2105
 
2104
2106
def _fix_overwrite_type(overwrite):
2105
2107
    if isinstance(overwrite, bool):
2129
2131
            return format._custom_format
2130
2132
        return format
2131
2133
 
2132
 
    def copy_content_into(self, revision_id=None):
 
2134
    def copy_content_into(self, revision_id=None, tag_selector=None):
2133
2135
        """Copy the content of source into target
2134
2136
 
2135
2137
        revision_id: if not None, the revision history in the new branch will
2136
2138
                     be truncated to end with revision_id.
2137
2139
        """
2138
2140
        with self.source.lock_read(), self.target.lock_write():
2139
 
            self.source.update_references(self.target)
2140
2141
            self.source._synchronize_history(self.target, revision_id)
 
2142
            self.update_references()
2141
2143
            try:
2142
2144
                parent = self.source.get_parent()
2143
2145
            except errors.InaccessibleParent as e:
2144
 
                mutter('parent was not accessible to copy: %s', e)
 
2146
                mutter('parent was not accessible to copy: %s', str(e))
2145
2147
            else:
2146
2148
                if parent:
2147
2149
                    self.target.set_parent(parent)
2148
2150
            if self.source._push_should_merge_tags():
2149
 
                self.source.tags.merge_to(self.target.tags)
 
2151
                self.source.tags.merge_to(self.target.tags, selector=tag_selector)
2150
2152
 
2151
 
    def fetch(self, stop_revision=None, limit=None):
 
2153
    def fetch(self, stop_revision=None, limit=None, lossy=False):
2152
2154
        if self.target.base == self.source.base:
2153
2155
            return (0, [])
2154
2156
        with self.source.lock_read(), self.target.lock_write():
2157
2159
            fetch_spec_factory.source_branch_stop_revision_id = stop_revision
2158
2160
            fetch_spec_factory.source_repo = self.source.repository
2159
2161
            fetch_spec_factory.target_repo = self.target.repository
2160
 
            fetch_spec_factory.target_repo_kind = fetch.TargetRepoKinds.PREEXISTING
 
2162
            fetch_spec_factory.target_repo_kind = (
 
2163
                fetch.TargetRepoKinds.PREEXISTING)
2161
2164
            fetch_spec_factory.limit = limit
2162
2165
            fetch_spec = fetch_spec_factory.make_fetch_spec()
2163
2166
            return self.target.repository.fetch(
2164
 
                    self.source.repository,
2165
 
                    fetch_spec=fetch_spec)
 
2167
                self.source.repository,
 
2168
                lossy=lossy,
 
2169
                fetch_spec=fetch_spec)
2166
2170
 
2167
2171
    def _update_revisions(self, stop_revision=None, overwrite=False,
2168
 
            graph=None):
 
2172
                          graph=None):
2169
2173
        with self.source.lock_read(), self.target.lock_write():
2170
2174
            other_revno, other_last_revision = self.source.last_revision_info()
2171
 
            stop_revno = None # unknown
 
2175
            stop_revno = None  # unknown
2172
2176
            if stop_revision is None:
2173
2177
                stop_revision = other_last_revision
2174
2178
                if _mod_revision.is_null(stop_revision):
2197
2201
                if graph is None:
2198
2202
                    graph = self.target.repository.get_graph()
2199
2203
                this_revno, this_last_revision = \
2200
 
                        self.target.last_revision_info()
2201
 
                stop_revno = graph.find_distance_to_null(stop_revision,
2202
 
                                [(other_last_revision, other_revno),
2203
 
                                 (this_last_revision, this_revno)])
 
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)])
2204
2208
            self.target.set_last_revision_info(stop_revno, stop_revision)
2205
2209
 
2206
2210
    def pull(self, overwrite=False, stop_revision=None,
2207
2211
             possible_transports=None, run_hooks=True,
2208
 
             _override_hook_target=None, local=False):
 
2212
             _override_hook_target=None, local=False,
 
2213
             tag_selector=None):
2209
2214
        """Pull from source into self, updating my master if any.
2210
2215
 
2211
2216
        :param run_hooks: Private parameter - if false, this branch
2212
2217
            is being called because it's the master of the primary branch,
2213
2218
            so it should not run its hooks.
2214
2219
        """
2215
 
        with self.target.lock_write():
 
2220
        with contextlib.ExitStack() as exit_stack:
 
2221
            exit_stack.enter_context(self.target.lock_write())
2216
2222
            bound_location = self.target.get_bound_location()
2217
2223
            if local and not bound_location:
2218
2224
                raise errors.LocalRequiresBoundBranch()
2229
2235
                    source_is_master = False
2230
2236
            if not local and bound_location and not source_is_master:
2231
2237
                # not pulling from master, so we need to update master.
2232
 
                master_branch = self.target.get_master_branch(possible_transports)
2233
 
                master_branch.lock_write()
2234
 
            try:
2235
 
                if master_branch:
2236
 
                    # pull from source into master.
2237
 
                    master_branch.pull(self.source, overwrite, stop_revision,
2238
 
                        run_hooks=False)
2239
 
                return self._pull(overwrite,
2240
 
                    stop_revision, _hook_master=master_branch,
2241
 
                    run_hooks=run_hooks,
2242
 
                    _override_hook_target=_override_hook_target,
2243
 
                    merge_tags_to_master=not source_is_master)
2244
 
            finally:
2245
 
                if master_branch:
2246
 
                    master_branch.unlock()
 
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)
2247
2252
 
2248
2253
    def push(self, overwrite=False, stop_revision=None, lossy=False,
2249
 
             _override_hook_source_branch=None):
 
2254
             _override_hook_source_branch=None, tag_selector=None):
2250
2255
        """See InterBranch.push.
2251
2256
 
2252
2257
        This is the basic concrete implementation of push()
2261
2266
        # TODO: Public option to disable running hooks - should be trivial but
2262
2267
        # needs tests.
2263
2268
 
2264
 
        op = cleanup.OperationWithCleanups(self._push_with_bound_branches)
2265
 
        op.add_cleanup(self.source.lock_read().unlock)
2266
 
        op.add_cleanup(self.target.lock_write().unlock)
2267
 
        return op.run(overwrite, stop_revision,
2268
 
            _override_hook_source_branch=_override_hook_source_branch)
2269
 
 
2270
 
    def _basic_push(self, overwrite, stop_revision):
 
2269
        def _run_hooks():
 
2270
            if _override_hook_source_branch:
 
2271
                result.source_branch = _override_hook_source_branch
 
2272
            for hook in Branch.hooks['post_push']:
 
2273
                hook(result)
 
2274
 
 
2275
        with self.source.lock_read(), self.target.lock_write():
 
2276
            bound_location = self.target.get_bound_location()
 
2277
            if bound_location and self.target.base != bound_location:
 
2278
                # there is a master branch.
 
2279
                #
 
2280
                # XXX: Why the second check?  Is it even supported for a branch
 
2281
                # to be bound to itself? -- mbp 20070507
 
2282
                master_branch = self.target.get_master_branch()
 
2283
                with master_branch.lock_write():
 
2284
                    # push into the master from the source branch.
 
2285
                    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)
 
2293
                    result.master_branch = master_branch
 
2294
                    result.local_branch = self.target
 
2295
                    _run_hooks()
 
2296
            else:
 
2297
                master_branch = None
 
2298
                # no master branch
 
2299
                result = self._basic_push(
 
2300
                    overwrite, stop_revision, tag_selector=tag_selector)
 
2301
                # TODO: Why set master_branch and local_branch if there's no
 
2302
                # binding?  Maybe cleaner to just leave them unset? -- mbp
 
2303
                # 20070504
 
2304
                result.master_branch = self.target
 
2305
                result.local_branch = None
 
2306
                _run_hooks()
 
2307
            return result
 
2308
 
 
2309
    def _basic_push(self, overwrite, stop_revision, tag_selector=None):
2271
2310
        """Basic implementation of push without bound branches or hooks.
2272
2311
 
2273
2312
        Must be called with source read locked and target write locked.
2276
2315
        result.source_branch = self.source
2277
2316
        result.target_branch = self.target
2278
2317
        result.old_revno, result.old_revid = self.target.last_revision_info()
2279
 
        self.source.update_references(self.target)
2280
2318
        overwrite = _fix_overwrite_type(overwrite)
2281
2319
        if result.old_revid != stop_revision:
2282
2320
            # We assume that during 'push' this repository is closer than
2283
2321
            # the target.
2284
2322
            graph = self.source.repository.get_graph(self.target.repository)
2285
 
            self._update_revisions(stop_revision,
2286
 
                overwrite=("history" in overwrite),
2287
 
                graph=graph)
 
2323
            self._update_revisions(
 
2324
                stop_revision, overwrite=("history" in overwrite), graph=graph)
2288
2325
        if self.source._push_should_merge_tags():
2289
2326
            result.tag_updates, result.tag_conflicts = (
2290
2327
                self.source.tags.merge_to(
2291
 
                self.target.tags, "tags" in overwrite))
 
2328
                    self.target.tags, "tags" in overwrite, selector=tag_selector))
 
2329
        self.update_references()
2292
2330
        result.new_revno, result.new_revid = self.target.last_revision_info()
2293
2331
        return result
2294
2332
 
2295
 
    def _push_with_bound_branches(self, operation, overwrite, stop_revision,
2296
 
            _override_hook_source_branch=None):
2297
 
        """Push from source into target, and into target's master if any.
2298
 
        """
2299
 
        def _run_hooks():
2300
 
            if _override_hook_source_branch:
2301
 
                result.source_branch = _override_hook_source_branch
2302
 
            for hook in Branch.hooks['post_push']:
2303
 
                hook(result)
2304
 
 
2305
 
        bound_location = self.target.get_bound_location()
2306
 
        if bound_location and self.target.base != bound_location:
2307
 
            # there is a master branch.
2308
 
            #
2309
 
            # XXX: Why the second check?  Is it even supported for a branch to
2310
 
            # be bound to itself? -- mbp 20070507
2311
 
            master_branch = self.target.get_master_branch()
2312
 
            master_branch.lock_write()
2313
 
            operation.add_cleanup(master_branch.unlock)
2314
 
            # push into the master from the source branch.
2315
 
            master_inter = InterBranch.get(self.source, master_branch)
2316
 
            master_inter._basic_push(overwrite, stop_revision)
2317
 
            # and push into the target branch from the source. Note that
2318
 
            # we push from the source branch again, because it's considered
2319
 
            # the highest bandwidth repository.
2320
 
            result = self._basic_push(overwrite, stop_revision)
2321
 
            result.master_branch = master_branch
2322
 
            result.local_branch = self.target
2323
 
        else:
2324
 
            master_branch = None
2325
 
            # no master branch
2326
 
            result = self._basic_push(overwrite, stop_revision)
2327
 
            # TODO: Why set master_branch and local_branch if there's no
2328
 
            # binding?  Maybe cleaner to just leave them unset? -- mbp
2329
 
            # 20070504
2330
 
            result.master_branch = self.target
2331
 
            result.local_branch = None
2332
 
        _run_hooks()
2333
 
        return result
2334
 
 
2335
2333
    def _pull(self, overwrite=False, stop_revision=None,
2336
 
             possible_transports=None, _hook_master=None, run_hooks=True,
2337
 
             _override_hook_target=None, local=False,
2338
 
             merge_tags_to_master=True):
 
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):
2339
2337
        """See Branch.pull.
2340
2338
 
2341
2339
        This function is the core worker, used by GenericInterBranch.pull to
2364
2362
        with self.source.lock_read():
2365
2363
            # We assume that during 'pull' the target repository is closer than
2366
2364
            # the source one.
2367
 
            self.source.update_references(self.target)
2368
2365
            graph = self.target.repository.get_graph(self.source.repository)
2369
 
            # TODO: Branch formats should have a flag that indicates 
 
2366
            # TODO: Branch formats should have a flag that indicates
2370
2367
            # that revno's are expensive, and pull() should honor that flag.
2371
2368
            # -- JRV20090506
2372
2369
            result.old_revno, result.old_revid = \
2373
2370
                self.target.last_revision_info()
2374
2371
            overwrite = _fix_overwrite_type(overwrite)
2375
 
            self._update_revisions(stop_revision,
2376
 
                overwrite=("history" in overwrite),
2377
 
                graph=graph)
2378
 
            # TODO: The old revid should be specified when merging tags, 
2379
 
            # so a tags implementation that versions tags can only 
 
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
2380
2376
            # pull in the most recent changes. -- JRV20090506
2381
2377
            result.tag_updates, result.tag_conflicts = (
2382
 
                self.source.tags.merge_to(self.target.tags,
2383
 
                    "tags" in overwrite,
2384
 
                    ignore_master=not merge_tags_to_master))
2385
 
            result.new_revno, result.new_revid = self.target.last_revision_info()
 
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())
2386
2385
            if _hook_master:
2387
2386
                result.master_branch = _hook_master
2388
2387
                result.local_branch = result.target_branch
2394
2393
                    hook(result)
2395
2394
            return result
2396
2395
 
 
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
 
2397
2416
 
2398
2417
InterBranch.register_optimiser(GenericInterBranch)