/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

Support backing up bare repositories.

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