/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/git/branch.py

  • Committer: Jelmer Vernooij
  • Date: 2019-03-05 07:32:38 UTC
  • mto: (7290.1.21 work)
  • mto: This revision was merged to the branch mainline in revision 7311.
  • Revision ID: jelmer@jelmer.uk-20190305073238-zlqn981opwnqsmzi
Add appveyor configuration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
"""An adapter between a Git Branch and a Bazaar Branch"""
19
19
 
 
20
from __future__ import absolute_import
20
21
 
21
 
import contextlib
22
22
from io import BytesIO
23
23
from collections import defaultdict
24
24
 
25
 
from dulwich.config import (
26
 
    ConfigFile as GitConfigFile,
27
 
    parse_submodules,
28
 
    )
29
 
 
30
25
from dulwich.objects import (
31
26
    NotCommitError,
32
27
    ZERO_SHA,
41
36
    lock,
42
37
    repository as _mod_repository,
43
38
    revision,
 
39
    tag,
44
40
    trace,
45
41
    transport,
46
42
    urlutils,
49
45
from ..revision import (
50
46
    NULL_REVISION,
51
47
    )
52
 
from ..tag import (
53
 
    Tags,
54
 
    InterTags,
 
48
from ..sixish import (
 
49
    text_type,
 
50
    viewitems,
55
51
    )
56
52
from ..trace import (
57
53
    is_quiet,
59
55
    warning,
60
56
    )
61
57
 
 
58
from .config import (
 
59
    GitBranchConfig,
 
60
    GitBranchStack,
 
61
    )
62
62
from .errors import (
63
63
    NoPushSupport,
64
64
    )
65
 
from .mapping import (
66
 
    encode_git_path,
67
 
    decode_git_path,
68
 
    )
69
65
from .push import (
70
66
    remote_divergence,
71
67
    )
80
76
from .unpeel_map import (
81
77
    UnpeelMap,
82
78
    )
83
 
from .urls import (
84
 
    git_url_to_bzr_url,
85
 
    bzr_url_to_git_url,
86
 
    )
87
 
 
88
 
 
89
 
def _calculate_revnos(branch):
90
 
    if branch._format.stores_revno():
91
 
        return True
92
 
    config = branch.get_config_stack()
93
 
    return config.get('calculate_revnos')
 
79
from .urls import git_url_to_bzr_url
94
80
 
95
81
 
96
82
class GitPullResult(branch.PullResult):
99
85
    def _lookup_revno(self, revid):
100
86
        if not isinstance(revid, bytes):
101
87
            raise TypeError(revid)
102
 
        if not _calculate_revnos(self.target_branch):
103
 
            return None
104
88
        # Try in source branch first, it'll be faster
105
89
        with self.target_branch.lock_read():
106
90
            return self.target_branch.revision_id_to_revno(revid)
114
98
        return self._lookup_revno(self.new_revid)
115
99
 
116
100
 
117
 
class InterTagsFromGitToRemoteGit(InterTags):
118
 
 
119
 
    @classmethod
120
 
    def is_compatible(klass, source, target):
121
 
        if not isinstance(source, GitTags):
122
 
            return False
123
 
        if not isinstance(target, GitTags):
124
 
            return False
125
 
        if getattr(target.branch.repository, "_git", None) is not None:
126
 
            return False
127
 
        return True
128
 
 
129
 
    def merge(self, overwrite=False, ignore_master=False, selector=None):
130
 
        if self.source.branch.repository.has_same_location(self.target.branch.repository):
131
 
            return {}, []
 
101
class GitTags(tag.BasicTags):
 
102
    """Ref-based tag dictionary."""
 
103
 
 
104
    def __init__(self, branch):
 
105
        self.branch = branch
 
106
        self.repository = branch.repository
 
107
 
 
108
    def _merge_to_remote_git(self, target_repo, source_tag_refs,
 
109
                             overwrite=False):
132
110
        updates = {}
133
111
        conflicts = []
134
 
        source_tag_refs = self.source.branch.get_tag_refs()
135
 
        ref_to_tag_map = {}
136
112
 
137
113
        def get_changed_refs(old_refs):
138
114
            ret = dict(old_refs)
139
115
            for ref_name, tag_name, peeled, unpeeled in (
140
116
                    source_tag_refs.iteritems()):
141
 
                if selector and not selector(tag_name):
142
 
                    continue
143
117
                if old_refs.get(ref_name) == unpeeled:
144
118
                    pass
145
119
                elif overwrite or ref_name not in old_refs:
146
120
                    ret[ref_name] = unpeeled
147
 
                    updates[tag_name] = self.target.branch.repository.lookup_foreign_revision_id(
 
121
                    updates[tag_name] = target_repo.lookup_foreign_revision_id(
148
122
                        peeled)
149
 
                    ref_to_tag_map[ref_name] = tag_name
150
 
                    self.target.branch._tag_refs = None
151
123
                else:
152
124
                    conflicts.append(
153
125
                        (tag_name,
154
126
                         self.repository.lookup_foreign_revision_id(peeled),
155
 
                         self.target.branch.repository.lookup_foreign_revision_id(
 
127
                         target_repo.lookup_foreign_revision_id(
156
128
                             old_refs[ref_name])))
157
129
            return ret
158
 
        result = self.target.branch.repository.controldir.send_pack(
 
130
        target_repo.controldir.send_pack(
159
131
            get_changed_refs, lambda have, want: [])
160
 
        if result is not None and not isinstance(result, dict):
161
 
            for ref, error in result.ref_status.items():
162
 
                if error:
163
 
                    warning('unable to update ref %s: %s',
164
 
                            ref, error)
165
 
                    del updates[ref_to_tag_map[ref]]
166
 
        return updates, set(conflicts)
167
 
 
168
 
 
169
 
class InterTagsFromGitToLocalGit(InterTags):
170
 
 
171
 
    @classmethod
172
 
    def is_compatible(klass, source, target):
173
 
        if not isinstance(source, GitTags):
174
 
            return False
175
 
        if not isinstance(target, GitTags):
176
 
            return False
177
 
        if getattr(target.branch.repository, "_git", None) is None:
178
 
            return False
179
 
        return True
180
 
 
181
 
    def merge(self, overwrite=False, ignore_master=False, selector=None):
182
 
        if self.source.branch.repository.has_same_location(self.target.branch.repository):
183
 
            return {}, []
184
 
 
 
132
        return updates, conflicts
 
133
 
 
134
    def _merge_to_local_git(self, target_repo, source_tag_refs,
 
135
                            overwrite=False):
185
136
        conflicts = []
186
137
        updates = {}
187
 
        source_tag_refs = self.source.branch.get_tag_refs()
188
 
 
189
 
        target_repo = self.target.branch.repository
190
 
 
191
138
        for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
192
 
            if selector and not selector(tag_name):
193
 
                continue
194
139
            if target_repo._git.refs.get(ref_name) == unpeeled:
195
140
                pass
196
141
            elif overwrite or ref_name not in target_repo._git.refs:
201
146
                    trace.warning('%s does not point to a valid object',
202
147
                                  tag_name)
203
148
                    continue
204
 
                except NotCommitError:
205
 
                    trace.warning('%s points to a non-commit object',
206
 
                                  tag_name)
207
 
                    continue
208
149
                target_repo._git.refs[ref_name] = unpeeled or peeled
209
 
                self.target.branch._tag_refs = None
210
150
            else:
211
151
                try:
212
 
                    source_revid = self.source.branch.repository.lookup_foreign_revision_id(
 
152
                    source_revid = self.repository.lookup_foreign_revision_id(
213
153
                        peeled)
214
154
                    target_revid = target_repo.lookup_foreign_revision_id(
215
155
                        target_repo._git.refs[ref_name])
217
157
                    trace.warning('%s does not point to a valid object',
218
158
                                  ref_name)
219
159
                    continue
220
 
                except NotCommitError:
221
 
                    trace.warning('%s points to a non-commit object',
222
 
                                  tag_name)
223
 
                    continue
224
160
                conflicts.append((tag_name, source_revid, target_revid))
225
 
        return updates, set(conflicts)
226
 
 
227
 
 
228
 
class InterTagsFromGitToNonGit(InterTags):
229
 
 
230
 
    @classmethod
231
 
    def is_compatible(klass, source, target):
232
 
        if not isinstance(source, GitTags):
233
 
            return False
234
 
        if isinstance(target, GitTags):
235
 
            return False
236
 
        return True
237
 
 
238
 
    def merge(self, overwrite=False, ignore_master=False, selector=None):
239
 
        """See Tags.merge_to."""
240
 
        source_tag_refs = self.source.branch.get_tag_refs()
241
 
        if ignore_master:
242
 
            master = None
243
 
        else:
244
 
            master = self.target.branch.get_master_branch()
245
 
        with contextlib.ExitStack() as es:
246
 
            if master is not None:
247
 
                es.enter_context(master.lock_write())
248
 
            updates, conflicts = self._merge_to(
249
 
                self.target, source_tag_refs, overwrite=overwrite,
250
 
                selector=selector)
251
 
            if master is not None:
252
 
                extra_updates, extra_conflicts = self._merge_to(
253
 
                    master.tags, overwrite=overwrite,
254
 
                    source_tag_refs=source_tag_refs,
255
 
                    ignore_master=ignore_master, selector=selector)
256
 
                updates.update(extra_updates)
257
 
                conflicts.update(extra_conflicts)
258
 
            return updates, conflicts
259
 
 
260
 
    def _merge_to(self, to_tags, source_tag_refs, overwrite=False,
261
 
                  selector=None):
 
161
        return updates, conflicts
 
162
 
 
163
    def _merge_to_git(self, to_tags, source_tag_refs, overwrite=False):
 
164
        target_repo = to_tags.repository
 
165
        if self.repository.has_same_location(target_repo):
 
166
            return {}, []
 
167
        try:
 
168
            if getattr(target_repo, "_git", None):
 
169
                return self._merge_to_local_git(
 
170
                    target_repo, source_tag_refs, overwrite)
 
171
            else:
 
172
                return self._merge_to_remote_git(
 
173
                    target_repo, source_tag_refs, overwrite)
 
174
        finally:
 
175
            to_tags.branch._tag_refs = None
 
176
 
 
177
    def _merge_to_non_git(self, to_tags, source_tag_refs, overwrite=False):
262
178
        unpeeled_map = defaultdict(set)
263
179
        conflicts = []
264
180
        updates = {}
265
181
        result = dict(to_tags.get_tag_dict())
266
182
        for ref_name, tag_name, peeled, unpeeled in source_tag_refs:
267
 
            if selector and not selector(tag_name):
268
 
                continue
269
183
            if unpeeled is not None:
270
184
                unpeeled_map[peeled].add(unpeeled)
271
185
            try:
272
 
                bzr_revid = self.source.branch.lookup_foreign_revision_id(peeled)
 
186
                bzr_revid = self.branch.lookup_foreign_revision_id(peeled)
273
187
            except NotCommitError:
274
188
                continue
275
189
            if result.get(tag_name) == bzr_revid:
284
198
            map_file = UnpeelMap.from_repository(to_tags.branch.repository)
285
199
            map_file.update(unpeeled_map)
286
200
            map_file.save_in_repository(to_tags.branch.repository)
287
 
        return updates, set(conflicts)
288
 
 
289
 
 
290
 
InterTags.register_optimiser(InterTagsFromGitToRemoteGit)
291
 
InterTags.register_optimiser(InterTagsFromGitToLocalGit)
292
 
InterTags.register_optimiser(InterTagsFromGitToNonGit)
293
 
 
294
 
 
295
 
class GitTags(Tags):
296
 
    """Ref-based tag dictionary."""
297
 
 
298
 
    def __init__(self, branch):
299
 
        self.branch = branch
300
 
        self.repository = branch.repository
 
201
        return updates, conflicts
 
202
 
 
203
    def merge_to(self, to_tags, overwrite=False, ignore_master=False,
 
204
                 source_tag_refs=None):
 
205
        """See Tags.merge_to."""
 
206
        if source_tag_refs is None:
 
207
            source_tag_refs = self.branch.get_tag_refs()
 
208
        if self == to_tags:
 
209
            return {}, []
 
210
        if isinstance(to_tags, GitTags):
 
211
            return self._merge_to_git(to_tags, source_tag_refs,
 
212
                                      overwrite=overwrite)
 
213
        else:
 
214
            if ignore_master:
 
215
                master = None
 
216
            else:
 
217
                master = to_tags.branch.get_master_branch()
 
218
            if master is not None:
 
219
                master.lock_write()
 
220
            try:
 
221
                updates, conflicts = self._merge_to_non_git(
 
222
                    to_tags, source_tag_refs, overwrite=overwrite)
 
223
                if master is not None:
 
224
                    extra_updates, extra_conflicts = self.merge_to(
 
225
                        master.tags, overwrite=overwrite,
 
226
                        source_tag_refs=source_tag_refs,
 
227
                        ignore_master=ignore_master)
 
228
                    updates.update(extra_updates)
 
229
                    conflicts += extra_conflicts
 
230
                return updates, conflicts
 
231
            finally:
 
232
                if master is not None:
 
233
                    master.unlock()
301
234
 
302
235
    def get_tag_dict(self):
303
236
        ret = {}
311
244
                ret[tag_name] = bzr_revid
312
245
        return ret
313
246
 
314
 
    def lookup_tag(self, tag_name):
315
 
        """Return the referent string of a tag"""
316
 
        # TODO(jelmer): Replace with something more efficient for local tags.
317
 
        td = self.get_tag_dict()
318
 
        try:
319
 
            return td[tag_name]
320
 
        except KeyError:
321
 
            raise errors.NoSuchTag(tag_name)
322
 
 
323
247
 
324
248
class LocalGitTagDict(GitTags):
325
249
    """Dictionary with tags in a local repository."""
330
254
 
331
255
    def _set_tag_dict(self, to_dict):
332
256
        extra = set(self.refs.allkeys())
333
 
        for k, revid in to_dict.items():
 
257
        for k, revid in viewitems(to_dict):
334
258
            name = tag_name_to_ref(k)
335
259
            if name in extra:
336
260
                extra.remove(name)
337
 
            try:
338
 
                self.set_tag(k, revid)
339
 
            except errors.GhostTagsNotSupported:
340
 
                pass
 
261
            self.set_tag(k, revid)
341
262
        for name in extra:
342
263
            if is_tag(name):
343
264
                del self.repository._git[name]
400
321
    def set_reference(self, controldir, name, target):
401
322
        return controldir.set_branch_reference(target, name)
402
323
 
403
 
    def stores_revno(self):
404
 
        """True if this branch format store revision numbers."""
405
 
        return False
406
 
 
407
 
    supports_reference_locations = False
408
 
 
409
324
 
410
325
class LocalGitBranchFormat(GitBranchFormat):
411
326
 
482
397
        return "git"
483
398
 
484
399
    def get_config(self):
485
 
        from .config import GitBranchConfig
486
400
        return GitBranchConfig(self)
487
401
 
488
402
    def get_config_stack(self):
489
 
        from .config import GitBranchStack
490
403
        return GitBranchStack(self)
491
404
 
492
405
    def _get_nick(self, local=False, possible_master_transports=None):
498
411
            cs = self.repository._git.get_config_stack()
499
412
            try:
500
413
                return cs.get((b"branch", self.name.encode('utf-8')),
501
 
                        b"nick").decode("utf-8")
 
414
                              b"nick").decode("utf-8")
502
415
            except KeyError:
503
416
                pass
504
417
        return self.name or u"HEAD"
551
464
        # Git doesn't do stacking (yet...)
552
465
        raise branch.UnstackableBranchFormat(self._format, self.base)
553
466
 
554
 
    def _get_push_origin(self, cs):
555
 
        """Get the name for the push origin.
556
 
 
557
 
        The exact behaviour is documented in the git-config(1) manpage.
558
 
        """
559
 
        try:
560
 
            return cs.get((b'branch', self.name.encode('utf-8')), b'pushRemote')
561
 
        except KeyError:
562
 
            try:
563
 
                return cs.get((b'branch', ), b'remote')
564
 
            except KeyError:
565
 
                try:
566
 
                    return cs.get((b'branch', self.name.encode('utf-8')), b'remote')
567
 
                except KeyError:
568
 
                    return b'origin'
569
 
 
570
 
    def _get_origin(self, cs):
571
 
        try:
572
 
            return cs.get((b'branch', self.name.encode('utf-8')), b'remote')
573
 
        except KeyError:
574
 
            return b'origin'
575
 
 
576
 
    def _get_related_push_branch(self, cs):
577
 
        remote = self._get_push_origin(cs)
578
 
        try:
579
 
            location = cs.get((b"remote", remote), b"url")
580
 
        except KeyError:
581
 
            return None
582
 
 
583
 
        return git_url_to_bzr_url(location.decode('utf-8'), ref=self.ref)
584
 
 
585
 
    def _get_related_merge_branch(self, cs):
586
 
        remote = self._get_origin(cs)
587
 
        try:
588
 
            location = cs.get((b"remote", remote), b"url")
589
 
        except KeyError:
590
 
            return None
591
 
 
592
 
        try:
593
 
            ref = cs.get((b"branch", remote), b"merge")
594
 
        except KeyError:
595
 
            ref = self.ref
596
 
 
597
 
        return git_url_to_bzr_url(location.decode('utf-8'), ref=ref)
598
 
 
599
467
    def _get_parent_location(self):
600
468
        """See Branch.get_parent()."""
 
469
        # FIXME: Set "origin" url from .git/config ?
601
470
        cs = self.repository._git.get_config_stack()
602
 
        return self._get_related_merge_branch(cs)
603
 
 
604
 
    def _write_git_config(self, cs):
605
 
        f = BytesIO()
606
 
        cs.write_to_file(f)
607
 
        self.repository._git._put_named_file('config', f.getvalue())
 
471
        try:
 
472
            location = cs.get((b"remote", b'origin'), b"url")
 
473
        except KeyError:
 
474
            return None
 
475
 
 
476
        params = {}
 
477
        try:
 
478
            ref = cs.get((b"remote", b"origin"), b"merge")
 
479
        except KeyError:
 
480
            pass
 
481
        else:
 
482
            if ref != b'HEAD':
 
483
                try:
 
484
                    params['branch'] = urlutils.escape(ref_to_branch_name(ref))
 
485
                except ValueError:
 
486
                    params['ref'] = urlutils.quote_from_bytes(ref)
 
487
 
 
488
        url = git_url_to_bzr_url(location.decode('utf-8'))
 
489
        return urlutils.join_segment_parameters(url, params)
608
490
 
609
491
    def set_parent(self, location):
 
492
        # FIXME: Set "origin" url in .git/config ?
610
493
        cs = self.repository._git.get_config()
611
 
        remote = self._get_origin(cs)
612
 
        this_url = urlutils.strip_segment_parameters(self.user_url)
613
 
        target_url, branch, ref = bzr_url_to_git_url(location)
 
494
        this_url = urlutils.split_segment_parameters(self.user_url)[0]
 
495
        target_url, target_params = urlutils.split_segment_parameters(location)
614
496
        location = urlutils.relative_url(this_url, target_url)
615
 
        cs.set((b"remote", remote), b"url", location)
616
 
        if branch:
617
 
            cs.set((b"branch", remote), b"merge", branch_name_to_ref(branch))
618
 
        elif ref:
619
 
            cs.set((b"branch", remote), b"merge", ref)
 
497
        cs.set((b"remote", b"origin"), b"url", location)
 
498
        if 'branch' in target_params:
 
499
            cs.set((b"remote", b"origin"), b"merge",
 
500
                   branch_name_to_ref(target_params['branch']))
 
501
        elif 'ref' in target_params:
 
502
            cs.set((b"remote", b"origin"), b"merge",
 
503
                   target_params['ref'])
620
504
        else:
621
505
            # TODO(jelmer): Maybe unset rather than setting to HEAD?
622
 
            cs.set((b"branch", remote), b"merge", b'HEAD')
623
 
        self._write_git_config(cs)
 
506
            cs.set((b"remote", b"origin"), b"merge", 'HEAD')
 
507
        f = BytesIO()
 
508
        cs.write_to_file(f)
 
509
        self.repository._git._put_named_file('config', f.getvalue())
624
510
 
625
511
    def break_lock(self):
626
512
        raise NotImplementedError(self.break_lock)
672
558
                return revision.NULL_REVISION
673
559
            return self.lookup_foreign_revision_id(self.head)
674
560
 
675
 
    def _basic_push(self, target, overwrite=False, stop_revision=None,
676
 
                    tag_selector=None):
 
561
    def _basic_push(self, target, overwrite=False, stop_revision=None):
677
562
        return branch.InterBranch.get(self, target)._basic_push(
678
 
            overwrite, stop_revision, tag_selector=tag_selector)
 
563
            overwrite, stop_revision)
679
564
 
680
565
    def lookup_foreign_revision_id(self, foreign_revid):
681
566
        try:
771
656
    def break_lock(self):
772
657
        self.repository._git.refs.unlock_ref(self.ref)
773
658
 
 
659
    def fetch(self, from_branch, last_revision=None, limit=None):
 
660
        return branch.InterBranch.get(from_branch, self).fetch(
 
661
            stop_revision=last_revision, limit=limit)
 
662
 
774
663
    def _gen_revision_history(self):
775
664
        if self.head is None:
776
665
            return []
831
720
    def get_push_location(self):
832
721
        """See Branch.get_push_location."""
833
722
        push_loc = self.get_config_stack().get('push_location')
834
 
        if push_loc is not None:
835
 
            return push_loc
836
 
        cs = self.repository._git.get_config_stack()
837
 
        return self._get_related_push_branch(cs)
 
723
        return push_loc
838
724
 
839
725
    def set_push_location(self, location):
840
726
        """See Branch.set_push_location."""
853
739
        :param refs: Refs dictionary (name -> git sha1)
854
740
        :return: iterator over (ref_name, tag_name, peeled_sha1, unpeeled_sha1)
855
741
        """
856
 
        refs = self.repository.controldir.get_refs_container()
857
 
        for ref_name, unpeeled in refs.as_dict().items():
 
742
        refs = self.repository._git.refs
 
743
        for ref_name, unpeeled in viewitems(refs.as_dict()):
858
744
            try:
859
745
                tag_name = ref_to_tag_name(ref_name)
860
746
            except (ValueError, UnicodeDecodeError):
862
748
            peeled = refs.get_peeled(ref_name)
863
749
            if peeled is None:
864
750
                peeled = unpeeled
865
 
            if not isinstance(tag_name, str):
 
751
            if not isinstance(tag_name, text_type):
866
752
                raise TypeError(tag_name)
867
753
            yield (ref_name, tag_name, peeled, unpeeled)
868
754
 
871
757
        return GitMemoryTree(self, self.repository._git.object_store,
872
758
                             self.head)
873
759
 
 
760
    def reference_parent(self, path, file_id=None, possible_transports=None):
 
761
        """Return the parent branch for a tree-reference file_id
 
762
 
 
763
        :param path: The path of the file_id in the tree
 
764
        :param file_id: Optional file_id of the tree reference
 
765
        :return: A branch associated with the file_id
 
766
        """
 
767
        # FIXME should provide multiple branches, based on config
 
768
        url = urlutils.join(self.user_url, path)
 
769
        return branch.Branch.open(
 
770
            url,
 
771
            possible_transports=possible_transports)
 
772
 
874
773
 
875
774
def _quick_lookup_revno(local_branch, remote_branch, revid):
876
775
    if not isinstance(revid, bytes):
877
776
        raise TypeError(revid)
878
777
    # Try in source branch first, it'll be faster
879
778
    with local_branch.lock_read():
880
 
        if not _calculate_revnos(local_branch):
881
 
            return None
882
779
        try:
883
780
            return local_branch.revision_id_to_revno(revid)
884
781
        except errors.NoSuchRevision:
887
784
                return graph.find_distance_to_null(
888
785
                    revid, [(revision.NULL_REVISION, 0)])
889
786
            except errors.GhostRevisionsHaveNoRevno:
890
 
                if not _calculate_revnos(remote_branch):
891
 
                    return None
892
787
                # FIXME: Check using graph.find_distance_to_null() ?
893
788
                with remote_branch.lock_read():
894
789
                    return remote_branch.revision_id_to_revno(revid)
990
885
            return False
991
886
        return True
992
887
 
993
 
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None, lossy=False):
994
 
        self.fetch_objects(
995
 
            stop_revision, fetch_tags=fetch_tags, limit=limit, lossy=lossy)
996
 
        return _mod_repository.FetchResult()
 
888
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
 
889
        self.fetch_objects(stop_revision, fetch_tags=fetch_tags, limit=limit)
997
890
 
998
 
    def fetch_objects(self, stop_revision, fetch_tags, limit=None, lossy=False, tag_selector=None):
 
891
    def fetch_objects(self, stop_revision, fetch_tags, limit=None):
999
892
        interrepo = self._get_interrepo(self.source, self.target)
1000
893
        if fetch_tags is None:
1001
894
            c = self.source.get_config_stack()
1013
906
            else:
1014
907
                self._last_revid = stop_revision
1015
908
            real = interrepo.get_determine_wants_revids(
1016
 
                [self._last_revid], include_tags=fetch_tags, tag_selector=tag_selector)
 
909
                [self._last_revid], include_tags=fetch_tags)
1017
910
            return real(heads)
1018
911
        pack_hint, head, refs = interrepo.fetch_objects(
1019
 
            determine_wants, self.source.mapping, limit=limit,
1020
 
            lossy=lossy)
 
912
            determine_wants, self.source.mapping, limit=limit)
1021
913
        if (pack_hint is not None and
1022
914
                self.target.repository._format.pack_compresses):
1023
915
            self.target.repository.pack(hint=pack_hint)
1024
916
        return head, refs
1025
917
 
1026
 
    def _update_revisions(self, stop_revision=None, overwrite=False, tag_selector=None):
1027
 
        head, refs = self.fetch_objects(stop_revision, fetch_tags=None, tag_selector=tag_selector)
 
918
    def _update_revisions(self, stop_revision=None, overwrite=False):
 
919
        head, refs = self.fetch_objects(stop_revision, fetch_tags=None)
1028
920
        if overwrite:
1029
921
            prev_last_revid = None
1030
922
        else:
1034
926
            other_branch=self.source)
1035
927
        return head, refs
1036
928
 
1037
 
    def update_references(self, revid=None):
1038
 
        if revid is None:
1039
 
            revid = self.target.last_revision()
1040
 
        tree = self.target.repository.revision_tree(revid)
1041
 
        try:
1042
 
            with tree.get_file('.gitmodules') as f:
1043
 
                for path, url, section in parse_submodules(
1044
 
                        GitConfigFile.from_file(f)):
1045
 
                    self.target.set_reference_info(
1046
 
                        tree.path2id(decode_git_path(path)), url.decode('utf-8'),
1047
 
                        decode_git_path(path))
1048
 
        except errors.NoSuchFile:
1049
 
            pass
1050
 
 
1051
929
    def _basic_pull(self, stop_revision, overwrite, run_hooks,
1052
 
                    _override_hook_target, _hook_master, tag_selector=None):
 
930
                    _override_hook_target, _hook_master):
1053
931
        if overwrite is True:
1054
932
            overwrite = set(["history", "tags"])
1055
933
        elif not overwrite:
1066
944
            (result.old_revno, result.old_revid) = \
1067
945
                self.target.last_revision_info()
1068
946
            result.new_git_head, remote_refs = self._update_revisions(
1069
 
                stop_revision, overwrite=("history" in overwrite),
1070
 
                tag_selector=tag_selector)
 
947
                stop_revision, overwrite=("history" in overwrite))
1071
948
            tags_ret = self.source.tags.merge_to(
1072
949
                self.target.tags, ("tags" in overwrite), ignore_master=True)
1073
950
            if isinstance(tags_ret, tuple):
1076
953
                result.tag_conflicts = tags_ret
1077
954
            (result.new_revno, result.new_revid) = \
1078
955
                self.target.last_revision_info()
1079
 
            self.update_references(revid=result.new_revid)
1080
956
            if _hook_master:
1081
957
                result.master_branch = _hook_master
1082
958
                result.local_branch = result.target_branch
1090
966
 
1091
967
    def pull(self, overwrite=False, stop_revision=None,
1092
968
             possible_transports=None, _hook_master=None, run_hooks=True,
1093
 
             _override_hook_target=None, local=False, tag_selector=None):
 
969
             _override_hook_target=None, local=False):
1094
970
        """See Branch.pull.
1095
971
 
1096
972
        :param _hook_master: Private parameter - set the branch to
1105
981
        bound_location = self.target.get_bound_location()
1106
982
        if local and not bound_location:
1107
983
            raise errors.LocalRequiresBoundBranch()
 
984
        master_branch = None
1108
985
        source_is_master = False
1109
 
        with contextlib.ExitStack() as es:
1110
 
            es.enter_context(self.source.lock_read())
1111
 
            if bound_location:
1112
 
                # bound_location comes from a config file, some care has to be
1113
 
                # taken to relate it to source.user_url
1114
 
                normalized = urlutils.normalize_url(bound_location)
1115
 
                try:
1116
 
                    relpath = self.source.user_transport.relpath(normalized)
1117
 
                    source_is_master = (relpath == '')
1118
 
                except (errors.PathNotChild, urlutils.InvalidURL):
1119
 
                    source_is_master = False
1120
 
            if not local and bound_location and not source_is_master:
1121
 
                # not pulling from master, so we need to update master.
1122
 
                master_branch = self.target.get_master_branch(possible_transports)
1123
 
                es.enter_context(master_branch.lock_write())
1124
 
                # pull from source into master.
1125
 
                master_branch.pull(self.source, overwrite, stop_revision,
1126
 
                                   run_hooks=False)
1127
 
            else:
1128
 
                master_branch = None
1129
 
            return self._basic_pull(stop_revision, overwrite, run_hooks,
1130
 
                                    _override_hook_target,
1131
 
                                    _hook_master=master_branch,
1132
 
                                    tag_selector=tag_selector)
 
986
        self.source.lock_read()
 
987
        if bound_location:
 
988
            # bound_location comes from a config file, some care has to be
 
989
            # taken to relate it to source.user_url
 
990
            normalized = urlutils.normalize_url(bound_location)
 
991
            try:
 
992
                relpath = self.source.user_transport.relpath(normalized)
 
993
                source_is_master = (relpath == '')
 
994
            except (errors.PathNotChild, urlutils.InvalidURL):
 
995
                source_is_master = False
 
996
        if not local and bound_location and not source_is_master:
 
997
            # not pulling from master, so we need to update master.
 
998
            master_branch = self.target.get_master_branch(possible_transports)
 
999
            master_branch.lock_write()
 
1000
        try:
 
1001
            try:
 
1002
                if master_branch:
 
1003
                    # pull from source into master.
 
1004
                    master_branch.pull(self.source, overwrite, stop_revision,
 
1005
                                       run_hooks=False)
 
1006
                result = self._basic_pull(stop_revision, overwrite, run_hooks,
 
1007
                                          _override_hook_target,
 
1008
                                          _hook_master=master_branch)
 
1009
            finally:
 
1010
                self.source.unlock()
 
1011
        finally:
 
1012
            if master_branch:
 
1013
                master_branch.unlock()
 
1014
        return result
1133
1015
 
1134
 
    def _basic_push(self, overwrite, stop_revision, tag_selector=None):
 
1016
    def _basic_push(self, overwrite, stop_revision):
1135
1017
        if overwrite is True:
1136
1018
            overwrite = set(["history", "tags"])
1137
1019
        elif not overwrite:
1141
1023
        result.target_branch = self.target
1142
1024
        result.old_revno, result.old_revid = self.target.last_revision_info()
1143
1025
        result.new_git_head, remote_refs = self._update_revisions(
1144
 
            stop_revision, overwrite=("history" in overwrite),
1145
 
            tag_selector=tag_selector)
 
1026
            stop_revision, overwrite=("history" in overwrite))
1146
1027
        tags_ret = self.source.tags.merge_to(
1147
 
            self.target.tags, "tags" in overwrite, ignore_master=True,
1148
 
            selector=tag_selector)
 
1028
            self.target.tags, "tags" in overwrite, ignore_master=True)
1149
1029
        (result.tag_updates, result.tag_conflicts) = tags_ret
1150
1030
        result.new_revno, result.new_revid = self.target.last_revision_info()
1151
 
        self.update_references(revid=result.new_revid)
1152
1031
        return result
1153
1032
 
1154
1033
 
1155
1034
class InterGitBranch(branch.GenericInterBranch):
1156
1035
    """InterBranch implementation that pulls between Git branches."""
1157
1036
 
1158
 
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None, lossy=False):
 
1037
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
1159
1038
        raise NotImplementedError(self.fetch)
1160
1039
 
1161
1040
 
1174
1053
        return (isinstance(source, LocalGitBranch) and
1175
1054
                isinstance(target, RemoteGitBranch))
1176
1055
 
1177
 
    def _basic_push(self, overwrite, stop_revision, tag_selector=None):
1178
 
        from .remote import parse_git_error
 
1056
    def _basic_push(self, overwrite, stop_revision):
1179
1057
        result = GitBranchPushResult()
1180
1058
        result.source_branch = self.source
1181
1059
        result.target_branch = self.target
1198
1076
                    raise errors.DivergedBranches(self.source, self.target)
1199
1077
            refs = {self.target.ref: new_ref}
1200
1078
            result.new_revid = stop_revision
1201
 
            for name, sha in (
1202
 
                    self.source.repository._git.refs.as_dict(b"refs/tags").items()):
1203
 
                if tag_selector and not tag_selector(name):
1204
 
                    continue
1205
 
                if sha not in self.source.repository._git:
1206
 
                    trace.mutter('Ignoring missing SHA: %s', sha)
1207
 
                    continue
 
1079
            for name, sha in viewitems(
 
1080
                    self.source.repository._git.refs.as_dict(b"refs/tags")):
1208
1081
                refs[tag_name_to_ref(name)] = sha
1209
1082
            return refs
1210
 
        dw_result = self.target.repository.send_pack(
 
1083
        self.target.repository.send_pack(
1211
1084
            get_changed_refs,
1212
 
            self.source.repository._git.generate_pack_data)
1213
 
        if dw_result is not None and not isinstance(dw_result, dict):
1214
 
            error = dw_result.ref_status.get(self.target.ref)
1215
 
            if error:
1216
 
                raise parse_git_error(self.target.user_url, error)
1217
 
            for ref, error in dw_result.ref_status.items():
1218
 
                if error:
1219
 
                    trace.warning('unable to open ref %s: %s', ref, error)
 
1085
            self.source.repository._git.object_store.generate_pack_data)
1220
1086
        return result
1221
1087
 
1222
1088
 
1235
1101
        return (isinstance(source, GitBranch) and
1236
1102
                isinstance(target, LocalGitBranch))
1237
1103
 
1238
 
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None, lossy=False):
1239
 
        interrepo = _mod_repository.InterRepository.get(
1240
 
            self.source.repository, self.target.repository)
 
1104
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
 
1105
        interrepo = _mod_repository.InterRepository.get(self.source.repository,
 
1106
                                                        self.target.repository)
1241
1107
        if stop_revision is None:
1242
1108
            stop_revision = self.source.last_revision()
1243
 
        if fetch_tags is None:
1244
 
            c = self.source.get_config_stack()
1245
 
            fetch_tags = c.get('branch.fetch_tags')
1246
1109
        determine_wants = interrepo.get_determine_wants_revids(
1247
1110
            [stop_revision], include_tags=fetch_tags)
1248
 
        interrepo.fetch_objects(determine_wants, limit=limit, lossy=lossy)
1249
 
        return _mod_repository.FetchResult()
 
1111
        interrepo.fetch_objects(determine_wants, limit=limit)
1250
1112
 
1251
 
    def _basic_push(self, overwrite=False, stop_revision=None, tag_selector=None):
 
1113
    def _basic_push(self, overwrite=False, stop_revision=None):
1252
1114
        if overwrite is True:
1253
1115
            overwrite = set(["history", "tags"])
1254
1116
        elif not overwrite:
1264
1126
            other_branch=self.source)
1265
1127
        tags_ret = self.source.tags.merge_to(
1266
1128
            self.target.tags,
1267
 
            overwrite=("tags" in overwrite),
1268
 
            selector=tag_selector)
 
1129
            source_tag_refs=remote_refs_dict_to_tag_refs(refs),
 
1130
            overwrite=("tags" in overwrite))
1269
1131
        if isinstance(tags_ret, tuple):
1270
1132
            (result.tag_updates, result.tag_conflicts) = tags_ret
1271
1133
        else:
1280
1142
        fetch_tags = c.get('branch.fetch_tags')
1281
1143
 
1282
1144
        if stop_revision is None:
1283
 
            result = interrepo.fetch(branches=[self.source.ref], include_tags=fetch_tags)
 
1145
            refs = interrepo.fetch(branches=[self.source.ref], include_tags=fetch_tags)
1284
1146
            try:
1285
 
                head = result.refs[self.source.ref]
 
1147
                head = refs[self.source.ref]
1286
1148
            except KeyError:
1287
1149
                stop_revision = revision.NULL_REVISION
1288
1150
            else:
1289
1151
                stop_revision = self.target.lookup_foreign_revision_id(head)
1290
1152
        else:
1291
 
            result = interrepo.fetch(
 
1153
            refs = interrepo.fetch(
1292
1154
                revision_id=stop_revision, include_tags=fetch_tags)
1293
 
        return result.refs, stop_revision
 
1155
        return refs, stop_revision
1294
1156
 
1295
1157
    def pull(self, stop_revision=None, overwrite=False,
1296
 
             possible_transports=None, run_hooks=True, local=False,
1297
 
             tag_selector=None):
 
1158
             possible_transports=None, run_hooks=True, local=False):
1298
1159
        # This type of branch can't be bound.
1299
1160
        if local:
1300
1161
            raise errors.LocalRequiresBoundBranch()
1315
1176
                other_branch=self.source)
1316
1177
            tags_ret = self.source.tags.merge_to(
1317
1178
                self.target.tags, overwrite=("tags" in overwrite),
1318
 
                selector=tag_selector)
 
1179
                source_tag_refs=remote_refs_dict_to_tag_refs(refs))
1319
1180
            if isinstance(tags_ret, tuple):
1320
1181
                (result.tag_updates, result.tag_conflicts) = tags_ret
1321
1182
            else:
1360
1221
        if stop_revision is None:
1361
1222
            (stop_revno, stop_revision) = self.source.last_revision_info()
1362
1223
        elif stop_revno is None:
1363
 
            try:
1364
 
                stop_revno = self.source.revision_id_to_revno(stop_revision)
1365
 
            except errors.NoSuchRevision:
1366
 
                stop_revno = None
 
1224
            stop_revno = self.source.revision_id_to_revno(stop_revision)
1367
1225
        if not isinstance(stop_revision, bytes):
1368
1226
            raise TypeError(stop_revision)
1369
1227
        main_ref = self.target.ref
1371
1229
        if fetch_tags is None:
1372
1230
            c = self.source.get_config_stack()
1373
1231
            fetch_tags = c.get('branch.fetch_tags')
1374
 
        for name, revid in self.source.tags.get_tag_dict().items():
 
1232
        for name, revid in viewitems(self.source.tags.get_tag_dict()):
1375
1233
            if self.source.repository.has_revision(revid):
1376
1234
                ref = tag_name_to_ref(name)
1377
1235
                if not check_ref_format(ref):
1383
1241
                    refs[ref] = (None, revid)
1384
1242
        return refs, main_ref, (stop_revno, stop_revision)
1385
1243
 
1386
 
    def _update_refs(self, result, old_refs, new_refs, overwrite, tag_selector):
 
1244
    def _update_refs(self, result, old_refs, new_refs, overwrite):
1387
1245
        mutter("updating refs. old refs: %r, new refs: %r",
1388
1246
               old_refs, new_refs)
1389
1247
        result.tag_updates = {}
1390
1248
        result.tag_conflicts = []
1391
 
        ret = {}
 
1249
        ret = dict(old_refs)
1392
1250
 
1393
1251
        def ref_equals(refs, ref, git_sha, revid):
1394
1252
            try:
1408
1266
            # updated that hasn't actually been updated.
1409
1267
            return False
1410
1268
        # FIXME: Check for diverged branches
1411
 
        for ref, (git_sha, revid) in new_refs.items():
 
1269
        for ref, (git_sha, revid) in viewitems(new_refs):
1412
1270
            if ref_equals(ret, ref, git_sha, revid):
1413
1271
                # Already up to date
1414
1272
                if git_sha is None:
1422
1280
                except ValueError:
1423
1281
                    pass
1424
1282
                else:
1425
 
                    if tag_selector and not tag_selector(tag_name):
1426
 
                        continue
1427
1283
                    result.tag_updates[tag_name] = revid
1428
1284
                ret[ref] = (git_sha, revid)
1429
1285
            else:
1447
1303
            stop_revision = self.source.last_revision()
1448
1304
        ret = []
1449
1305
        if fetch_tags:
1450
 
            for k, v in self.source.tags.get_tag_dict().items():
 
1306
            for k, v in viewitems(self.source.tags.get_tag_dict()):
1451
1307
                ret.append((None, v))
1452
1308
        ret.append((None, stop_revision))
1453
1309
        try:
1454
 
            revidmap = self.interrepo.fetch_objects(ret, lossy=lossy, limit=limit)
 
1310
            self.interrepo.fetch_objects(ret, lossy=lossy, limit=limit)
1455
1311
        except NoPushSupport:
1456
1312
            raise errors.NoRoundtrippingSupport(self.source, self.target)
1457
 
        return _mod_repository.FetchResult(revidmap={
1458
 
            old_revid: new_revid
1459
 
            for (old_revid, (new_sha, new_revid)) in revidmap.items()})
1460
1313
 
1461
1314
    def pull(self, overwrite=False, stop_revision=None, local=False,
1462
 
             possible_transports=None, run_hooks=True, _stop_revno=None,
1463
 
             tag_selector=None):
 
1315
             possible_transports=None, run_hooks=True, _stop_revno=None):
1464
1316
        result = GitBranchPullResult()
1465
1317
        result.source_branch = self.source
1466
1318
        result.target_branch = self.target
1469
1321
                stop_revision, stop_revno=_stop_revno)
1470
1322
 
1471
1323
            def update_refs(old_refs):
1472
 
                return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector)
 
1324
                return self._update_refs(result, old_refs, new_refs, overwrite)
1473
1325
            try:
1474
1326
                result.revidmap, old_refs, new_refs = (
1475
1327
                    self.interrepo.fetch_refs(update_refs, lossy=False))
1489
1341
        return result
1490
1342
 
1491
1343
    def push(self, overwrite=False, stop_revision=None, lossy=False,
1492
 
             _override_hook_source_branch=None, _stop_revno=None,
1493
 
             tag_selector=None):
 
1344
             _override_hook_source_branch=None, _stop_revno=None):
1494
1345
        result = GitBranchPushResult()
1495
1346
        result.source_branch = self.source
1496
1347
        result.target_branch = self.target
1501
1352
                stop_revision, stop_revno=_stop_revno)
1502
1353
 
1503
1354
            def update_refs(old_refs):
1504
 
                return self._update_refs(result, old_refs, new_refs, overwrite, tag_selector)
 
1355
                return self._update_refs(result, old_refs, new_refs, overwrite)
1505
1356
            try:
1506
1357
                result.revidmap, old_refs, new_refs = (
1507
1358
                    self.interrepo.fetch_refs(
1510
1361
                raise errors.NoRoundtrippingSupport(self.source, self.target)
1511
1362
            (old_sha1, result.old_revid) = old_refs.get(
1512
1363
                main_ref, (ZERO_SHA, NULL_REVISION))
1513
 
            if lossy or result.old_revid is None:
 
1364
            if result.old_revid is None:
1514
1365
                result.old_revid = self.target.lookup_foreign_revision_id(
1515
1366
                    old_sha1)
1516
1367
            result.new_revid = new_refs[main_ref][1]