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

Add test for option command in git-remote-helper.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007 Canonical Ltd
2
 
# Copyright (C) 2009-2010 Jelmer Vernooij <jelmer@samba.org>
 
1
# Copyright (C) 2007,2011 Canonical Ltd
 
2
# Copyright (C) 2009-2011 Jelmer Vernooij <jelmer@samba.org>
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
17
17
 
18
18
"""An adapter between a Git Branch and a Bazaar Branch"""
19
19
 
 
20
from collections import defaultdict
 
21
 
20
22
from dulwich.objects import (
21
 
    Commit,
22
 
    Tag,
 
23
    ZERO_SHA,
23
24
    )
 
25
from dulwich.repo import check_ref_format
24
26
 
25
27
from bzrlib import (
26
28
    branch,
27
29
    bzrdir,
28
30
    config,
29
31
    errors,
30
 
    repository,
 
32
    repository as _mod_repository,
31
33
    revision,
32
34
    tag,
33
35
    transport,
 
36
    urlutils,
34
37
    )
35
38
from bzrlib.decorators import (
36
39
    needs_read_lock,
41
44
from bzrlib.trace import (
42
45
    is_quiet,
43
46
    mutter,
 
47
    warning,
44
48
    )
45
49
 
46
50
from bzrlib.plugins.git.config import (
47
51
    GitBranchConfig,
 
52
    GitBranchStack,
48
53
    )
49
54
from bzrlib.plugins.git.errors import (
50
55
    NoPushSupport,
51
56
    NoSuchRef,
52
57
    )
53
58
from bzrlib.plugins.git.refs import (
 
59
    is_tag,
54
60
    ref_to_branch_name,
55
 
    extract_tags,
 
61
    ref_to_tag_name,
56
62
    tag_name_to_ref,
57
63
    )
 
64
from bzrlib.plugins.git.unpeel_map import (
 
65
    UnpeelMap,
 
66
    )
58
67
 
59
68
from bzrlib.foreign import ForeignBranch
60
69
 
65
74
    def _lookup_revno(self, revid):
66
75
        assert isinstance(revid, str), "was %r" % revid
67
76
        # Try in source branch first, it'll be faster
68
 
        return self.target_branch.revision_id_to_revno(revid)
 
77
        self.target_branch.lock_read()
 
78
        try:
 
79
            return self.target_branch.revision_id_to_revno(revid)
 
80
        finally:
 
81
            self.target_branch.unlock()
69
82
 
70
83
    @property
71
84
    def old_revno(self):
76
89
        return self._lookup_revno(self.new_revid)
77
90
 
78
91
 
79
 
class LocalGitTagDict(tag.BasicTags):
80
 
    """Dictionary with tags in a local repository."""
 
92
class GitTags(tag.BasicTags):
 
93
    """Ref-based tag dictionary."""
81
94
 
82
95
    def __init__(self, branch):
83
96
        self.branch = branch
84
97
        self.repository = branch.repository
85
98
 
 
99
    def get_refs_container(self):
 
100
        raise NotImplementedError(self.get_refs_container)
 
101
 
 
102
    def _iter_tag_refs(self, refs):
 
103
        """Iterate over the tag refs.
 
104
 
 
105
        :param refs: Refs dictionary (name -> git sha1)
 
106
        :return: iterator over (name, peeled_sha1, unpeeled_sha1, bzr_revid)
 
107
        """
 
108
        for k, unpeeled in refs.as_dict().iteritems():
 
109
            try:
 
110
                tag_name = ref_to_tag_name(k)
 
111
            except (ValueError, UnicodeDecodeError):
 
112
                continue
 
113
            peeled = refs.get_peeled(k)
 
114
            if peeled is None:
 
115
                peeled = self.repository.bzrdir._git.object_store.peel_sha(unpeeled).id
 
116
            yield (tag_name, peeled, unpeeled,
 
117
                   self.branch.lookup_foreign_revision_id(peeled))
 
118
 
 
119
    def _merge_to_remote_git(self, target_repo, new_refs, overwrite=False):
 
120
        updates = {}
 
121
        conflicts = []
 
122
        def get_changed_refs(old_refs):
 
123
            ret = dict(old_refs)
 
124
            for k, v in new_refs.iteritems():
 
125
                if not is_tag(k):
 
126
                    continue
 
127
                name = ref_to_tag_name(k)
 
128
                if old_refs.get(k) == v:
 
129
                    pass
 
130
                elif overwrite or not k in old_refs:
 
131
                    ret[k] = v
 
132
                    updates[name] = target_repo.lookup_foreign_revision_id(v)
 
133
                else:
 
134
                    conflicts.append((name, v, old_refs[k]))
 
135
            return ret
 
136
        target_repo.bzrdir.send_pack(get_changed_refs, lambda have, want: [])
 
137
        return updates, conflicts
 
138
 
 
139
    def _merge_to_local_git(self, target_repo, refs, overwrite=False):
 
140
        conflicts = []
 
141
        updates = {}
 
142
        for k, unpeeled in refs.as_dict().iteritems():
 
143
            if not is_tag(k):
 
144
                continue
 
145
            name = ref_to_tag_name(k)
 
146
            peeled = self.repository.bzrdir.get_peeled(k)
 
147
            if target_repo._git.refs.get(k) == unpeeled:
 
148
                pass
 
149
            elif overwrite or not k in target_repo._git.refs:
 
150
                target_repo._git.refs[k] = unpeeled or peeled
 
151
                updates[name] = target_repo.lookup_foreign_revision_id(peeled)
 
152
            else:
 
153
                conflicts.append((name, peeled, target_repo._git.refs[k]))
 
154
        return updates, conflicts
 
155
 
 
156
    def _merge_to_git(self, to_tags, refs, overwrite=False):
 
157
        target_repo = to_tags.repository
 
158
        if self.repository.has_same_location(target_repo):
 
159
            return {}, []
 
160
        if getattr(target_repo, "_git", None):
 
161
            return self._merge_to_local_git(target_repo, refs, overwrite)
 
162
        else:
 
163
            return self._merge_to_remote_git(target_repo, refs, overwrite)
 
164
 
 
165
    def _merge_to_non_git(self, to_tags, refs, overwrite=False):
 
166
        unpeeled_map = defaultdict(set)
 
167
        conflicts = []
 
168
        updates = {}
 
169
        result = dict(to_tags.get_tag_dict())
 
170
        for n, peeled, unpeeled, bzr_revid in self._iter_tag_refs(refs):
 
171
            if unpeeled is not None:
 
172
                unpeeled_map[peeled].add(unpeeled)
 
173
            if result.get(n) == bzr_revid:
 
174
                pass
 
175
            elif n not in result or overwrite:
 
176
                result[n] = bzr_revid
 
177
                updates[n] = bzr_revid
 
178
            else:
 
179
                conflicts.append((n, result[n], bzr_revid))
 
180
        to_tags._set_tag_dict(result)
 
181
        if len(unpeeled_map) > 0:
 
182
            map_file = UnpeelMap.from_repository(to_tags.branch.repository)
 
183
            map_file.update(unpeeled_map)
 
184
            map_file.save_in_repository(to_tags.branch.repository)
 
185
        return updates, conflicts
 
186
 
 
187
    def merge_to(self, to_tags, overwrite=False, ignore_master=False,
 
188
                 source_refs=None):
 
189
        """See Tags.merge_to."""
 
190
        if source_refs is None:
 
191
            source_refs = self.get_refs_container()
 
192
        if self == to_tags:
 
193
            return {}, []
 
194
        if isinstance(to_tags, GitTags):
 
195
            return self._merge_to_git(to_tags, source_refs,
 
196
                                      overwrite=overwrite)
 
197
        else:
 
198
            if ignore_master:
 
199
                master = None
 
200
            else:
 
201
                master = to_tags.branch.get_master_branch()
 
202
            updates, conflicts = self._merge_to_non_git(to_tags, source_refs,
 
203
                                              overwrite=overwrite)
 
204
            if master is not None:
 
205
                extra_updates, extra_conflicts = self.merge_to(
 
206
                    master.tags, overwrite=overwrite,
 
207
                                           source_refs=source_refs,
 
208
                                           ignore_master=ignore_master)
 
209
                updates.update(extra_updates)
 
210
                conflicts += extra_conflicts
 
211
            return updates, conflicts
 
212
 
86
213
    def get_tag_dict(self):
87
214
        ret = {}
88
 
        for k,v in extract_tags(self.repository._git.get_refs()).iteritems():
89
 
            try:
90
 
                obj = self.repository._git[v]
91
 
            except KeyError:
92
 
                mutter("Tag %s points at unknown object %s, ignoring", v, obj)
93
 
                continue
94
 
            while isinstance(obj, Tag):
95
 
                v = obj.object[1]
96
 
                obj = self.repository._git[v]
97
 
            if not isinstance(obj, Commit):
98
 
                mutter("Tag %s points at object %r that is not a commit, "
99
 
                       "ignoring", k, obj)
100
 
                continue
101
 
            ret[k] = self.branch.lookup_foreign_revision_id(v)
 
215
        refs = self.get_refs_container()
 
216
        for (name, peeled, unpeeled, bzr_revid) in self._iter_tag_refs(refs):
 
217
            ret[name] = bzr_revid
102
218
        return ret
103
219
 
 
220
 
 
221
class LocalGitTagDict(GitTags):
 
222
    """Dictionary with tags in a local repository."""
 
223
 
 
224
    def __init__(self, branch):
 
225
        super(LocalGitTagDict, self).__init__(branch)
 
226
        self.refs = self.repository.bzrdir._git.refs
 
227
 
 
228
    def get_refs_container(self):
 
229
        return self.refs
 
230
 
104
231
    def _set_tag_dict(self, to_dict):
105
 
        extra = set(self.repository._git.get_refs().keys())
 
232
        extra = set(self.refs.allkeys())
106
233
        for k, revid in to_dict.iteritems():
107
234
            name = tag_name_to_ref(k)
108
235
            if name in extra:
109
236
                extra.remove(name)
110
237
            self.set_tag(k, revid)
111
238
        for name in extra:
112
 
            if name.startswith("refs/tags/"):
 
239
            if is_tag(name):
113
240
                del self.repository._git[name]
114
241
 
115
242
    def set_tag(self, name, revid):
116
 
        self.repository._git.refs[tag_name_to_ref(name)], _ = \
117
 
            self.branch.lookup_bzr_revision_id(revid)
118
 
 
119
 
 
120
 
class DictTagDict(LocalGitTagDict):
 
243
        try:
 
244
            git_sha, mapping = self.branch.lookup_bzr_revision_id(revid)
 
245
        except errors.NoSuchRevision:
 
246
            raise errors.GhostTagsNotSupported(self)
 
247
        self.refs[tag_name_to_ref(name)] = git_sha
 
248
 
 
249
 
 
250
class DictTagDict(tag.BasicTags):
121
251
 
122
252
    def __init__(self, branch, tags):
123
253
        super(DictTagDict, self).__init__(branch)
127
257
        return self._tags
128
258
 
129
259
 
 
260
class GitSymrefBranchFormat(branch.BranchFormat):
 
261
 
 
262
    def get_format_description(self):
 
263
        return 'Git Symbolic Reference Branch'
 
264
 
 
265
    def network_name(self):
 
266
        return "git"
 
267
 
 
268
    def get_reference(self, controldir, name=None):
 
269
        return controldir.get_branch_reference(name)
 
270
 
 
271
    def set_reference(self, controldir, name, target):
 
272
        return controldir.set_branch_reference(name, target)
 
273
 
 
274
 
130
275
class GitBranchFormat(branch.BranchFormat):
131
276
 
132
277
    def get_format_description(self):
138
283
    def supports_tags(self):
139
284
        return True
140
285
 
 
286
    def supports_leaving_lock(self):
 
287
        return False
 
288
 
 
289
    def supports_tags_referencing_ghosts(self):
 
290
        return False
 
291
 
 
292
    def tags_are_versioned(self):
 
293
        return False
 
294
 
 
295
    @property
 
296
    def _matchingbzrdir(self):
 
297
        from bzrlib.plugins.git.dir import LocalGitControlDirFormat
 
298
        return LocalGitControlDirFormat()
 
299
 
141
300
    def get_foreign_tests_branch_factory(self):
142
301
        from bzrlib.plugins.git.tests.test_branch import ForeignTestsBranchFactory
143
302
        return ForeignTestsBranchFactory()
144
303
 
145
304
    def make_tags(self, branch):
146
 
        if getattr(branch.repository, "get_refs", None) is not None:
 
305
        try:
 
306
            return branch.tags
 
307
        except AttributeError:
 
308
            pass
 
309
        if getattr(branch.repository, "_git", None) is None:
147
310
            from bzrlib.plugins.git.remote import RemoteGitTagDict
148
311
            return RemoteGitTagDict(branch)
149
312
        else:
150
313
            return LocalGitTagDict(branch)
151
314
 
 
315
    def initialize(self, a_bzrdir, name=None, repository=None,
 
316
                   append_revisions_only=None):
 
317
        from bzrlib.plugins.git.dir import LocalGitDir
 
318
        if not isinstance(a_bzrdir, LocalGitDir):
 
319
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
320
        return a_bzrdir.create_branch(repository=repository, name=name,
 
321
            append_revisions_only=append_revisions_only)
 
322
 
152
323
 
153
324
class GitReadLock(object):
154
325
 
159
330
class GitWriteLock(object):
160
331
 
161
332
    def __init__(self, unlock):
 
333
        self.branch_token = None
162
334
        self.unlock = unlock
163
335
 
164
336
 
165
337
class GitBranch(ForeignBranch):
166
338
    """An adapter to git repositories for bzr Branch objects."""
167
339
 
168
 
    def __init__(self, bzrdir, repository, ref, lockfiles, tagsdict=None):
 
340
    @property
 
341
    def control_transport(self):
 
342
        return self.bzrdir.control_transport
 
343
 
 
344
    def __init__(self, bzrdir, repository, ref):
 
345
        self.base = bzrdir.root_transport.base
169
346
        self.repository = repository
170
347
        self._format = GitBranchFormat()
171
 
        self.control_files = lockfiles
172
348
        self.bzrdir = bzrdir
 
349
        self._lock_mode = None
 
350
        self._lock_count = 0
173
351
        super(GitBranch, self).__init__(repository.get_mapping())
174
 
        if tagsdict is not None:
175
 
            self.tags = DictTagDict(self, tagsdict)
176
352
        self.ref = ref
177
 
        self.name = ref_to_branch_name(ref)
 
353
        try:
 
354
            self.name = ref_to_branch_name(ref)
 
355
        except ValueError:
 
356
            self.name = None
178
357
        self._head = None
179
 
        self.base = bzrdir.root_transport.base
180
358
 
181
 
    def _get_checkout_format(self):
 
359
    def _get_checkout_format(self, lightweight=False):
182
360
        """Return the most suitable metadir for a checkout of this branch.
183
361
        Weaves are used if this branch's repository uses weaves.
184
362
        """
191
369
            return ret
192
370
        return "git"
193
371
 
 
372
    def get_config(self):
 
373
        return GitBranchConfig(self)
 
374
 
 
375
    def get_config_stack(self):
 
376
        return GitBranchStack(self)
 
377
 
194
378
    def _get_nick(self, local=False, possible_master_transports=None):
195
379
        """Find the nick name for this branch.
196
380
 
205
389
 
206
390
    def __repr__(self):
207
391
        return "<%s(%r, %r)>" % (self.__class__.__name__, self.repository.base,
208
 
            self.ref or "HEAD")
 
392
            self.name)
209
393
 
210
394
    def generate_revision_history(self, revid, old_revid=None):
211
 
        # FIXME: Check that old_revid is in the ancestry of revid
212
 
        newhead, self.mapping = self.mapping.revision_id_bzr_to_foreign(revid)
 
395
        if revid == NULL_REVISION:
 
396
            newhead = ZERO_SHA
 
397
        else:
 
398
            # FIXME: Check that old_revid is in the ancestry of revid
 
399
            newhead, self.mapping = self.repository.lookup_bzr_revision_id(revid)
 
400
            if self.mapping is None:
 
401
                raise AssertionError
213
402
        self._set_head(newhead)
214
403
 
215
 
    def lock_write(self):
216
 
        self.control_files.lock_write()
 
404
    def lock_write(self, token=None):
 
405
        if token is not None:
 
406
            raise errors.TokenLockingNotSupported(self)
 
407
        if self._lock_mode:
 
408
            if self._lock_mode == 'r':
 
409
                raise errors.ReadOnlyError(self)
 
410
            self._lock_count += 1
 
411
        else:
 
412
            self._lock_mode = 'w'
 
413
            self._lock_count = 1
 
414
        self.repository.lock_write()
217
415
        return GitWriteLock(self.unlock)
218
416
 
 
417
    def leave_lock_in_place(self):
 
418
        raise NotImplementedError(self.leave_lock_in_place)
 
419
 
 
420
    def dont_leave_lock_in_place(self):
 
421
        raise NotImplementedError(self.dont_leave_lock_in_place)
 
422
 
219
423
    def get_stacked_on_url(self):
220
424
        # Git doesn't do stacking (yet...)
221
425
        raise errors.UnstackableBranchFormat(self._format, self.base)
229
433
        # FIXME: Set "origin" url in .git/config ?
230
434
        pass
231
435
 
 
436
    def break_lock(self):
 
437
        raise NotImplementedError(self.break_lock)
 
438
 
232
439
    def lock_read(self):
233
 
        self.control_files.lock_read()
 
440
        if self._lock_mode:
 
441
            assert self._lock_mode in ('r', 'w')
 
442
            self._lock_count += 1
 
443
        else:
 
444
            self._lock_mode = 'r'
 
445
            self._lock_count = 1
 
446
        self.repository.lock_read()
234
447
        return GitReadLock(self.unlock)
235
448
 
 
449
    def peek_lock_mode(self):
 
450
        return self._lock_mode
 
451
 
236
452
    def is_locked(self):
237
 
        return self.control_files.is_locked()
 
453
        return (self._lock_mode is not None)
238
454
 
239
455
    def unlock(self):
240
 
        self.control_files.unlock()
 
456
        """See Branch.unlock()."""
 
457
        self._lock_count -= 1
 
458
        if self._lock_count == 0:
 
459
            self._lock_mode = None
 
460
            self._clear_cached_state()
 
461
        self.repository.unlock()
241
462
 
242
463
    def get_physical_lock_status(self):
243
464
        return False
265
486
class LocalGitBranch(GitBranch):
266
487
    """A local Git branch."""
267
488
 
268
 
    def __init__(self, bzrdir, repository, name, lockfiles, tagsdict=None):
269
 
        super(LocalGitBranch, self).__init__(bzrdir, repository, name,
270
 
              lockfiles, tagsdict)
271
 
        refs = repository._git.get_refs()
272
 
        if not (name in refs.keys() or "HEAD" in refs.keys()):
 
489
    def __init__(self, bzrdir, repository, ref):
 
490
        super(LocalGitBranch, self).__init__(bzrdir, repository, ref)
 
491
        refs = bzrdir.get_refs_container()
 
492
        if not (ref in refs or "HEAD" in refs):
273
493
            raise errors.NotBranchError(self.base)
274
494
 
275
495
    def create_checkout(self, to_location, revision_id=None, lightweight=False,
277
497
        if lightweight:
278
498
            t = transport.get_transport(to_location)
279
499
            t.ensure_base()
280
 
            format = self._get_checkout_format()
 
500
            format = self._get_checkout_format(lightweight=True)
281
501
            checkout = format.initialize_on_transport(t)
282
502
            from_branch = branch.BranchReferenceFormat().initialize(checkout,
283
503
                self)
298
518
        :return: WorkingTree object of checkout.
299
519
        """
300
520
        checkout_branch = bzrdir.BzrDir.create_branch_convenience(
301
 
            to_location, force_new_tree=False)
 
521
            to_location, force_new_tree=False,
 
522
            format=self._get_checkout_format(lightweight=False))
302
523
        checkout = checkout_branch.bzrdir
303
524
        checkout_branch.bind(self)
304
525
        # pull up to the specified revision_id to set the initial
306
527
        checkout_branch.pull(self, stop_revision=revision_id)
307
528
        return checkout.create_workingtree(revision_id, hardlink=hardlink)
308
529
 
 
530
    def fetch(self, from_branch, last_revision=None, limit=None):
 
531
        return branch.InterBranch.get(from_branch, self).fetch(
 
532
            stop_revision=last_revision, limit=limit)
 
533
 
309
534
    def _gen_revision_history(self):
310
535
        if self.head is None:
311
536
            return []
312
 
        ret = list(self.repository.iter_reverse_revision_history(
313
 
            self.last_revision()))
 
537
        graph = self.repository.get_graph()
 
538
        ret = list(graph.iter_lefthand_ancestry(self.last_revision(),
 
539
            (revision.NULL_REVISION, )))
314
540
        ret.reverse()
315
541
        return ret
316
542
 
320
546
        except KeyError:
321
547
            return None
322
548
 
323
 
    def set_last_revision_info(self, revno, revid):
324
 
        self.set_last_revision(revid)
 
549
    def _read_last_revision_info(self):
 
550
        last_revid = self.last_revision()
 
551
        graph = self.repository.get_graph()
 
552
        revno = graph.find_distance_to_null(last_revid,
 
553
            [(revision.NULL_REVISION, 0)])
 
554
        return revno, last_revid
 
555
 
 
556
    def set_last_revision_info(self, revno, revision_id):
 
557
        self.set_last_revision(revision_id)
 
558
        self._last_revision_info_cache = revno, revision_id
325
559
 
326
560
    def set_last_revision(self, revid):
327
 
        (newhead, self.mapping) = self.repository.lookup_bzr_revision_id(revid)
328
 
        self.head = newhead
 
561
        if not revid or not isinstance(revid, basestring):
 
562
            raise errors.InvalidRevisionId(revision_id=revid, branch=self)
 
563
        if revid == NULL_REVISION:
 
564
            newhead = ZERO_SHA
 
565
        else:
 
566
            (newhead, self.mapping) = self.repository.lookup_bzr_revision_id(revid)
 
567
            if self.mapping is None:
 
568
                raise AssertionError
 
569
        self._set_head(newhead)
329
570
 
330
571
    def _set_head(self, value):
331
572
        self._head = value
334
575
 
335
576
    head = property(_get_head, _set_head)
336
577
 
337
 
    def get_config(self):
338
 
        return GitBranchConfig(self)
339
 
 
340
578
    def get_push_location(self):
341
579
        """See Branch.get_push_location."""
342
580
        push_loc = self.get_config().get_user_option('push_location')
351
589
        return True
352
590
 
353
591
 
 
592
def _quick_lookup_revno(local_branch, remote_branch, revid):
 
593
    assert isinstance(revid, str), "was %r" % revid
 
594
    # Try in source branch first, it'll be faster
 
595
    local_branch.lock_read()
 
596
    try:
 
597
        try:
 
598
            return local_branch.revision_id_to_revno(revid)
 
599
        except errors.NoSuchRevision:
 
600
            graph = local_branch.repository.get_graph()
 
601
            try:
 
602
                return graph.find_distance_to_null(revid,
 
603
                    [(revision.NULL_REVISION, 0)])
 
604
            except errors.GhostRevisionsHaveNoRevno:
 
605
                # FIXME: Check using graph.find_distance_to_null() ?
 
606
                remote_branch.lock_read()
 
607
                try:
 
608
                    return remote_branch.revision_id_to_revno(revid)
 
609
                finally:
 
610
                    remote_branch.unlock()
 
611
    finally:
 
612
        local_branch.unlock()
 
613
 
 
614
 
354
615
class GitBranchPullResult(branch.PullResult):
355
616
 
356
617
    def __init__(self):
371
632
        self._show_tag_conficts(to_file)
372
633
 
373
634
    def _lookup_revno(self, revid):
374
 
        assert isinstance(revid, str), "was %r" % revid
375
 
        # Try in source branch first, it'll be faster
376
 
        try:
377
 
            return self.source_branch.revision_id_to_revno(revid)
378
 
        except errors.NoSuchRevision:
379
 
            # FIXME: Check using graph.find_distance_to_null() ?
380
 
            return self.target_branch.revision_id_to_revno(revid)
 
635
        return _quick_lookup_revno(self.target_branch, self.source_branch,
 
636
            revid)
381
637
 
382
638
    def _get_old_revno(self):
383
639
        if self._old_revno is not None:
403
659
class GitBranchPushResult(branch.BranchPushResult):
404
660
 
405
661
    def _lookup_revno(self, revid):
406
 
        assert isinstance(revid, str), "was %r" % revid
407
 
        # Try in source branch first, it'll be faster
408
 
        try:
409
 
            return self.source_branch.revision_id_to_revno(revid)
410
 
        except errors.NoSuchRevision:
411
 
            # FIXME: Check using graph.find_distance_to_null() ?
412
 
            return self.target_branch.revision_id_to_revno(revid)
 
662
        return _quick_lookup_revno(self.source_branch, self.target_branch,
 
663
            revid)
413
664
 
414
665
    @property
415
666
    def old_revno(self):
417
668
 
418
669
    @property
419
670
    def new_revno(self):
 
671
        new_original_revno = getattr(self, "new_original_revno", None)
 
672
        if new_original_revno:
 
673
            return new_original_revno
 
674
        if getattr(self, "new_original_revid", None) is not None:
 
675
            return self._lookup_revno(self.new_original_revid)
420
676
        return self._lookup_revno(self.new_revid)
421
677
 
422
678
 
425
681
 
426
682
    @staticmethod
427
683
    def _get_branch_formats_to_test():
428
 
        return []
 
684
        try:
 
685
            default_format = branch.format_registry.get_default()
 
686
        except AttributeError:
 
687
            default_format = branch.BranchFormat._default_format
 
688
        return [
 
689
            (GitBranchFormat(), GitBranchFormat()),
 
690
            (GitBranchFormat(), default_format)]
429
691
 
430
692
    @classmethod
431
693
    def _get_interrepo(self, source, target):
432
 
        return repository.InterRepository.get(source.repository,
433
 
            target.repository)
 
694
        return _mod_repository.InterRepository.get(source.repository, target.repository)
434
695
 
435
696
    @classmethod
436
697
    def is_compatible(cls, source, target):
437
 
        return (isinstance(source, GitBranch) and
438
 
                not isinstance(target, GitBranch) and
439
 
                (getattr(cls._get_interrepo(source, target), "fetch_objects", None) is not None))
440
 
 
441
 
    def _update_revisions(self, stop_revision=None, overwrite=False,
442
 
        graph=None, limit=None):
443
 
        """Like InterBranch.update_revisions(), but with additions.
444
 
 
445
 
        Compared to the `update_revisions()` below, this function takes a
446
 
        `limit` argument that limits how many git commits will be converted
447
 
        and returns the new git head.
448
 
        """
 
698
        if not isinstance(source, GitBranch):
 
699
            return False
 
700
        if isinstance(target, GitBranch):
 
701
            # InterLocalGitRemoteGitBranch or InterToGitBranch should be used
 
702
            return False
 
703
        if getattr(cls._get_interrepo(source, target), "fetch_objects", None) is None:
 
704
            # fetch_objects is necessary for this to work
 
705
            return False
 
706
        return True
 
707
 
 
708
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
 
709
        self.fetch_objects(stop_revision, fetch_tags=fetch_tags, limit=limit)
 
710
 
 
711
    def fetch_objects(self, stop_revision, fetch_tags, limit=None):
449
712
        interrepo = self._get_interrepo(self.source, self.target)
 
713
        if fetch_tags is None:
 
714
            c = self.source.get_config()
 
715
            fetch_tags = c.get_user_option_as_bool('branch.fetch_tags')
450
716
        def determine_wants(heads):
451
717
            if self.source.ref is not None and not self.source.ref in heads:
452
 
                raise NoSuchRef(self.source.ref, heads.keys())
453
 
            if stop_revision is not None:
454
 
                self._last_revid = stop_revision
455
 
                head, mapping = self.source.repository.lookup_bzr_revision_id(
456
 
                    stop_revision)
457
 
            else:
 
718
                raise NoSuchRef(self.source.ref, self.source.user_url, heads.keys())
 
719
 
 
720
            if stop_revision is None:
458
721
                if self.source.ref is not None:
459
722
                    head = heads[self.source.ref]
460
723
                else:
461
724
                    head = heads["HEAD"]
462
725
                self._last_revid = self.source.lookup_foreign_revision_id(head)
463
 
            if self.target.repository.has_revision(self._last_revid):
464
 
                return []
465
 
            return [head]
 
726
            else:
 
727
                self._last_revid = stop_revision
 
728
            real = interrepo.get_determine_wants_revids(
 
729
                [self._last_revid], include_tags=fetch_tags)
 
730
            return real(heads)
466
731
        pack_hint, head, refs = interrepo.fetch_objects(
467
732
            determine_wants, self.source.mapping, limit=limit)
468
733
        if (pack_hint is not None and
469
734
            self.target.repository._format.pack_compresses):
470
735
            self.target.repository.pack(hint=pack_hint)
471
 
        if head is not None:
472
 
            self._last_revid = self.source.lookup_foreign_revision_id(head)
 
736
        return head, refs
 
737
 
 
738
    def _update_revisions(self, stop_revision=None, overwrite=False):
 
739
        head, refs = self.fetch_objects(stop_revision, fetch_tags=None)
473
740
        if overwrite:
474
741
            prev_last_revid = None
475
742
        else:
476
743
            prev_last_revid = self.target.last_revision()
477
744
        self.target.generate_revision_history(self._last_revid,
478
 
            prev_last_revid)
479
 
        return head
 
745
            prev_last_revid, self.source)
 
746
        return head, refs
480
747
 
481
 
    def update_revisions(self, stop_revision=None, overwrite=False,
482
 
                         graph=None):
483
 
        """See InterBranch.update_revisions()."""
484
 
        self._update_revisions(stop_revision, overwrite, graph)
 
748
    def _basic_pull(self, stop_revision, overwrite, run_hooks,
 
749
              _override_hook_target, _hook_master):
 
750
        result = GitBranchPullResult()
 
751
        result.source_branch = self.source
 
752
        if _override_hook_target is None:
 
753
            result.target_branch = self.target
 
754
        else:
 
755
            result.target_branch = _override_hook_target
 
756
        self.source.lock_read()
 
757
        try:
 
758
            self.target.lock_write()
 
759
            try:
 
760
                # We assume that during 'pull' the target repository is closer than
 
761
                # the source one.
 
762
                (result.old_revno, result.old_revid) = \
 
763
                    self.target.last_revision_info()
 
764
                result.new_git_head, remote_refs = self._update_revisions(
 
765
                    stop_revision, overwrite=overwrite)
 
766
                tags_ret  = self.source.tags.merge_to(
 
767
                    self.target.tags, overwrite)
 
768
                if isinstance(tags_ret, tuple):
 
769
                    result.tag_updates, result.tag_conflicts = tags_ret
 
770
                else:
 
771
                    result.tag_conflicts = tags_ret
 
772
                (result.new_revno, result.new_revid) = \
 
773
                    self.target.last_revision_info()
 
774
                if _hook_master:
 
775
                    result.master_branch = _hook_master
 
776
                    result.local_branch = result.target_branch
 
777
                else:
 
778
                    result.master_branch = result.target_branch
 
779
                    result.local_branch = None
 
780
                if run_hooks:
 
781
                    for hook in branch.Branch.hooks['post_pull']:
 
782
                        hook(result)
 
783
                return result
 
784
            finally:
 
785
                self.target.unlock()
 
786
        finally:
 
787
            self.source.unlock()
485
788
 
486
789
    def pull(self, overwrite=False, stop_revision=None,
487
790
             possible_transports=None, _hook_master=None, run_hooks=True,
488
 
             _override_hook_target=None, local=False, limit=None):
 
791
             _override_hook_target=None, local=False):
489
792
        """See Branch.pull.
490
793
 
491
794
        :param _hook_master: Private parameter - set the branch to
495
798
            so it should not run its hooks.
496
799
        :param _override_hook_target: Private parameter - set the branch to be
497
800
            supplied as the target_branch to pull hooks.
498
 
        :param limit: Only import this many revisons.  `None`, the default,
499
 
            means import all revisions.
500
801
        """
501
802
        # This type of branch can't be bound.
502
 
        if local:
 
803
        bound_location = self.target.get_bound_location()
 
804
        if local and not bound_location:
503
805
            raise errors.LocalRequiresBoundBranch()
504
 
        result = GitBranchPullResult()
505
 
        result.source_branch = self.source
506
 
        if _override_hook_target is None:
507
 
            result.target_branch = self.target
508
 
        else:
509
 
            result.target_branch = _override_hook_target
 
806
        master_branch = None
 
807
        source_is_master = False
510
808
        self.source.lock_read()
 
809
        if bound_location:
 
810
            # bound_location comes from a config file, some care has to be
 
811
            # taken to relate it to source.user_url
 
812
            normalized = urlutils.normalize_url(bound_location)
 
813
            try:
 
814
                relpath = self.source.user_transport.relpath(normalized)
 
815
                source_is_master = (relpath == '')
 
816
            except (errors.PathNotChild, errors.InvalidURL):
 
817
                source_is_master = False
 
818
        if not local and bound_location and not source_is_master:
 
819
            # not pulling from master, so we need to update master.
 
820
            master_branch = self.target.get_master_branch(possible_transports)
 
821
            master_branch.lock_write()
511
822
        try:
512
 
            # We assume that during 'pull' the target repository is closer than
513
 
            # the source one.
514
 
            graph = self.target.repository.get_graph(self.source.repository)
515
 
            (result.old_revno, result.old_revid) = \
516
 
                self.target.last_revision_info()
517
 
            result.new_git_head = self._update_revisions(
518
 
                stop_revision, overwrite=overwrite, graph=graph, limit=limit)
519
 
            result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
520
 
                overwrite)
521
 
            (result.new_revno, result.new_revid) = \
522
 
                self.target.last_revision_info()
523
 
            if _hook_master:
524
 
                result.master_branch = _hook_master
525
 
                result.local_branch = result.target_branch
526
 
            else:
527
 
                result.master_branch = result.target_branch
528
 
                result.local_branch = None
529
 
            if run_hooks:
530
 
                for hook in branch.Branch.hooks['post_pull']:
531
 
                    hook(result)
 
823
            try:
 
824
                if master_branch:
 
825
                    # pull from source into master.
 
826
                    master_branch.pull(self.source, overwrite, stop_revision,
 
827
                        run_hooks=False)
 
828
                result = self._basic_pull(stop_revision, overwrite, run_hooks,
 
829
                    _override_hook_target, _hook_master=master_branch)
 
830
            finally:
 
831
                self.source.unlock()
532
832
        finally:
533
 
            self.source.unlock()
 
833
            if master_branch:
 
834
                master_branch.unlock()
534
835
        return result
535
836
 
536
837
    def _basic_push(self, overwrite=False, stop_revision=None):
537
838
        result = branch.BranchPushResult()
538
839
        result.source_branch = self.source
539
840
        result.target_branch = self.target
540
 
        graph = self.target.repository.get_graph(self.source.repository)
541
841
        result.old_revno, result.old_revid = self.target.last_revision_info()
542
 
        result.new_git_head = self._update_revisions(
543
 
            stop_revision, overwrite=overwrite, graph=graph)
544
 
        result.tag_conflicts = self.source.tags.merge_to(self.target.tags,
 
842
        result.new_git_head, remote_refs = self._update_revisions(
 
843
            stop_revision, overwrite=overwrite)
 
844
        tags_ret = self.source.tags.merge_to(self.target.tags,
545
845
            overwrite)
 
846
        if isinstance(tags_ret, tuple):
 
847
            (result.tag_updates, result.tag_conflicts) = tags_ret
 
848
        else:
 
849
            result.tag_conflicts = tags_ret
546
850
        result.new_revno, result.new_revid = self.target.last_revision_info()
547
851
        return result
548
852
 
550
854
class InterGitBranch(branch.GenericInterBranch):
551
855
    """InterBranch implementation that pulls between Git branches."""
552
856
 
553
 
 
554
 
class InterGitLocalRemoteBranch(InterGitBranch):
 
857
    def fetch(self, stop_revision=None, fetch_tags=None, limit=None):
 
858
        raise NotImplementedError(self.fetch)
 
859
 
 
860
 
 
861
class InterLocalGitRemoteGitBranch(InterGitBranch):
555
862
    """InterBranch that copies from a local to a remote git branch."""
556
863
 
557
864
    @staticmethod
558
865
    def _get_branch_formats_to_test():
 
866
        # FIXME
559
867
        return []
560
868
 
561
869
    @classmethod
565
873
                isinstance(target, RemoteGitBranch))
566
874
 
567
875
    def _basic_push(self, overwrite=False, stop_revision=None):
568
 
        from dulwich.protocol import ZERO_SHA
569
876
        result = GitBranchPushResult()
570
877
        result.source_branch = self.source
571
878
        result.target_branch = self.target
573
880
            stop_revision = self.source.last_revision()
574
881
        # FIXME: Check for diverged branches
575
882
        def get_changed_refs(old_refs):
576
 
            result.old_revid = self.target.lookup_foreign_revision_id(old_refs.get(self.target.ref, ZERO_SHA))
 
883
            old_ref = old_refs.get(self.target.ref, ZERO_SHA)
 
884
            result.old_revid = self.target.lookup_foreign_revision_id(old_ref)
577
885
            refs = { self.target.ref: self.source.repository.lookup_bzr_revision_id(stop_revision)[0] }
578
886
            result.new_revid = stop_revision
579
887
            for name, sha in self.source.repository._git.refs.as_dict("refs/tags").iteritems():
584
892
        return result
585
893
 
586
894
 
587
 
class InterGitRemoteLocalBranch(InterGitBranch):
 
895
class InterGitLocalGitBranch(InterGitBranch):
588
896
    """InterBranch that copies from a remote to a local git branch."""
589
897
 
590
898
    @staticmethod
591
899
    def _get_branch_formats_to_test():
 
900
        # FIXME
592
901
        return []
593
902
 
594
903
    @classmethod
595
904
    def is_compatible(self, source, target):
596
 
        from bzrlib.plugins.git.remote import RemoteGitBranch
597
 
        return (isinstance(source, RemoteGitBranch) and
 
905
        return (isinstance(source, GitBranch) and
598
906
                isinstance(target, LocalGitBranch))
599
907
 
600
908
    def _basic_push(self, overwrite=False, stop_revision=None):
601
 
        result = branch.BranchPushResult()
 
909
        result = GitBranchPushResult()
602
910
        result.source_branch = self.source
603
911
        result.target_branch = self.target
604
912
        result.old_revid = self.target.last_revision()
605
913
        refs, stop_revision = self.update_refs(stop_revision)
606
914
        self.target.generate_revision_history(stop_revision, result.old_revid)
607
 
        self.update_tags(refs)
 
915
        tags_ret = self.source.tags.merge_to(self.target.tags,
 
916
            source_refs=refs, overwrite=overwrite)
 
917
        if isinstance(tags_ret, tuple):
 
918
            (result.tag_updates, result.tag_conflicts) = tags_ret
 
919
        else:
 
920
            result.tag_conflicts = tags_ret
608
921
        result.new_revid = self.target.last_revision()
609
922
        return result
610
923
 
611
 
    def update_tags(self, refs):
612
 
        for name, v in extract_tags(refs).iteritems():
613
 
            revid = self.target.lookup_foreign_revision_id(v)
614
 
            self.target.tags.set_tag(name, revid)
615
 
 
616
924
    def update_refs(self, stop_revision=None):
617
 
        interrepo = repository.InterRepository.get(self.source.repository,
 
925
        interrepo = _mod_repository.InterRepository.get(self.source.repository,
618
926
            self.target.repository)
619
927
        if stop_revision is None:
620
928
            refs = interrepo.fetch(branches=["HEAD"])
631
939
        result = GitPullResult()
632
940
        result.source_branch = self.source
633
941
        result.target_branch = self.target
634
 
        result.old_revid = self.target.last_revision()
635
 
        refs, stop_revision = self.update_refs(stop_revision)
636
 
        self.target.generate_revision_history(stop_revision, result.old_revid)
637
 
        self.update_tags(refs)
638
 
        result.new_revid = self.target.last_revision()
 
942
        self.source.lock_read()
 
943
        try:
 
944
            self.target.lock_write()
 
945
            try:
 
946
                result.old_revid = self.target.last_revision()
 
947
                refs, stop_revision = self.update_refs(stop_revision)
 
948
                self.target.generate_revision_history(stop_revision, result.old_revid)
 
949
                tags_ret = self.source.tags.merge_to(self.target.tags,
 
950
                    overwrite=overwrite, source_refs=refs)
 
951
                if isinstance(tags_ret, tuple):
 
952
                    (result.tag_updates, result.tag_conflicts) = tags_ret
 
953
                else:
 
954
                    result.tag_conflicts = tags_ret
 
955
                result.new_revid = self.target.last_revision()
 
956
                result.local_branch = None
 
957
                result.master_branch = result.target_branch
 
958
                if run_hooks:
 
959
                    for hook in branch.Branch.hooks['post_pull']:
 
960
                        hook(result)
 
961
            finally:
 
962
                self.target.unlock()
 
963
        finally:
 
964
            self.source.unlock()
639
965
        return result
640
966
 
641
967
 
642
968
class InterToGitBranch(branch.GenericInterBranch):
643
 
    """InterBranch implementation that pulls from Git into bzr."""
 
969
    """InterBranch implementation that pulls into a Git branch."""
644
970
 
645
971
    def __init__(self, source, target):
646
972
        super(InterToGitBranch, self).__init__(source, target)
647
 
        self.interrepo = repository.InterRepository.get(source.repository,
 
973
        self.interrepo = _mod_repository.InterRepository.get(source.repository,
648
974
                                           target.repository)
649
975
 
650
976
    @staticmethod
651
977
    def _get_branch_formats_to_test():
652
 
        return []
 
978
        try:
 
979
            default_format = branch.format_registry.get_default()
 
980
        except AttributeError:
 
981
            default_format = branch.BranchFormat._default_format
 
982
        return [(default_format, GitBranchFormat())]
653
983
 
654
984
    @classmethod
655
985
    def is_compatible(self, source, target):
656
986
        return (not isinstance(source, GitBranch) and
657
987
                isinstance(target, GitBranch))
658
988
 
659
 
    def update_revisions(self, *args, **kwargs):
660
 
        raise NoPushSupport()
661
 
 
662
 
    def _get_new_refs(self, stop_revision=None):
 
989
    def _get_new_refs(self, stop_revision=None, fetch_tags=None):
 
990
        assert self.source.is_locked()
663
991
        if stop_revision is None:
664
 
            stop_revision = self.source.last_revision()
 
992
            (stop_revno, stop_revision) = self.source.last_revision_info()
 
993
        else:
 
994
            stop_revno = self.source.revision_id_to_revno(stop_revision)
665
995
        assert type(stop_revision) is str
666
996
        main_ref = self.target.ref or "refs/heads/master"
667
997
        refs = { main_ref: (None, stop_revision) }
 
998
        if fetch_tags is None:
 
999
            c = self.source.get_config()
 
1000
            fetch_tags = c.get_user_option_as_bool('branch.fetch_tags')
668
1001
        for name, revid in self.source.tags.get_tag_dict().iteritems():
669
1002
            if self.source.repository.has_revision(revid):
670
 
                refs[tag_name_to_ref(name)] = (None, revid)
671
 
        return refs, main_ref
 
1003
                ref = tag_name_to_ref(name)
 
1004
                if not check_ref_format(ref):
 
1005
                    warning("skipping tag with invalid characters %s (%s)",
 
1006
                        name, ref)
 
1007
                    continue
 
1008
                if fetch_tags:
 
1009
                    # FIXME: Skip tags that are not in the ancestry
 
1010
                    refs[ref] = (None, revid)
 
1011
        return refs, main_ref, (stop_revno, stop_revision)
 
1012
 
 
1013
    def _update_refs(self, result, old_refs, new_refs, overwrite):
 
1014
        mutter("updating refs. old refs: %r, new refs: %r",
 
1015
               old_refs, new_refs)
 
1016
        result.tag_updates = {}
 
1017
        result.tag_conflicts = []
 
1018
        ret = dict(old_refs)
 
1019
        def ref_equals(refs, ref, git_sha, revid):
 
1020
            try:
 
1021
                value = refs[ref]
 
1022
            except KeyError:
 
1023
                return False
 
1024
            if (value[0] is not None and
 
1025
                git_sha is not None and
 
1026
                value[0] == git_sha):
 
1027
                return True
 
1028
            if (value[1] is not None and
 
1029
                revid is not None and
 
1030
                value[1] == revid):
 
1031
                return True
 
1032
            # FIXME: If one side only has the git sha available and the other only
 
1033
            # has the bzr revid, then this will cause us to show a tag as updated
 
1034
            # that hasn't actually been updated. 
 
1035
            return False
 
1036
        # FIXME: Check for diverged branches
 
1037
        for ref, (git_sha, revid) in new_refs.iteritems():
 
1038
            if ref_equals(ret, ref, git_sha, revid):
 
1039
                # Already up to date
 
1040
                if git_sha is None:
 
1041
                    git_sha = old_refs[ref][0]
 
1042
                if revid is None:
 
1043
                    revid = old_refs[ref][1]
 
1044
                ret[ref] = new_refs[ref] = (git_sha, revid)
 
1045
            elif ref not in ret or overwrite:
 
1046
                try:
 
1047
                    tag_name = ref_to_tag_name(ref)
 
1048
                except ValueError:
 
1049
                    pass
 
1050
                else:
 
1051
                    result.tag_updates[tag_name] = revid
 
1052
                ret[ref] = (git_sha, revid)
 
1053
            else:
 
1054
                # FIXME: Check diverged
 
1055
                diverged = False
 
1056
                if diverged:
 
1057
                    try:
 
1058
                        name = ref_to_tag_name(ref)
 
1059
                    except ValueError:
 
1060
                        pass
 
1061
                    else:
 
1062
                        result.tag_conflicts.append((name, revid, ret[name][1]))
 
1063
                else:
 
1064
                    ret[ref] = (git_sha, revid)
 
1065
        return ret
 
1066
 
 
1067
    def fetch(self, stop_revision=None, fetch_tags=None, lossy=False, limit=None):
 
1068
        assert limit is None
 
1069
        if stop_revision is None:
 
1070
            stop_revision = self.source.last_revision()
 
1071
        ret = []
 
1072
        if fetch_tags:
 
1073
            for k, v in self.source.tags.get_tag_dict().iteritems():
 
1074
                ret.append((None, v))
 
1075
        ret.append((None, stop_revision))
 
1076
        self.interrepo.fetch_objects(ret, lossy=lossy)
672
1077
 
673
1078
    def pull(self, overwrite=False, stop_revision=None, local=False,
674
 
             possible_transports=None):
675
 
        from dulwich.protocol import ZERO_SHA
 
1079
             possible_transports=None, run_hooks=True):
676
1080
        result = GitBranchPullResult()
677
1081
        result.source_branch = self.source
678
1082
        result.target_branch = self.target
679
 
        new_refs, main_ref = self._get_new_refs(stop_revision)
680
 
        def update_refs(old_refs):
681
 
            refs = dict(old_refs)
682
 
            # FIXME: Check for diverged branches
683
 
            refs.update(new_refs)
684
 
            return refs
685
 
        old_refs, new_refs = self.interrepo.fetch_refs(update_refs)
686
 
        result.old_revid = self.target.lookup_foreign_revision_id(
687
 
            old_refs.get(main_ref, ZERO_SHA))
688
 
        result.new_revid = new_refs[main_ref]
 
1083
        self.source.lock_read()
 
1084
        try:
 
1085
            self.target.lock_write()
 
1086
            try:
 
1087
                new_refs, main_ref, stop_revinfo = self._get_new_refs(
 
1088
                    stop_revision)
 
1089
                def update_refs(old_refs):
 
1090
                    return self._update_refs(result, old_refs, new_refs, overwrite)
 
1091
                try:
 
1092
                    result.revidmap, old_refs, new_refs = self.interrepo.fetch_refs(
 
1093
                        update_refs, lossy=False)
 
1094
                except NoPushSupport:
 
1095
                    raise errors.NoRoundtrippingSupport(self.source, self.target)
 
1096
                (old_sha1, result.old_revid) = old_refs.get(main_ref, (ZERO_SHA, NULL_REVISION))
 
1097
                if result.old_revid is None:
 
1098
                    result.old_revid = self.target.lookup_foreign_revision_id(old_sha1)
 
1099
                result.new_revid = new_refs[main_ref][1]
 
1100
                result.local_branch = None
 
1101
                result.master_branch = self.target
 
1102
                if run_hooks:
 
1103
                    for hook in branch.Branch.hooks['post_pull']:
 
1104
                        hook(result)
 
1105
            finally:
 
1106
                self.target.unlock()
 
1107
        finally:
 
1108
            self.source.unlock()
689
1109
        return result
690
1110
 
691
 
    def push(self, overwrite=False, stop_revision=None,
 
1111
    def push(self, overwrite=False, stop_revision=None, lossy=False,
692
1112
             _override_hook_source_branch=None):
693
 
        from dulwich.protocol import ZERO_SHA
694
1113
        result = GitBranchPushResult()
695
1114
        result.source_branch = self.source
696
1115
        result.target_branch = self.target
697
 
        new_refs, main_ref = self._get_new_refs(stop_revision)
698
 
        def update_refs(old_refs):
699
 
            refs = dict(old_refs)
700
 
            # FIXME: Check for diverged branches
701
 
            refs.update(new_refs)
702
 
            return refs
703
 
        old_refs, new_refs = self.interrepo.fetch_refs(update_refs)
704
 
        (result.old_revid, old_sha1) = old_refs.get(main_ref, (ZERO_SHA, NULL_REVISION))
705
 
        if result.old_revid is None:
706
 
            result.old_revid = self.target.lookup_foreign_revision_id(old_sha1)
707
 
        result.new_revid = new_refs[main_ref]
 
1116
        result.local_branch = None
 
1117
        result.master_branch = result.target_branch
 
1118
        self.source.lock_read()
 
1119
        try:
 
1120
            new_refs, main_ref, stop_revinfo = self._get_new_refs(stop_revision)
 
1121
            def update_refs(old_refs):
 
1122
                return self._update_refs(result, old_refs, new_refs, overwrite)
 
1123
            try:
 
1124
                result.revidmap, old_refs, new_refs = self.interrepo.fetch_refs(
 
1125
                    update_refs, lossy=lossy)
 
1126
            except NoPushSupport:
 
1127
                raise errors.NoRoundtrippingSupport(self.source, self.target)
 
1128
            (old_sha1, result.old_revid) = old_refs.get(main_ref, (ZERO_SHA, NULL_REVISION))
 
1129
            if result.old_revid is None:
 
1130
                result.old_revid = self.target.lookup_foreign_revision_id(old_sha1)
 
1131
            result.new_revid = new_refs[main_ref][1]
 
1132
            (result.new_original_revno, result.new_original_revid) = stop_revinfo
 
1133
            for hook in branch.Branch.hooks['post_push']:
 
1134
                hook(result)
 
1135
        finally:
 
1136
            self.source.unlock()
708
1137
        return result
709
1138
 
710
1139
    def lossy_push(self, stop_revision=None):
711
 
        result = GitBranchPushResult()
712
 
        result.source_branch = self.source
713
 
        result.target_branch = self.target
714
 
        new_refs, main_ref = self._get_new_refs(stop_revision)
715
 
        def update_refs(old_refs):
716
 
            refs = dict(old_refs)
717
 
            # FIXME: Check for diverged branches
718
 
            refs.update(new_refs)
719
 
            return refs
720
 
        result.revidmap, old_refs, new_refs = self.interrepo.dfetch_refs(
721
 
            update_refs)
722
 
        result.old_revid = old_refs.get(self.target.ref, (None, NULL_REVISION))[1]
723
 
        result.new_revid = new_refs[main_ref][1]
724
 
        return result
725
 
 
726
 
 
727
 
branch.InterBranch.register_optimiser(InterGitRemoteLocalBranch)
 
1140
        # For compatibility with bzr < 2.4
 
1141
        return self.push(lossy=True, stop_revision=stop_revision)
 
1142
 
 
1143
 
 
1144
branch.InterBranch.register_optimiser(InterGitLocalGitBranch)
728
1145
branch.InterBranch.register_optimiser(InterFromGitBranch)
729
1146
branch.InterBranch.register_optimiser(InterToGitBranch)
730
 
branch.InterBranch.register_optimiser(InterGitLocalRemoteBranch)
 
1147
branch.InterBranch.register_optimiser(InterLocalGitRemoteGitBranch)