/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
1
# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""InterRepository operations."""
18
19
from __future__ import absolute_import
20
21
from io import BytesIO
22
23
from dulwich.errors import (
24
    NotCommitError,
25
    )
26
from dulwich.object_store import (
27
    ObjectStoreGraphWalker,
28
    )
29
from dulwich.protocol import (
30
    CAPABILITY_THIN_PACK,
31
    ZERO_SHA,
32
    )
33
from dulwich.walk import Walker
34
35
from ...errors import (
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
36
    DivergedBranches,
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
37
    FetchLimitUnsupported,
38
    InvalidRevisionId,
39
    LossyPushToSameVCS,
40
    NoRoundtrippingSupport,
41
    NoSuchRevision,
42
    )
43
from ...repository import (
44
    InterRepository,
45
    )
46
from ...revision import (
47
    NULL_REVISION,
48
    )
49
from ... import (
0.418.1 by Jelmer Vernooij
Support suppressing slow intervcs warnings.
50
    config,
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
51
    trace,
52
    ui,
53
    )
54
55
from .errors import (
56
    NoPushSupport,
57
    )
58
from .fetch import (
59
    import_git_objects,
60
    DetermineWantsRecorder,
61
    )
62
from .mapping import (
63
    needs_roundtripping,
64
    )
65
from .object_store import (
66
    get_object_store,
67
    _tree_to_objects,
68
    )
69
from .push import (
70
    MissingObjectsIterator,
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
71
    remote_divergence,
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
72
    )
73
from .refs import (
74
    is_tag,
75
    )
76
from .repository import (
77
    GitRepository,
78
    LocalGitRepository,
79
    GitRepositoryFormat,
80
    )
81
from .remote import (
82
    RemoteGitRepository,
83
    )
84
from .unpeel_map import (
85
    UnpeelMap,
86
    )
87
88
89
class InterToGitRepository(InterRepository):
90
    """InterRepository that copies into a Git repository."""
91
92
    _matching_repo_format = GitRepositoryFormat()
93
94
    def __init__(self, source, target):
95
        super(InterToGitRepository, self).__init__(source, target)
96
        self.mapping = self.target.get_mapping()
97
        self.source_store = get_object_store(self.source, self.mapping)
98
99
    @staticmethod
100
    def _get_repo_format_to_test():
101
        return None
102
103
    def copy_content(self, revision_id=None, pb=None):
104
        """See InterRepository.copy_content."""
105
        self.fetch(revision_id, pb, find_ghosts=False)
106
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
107
    def fetch_refs(self, update_refs, lossy, overwrite=False):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
108
        """Fetch possibly roundtripped revisions into the target repository
109
        and update refs.
110
111
        :param update_refs: Generate refs to fetch. Receives dictionary
112
            with old refs (git shas), returns dictionary of new names to
113
            git shas.
114
        :param lossy: Whether to roundtrip
115
        :return: old refs, new refs
116
        """
117
        raise NotImplementedError(self.fetch_refs)
118
119
    def search_missing_revision_ids(self,
120
            find_ghosts=True, revision_ids=None, if_present_ids=None,
121
            limit=None):
122
        if limit is not None:
123
            raise FetchLimitUnsupported(self)
124
        git_shas = []
125
        todo = []
126
        if revision_ids:
127
            todo.extend(revision_ids)
128
        if if_present_ids:
129
            todo.extend(revision_ids)
130
        with self.source_store.lock_read():
131
            for revid in revision_ids:
132
                if revid == NULL_REVISION:
133
                    continue
134
                git_sha = self.source_store._lookup_revision_sha1(revid)
135
                git_shas.append(git_sha)
136
            walker = Walker(self.source_store,
0.401.3 by Jelmer Vernooij
Formatting fixes.
137
                include=git_shas, exclude=[
138
                    sha for sha in self.target.controldir.get_refs_container().as_dict().values()
139
                    if sha != ZERO_SHA])
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
140
            missing_revids = set()
141
            for entry in walker:
142
                for (kind, type_data) in self.source_store.lookup_git_sha(entry.commit.id):
143
                    if kind == "commit":
144
                        missing_revids.add(type_data[0])
145
        return self.source.revision_ids_to_search_result(missing_revids)
146
147
    def _warn_slow(self):
0.418.1 by Jelmer Vernooij
Support suppressing slow intervcs warnings.
148
        if not config.GlobalConfig().suppress_warning('slow_intervcs_push'):
149
            trace.warning(
150
                'Pushing from a Bazaar to a Git repository. '
151
                'For better performance, push into a Bazaar repository.')
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
152
153
154
class InterToLocalGitRepository(InterToGitRepository):
155
    """InterBranch implementation between a Bazaar and a Git repository."""
156
157
    def __init__(self, source, target):
158
        super(InterToLocalGitRepository, self).__init__(source, target)
159
        self.target_store = self.target.controldir._git.object_store
160
        self.target_refs = self.target.controldir._git.refs
161
162
    def _commit_needs_fetching(self, sha_id):
163
        try:
164
            return (sha_id not in self.target_store)
165
        except NoSuchRevision:
166
            # Ghost, can't push
167
            return False
168
169
    def _revision_needs_fetching(self, sha_id, revid):
170
        if revid == NULL_REVISION:
171
            return False
172
        if sha_id is None:
173
            try:
174
                sha_id = self.source_store._lookup_revision_sha1(revid)
175
            except KeyError:
176
                return False
177
        return self._commit_needs_fetching(sha_id)
178
179
    def missing_revisions(self, stop_revisions):
180
        """Find the revisions that are missing from the target repository.
181
182
        :param stop_revisions: Revisions to check for (tuples with
183
            Git SHA1, bzr revid)
184
        :return: sequence of missing revisions, in topological order
185
        :raise: NoSuchRevision if the stop_revisions are not present in
186
            the source
187
        """
188
        revid_sha_map = {}
189
        stop_revids = []
190
        for (sha1, revid) in stop_revisions:
191
            if sha1 is not None and revid is not None:
192
                revid_sha_map[revid] = sha1
193
                stop_revids.append(revid)
194
            elif sha1 is not None:
195
                if self._commit_needs_fetching(sha1):
196
                    for (kind, (revid, tree_sha, verifiers)) in self.source_store.lookup_git_sha(sha1):
197
                        revid_sha_map[revid] = sha1
198
                        stop_revids.append(revid)
199
            else:
200
                if revid is None:
201
                    raise AssertionError
202
                stop_revids.append(revid)
203
        missing = set()
204
        graph = self.source.get_graph()
205
        pb = ui.ui_factory.nested_progress_bar()
206
        try:
207
            while stop_revids:
208
                new_stop_revids = []
209
                for revid in stop_revids:
210
                    sha1 = revid_sha_map.get(revid)
211
                    if (not revid in missing and
212
                        self._revision_needs_fetching(sha1, revid)):
213
                        missing.add(revid)
214
                        new_stop_revids.append(revid)
215
                stop_revids = set()
216
                parent_map = graph.get_parent_map(new_stop_revids)
217
                for parent_revids in parent_map.itervalues():
218
                    stop_revids.update(parent_revids)
219
                pb.update("determining revisions to fetch", len(missing))
220
        finally:
221
            pb.finished()
222
        return graph.iter_topo_order(missing)
223
224
    def _get_target_bzr_refs(self):
225
        """Return a dictionary with references.
226
227
        :return: Dictionary with reference names as keys and tuples
228
            with Git SHA, Bazaar revid as values.
229
        """
230
        bzr_refs = {}
231
        refs = {}
232
        for k in self.target._git.refs.allkeys():
233
            try:
234
                v = self.target._git.refs[k]
235
            except KeyError:
236
                # broken symref?
237
                continue
238
            try:
239
                for (kind, type_data) in self.source_store.lookup_git_sha(v):
240
                    if kind == "commit" and self.source.has_revision(type_data[0]):
241
                        revid = type_data[0]
242
                        break
243
                else:
244
                    revid = None
245
            except KeyError:
246
                revid = None
247
            bzr_refs[k] = (v, revid)
248
        return bzr_refs
249
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
250
    def fetch_refs(self, update_refs, lossy, overwrite=False):
0.403.1 by Jelmer Vernooij
Properly warn on slow push from bzr->git.
251
        self._warn_slow()
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
252
        with self.source_store.lock_read():
253
            old_refs = self._get_target_bzr_refs()
254
            new_refs = update_refs(old_refs)
255
            revidmap = self.fetch_objects(
256
                [(git_sha, bzr_revid) for (git_sha, bzr_revid) in new_refs.values() if git_sha is None or not git_sha.startswith('ref:')], lossy=lossy)
257
            for name, (gitid, revid) in new_refs.iteritems():
258
                if gitid is None:
259
                    try:
260
                        gitid = revidmap[revid][0]
261
                    except KeyError:
262
                        gitid = self.source_store._lookup_revision_sha1(revid)
263
                if len(gitid) != 40 and not gitid.startswith('ref: '):
264
                    raise AssertionError("invalid ref contents: %r" % gitid)
265
                self.target_refs[name] = gitid
266
        return revidmap, old_refs, new_refs
267
268
    def fetch_objects(self, revs, lossy, limit=None):
269
        if not lossy and not self.mapping.roundtripping:
270
            for git_sha, bzr_revid in revs:
271
                if bzr_revid is not None and needs_roundtripping(self.source, bzr_revid):
272
                    raise NoPushSupport(self.source, self.target, self.mapping,
273
                                        bzr_revid)
274
        with self.source_store.lock_read():
275
            todo = list(self.missing_revisions(revs))[:limit]
276
            revidmap = {}
277
            pb = ui.ui_factory.nested_progress_bar()
278
            try:
279
                object_generator = MissingObjectsIterator(
280
                    self.source_store, self.source, pb)
281
                for (old_revid, git_sha) in object_generator.import_revisions(
282
                    todo, lossy=lossy):
283
                    if lossy:
284
                        new_revid = self.mapping.revision_id_foreign_to_bzr(git_sha)
285
                    else:
286
                        new_revid = old_revid
287
                        try:
288
                            self.mapping.revision_id_bzr_to_foreign(old_revid)
289
                        except InvalidRevisionId:
290
                            refname = self.mapping.revid_as_refname(old_revid)
291
                            self.target_refs[refname] = git_sha
292
                    revidmap[old_revid] = (git_sha, new_revid)
293
                self.target_store.add_objects(object_generator)
294
                return revidmap
295
            finally:
296
                pb.finished()
297
298
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
299
            fetch_spec=None, mapped_refs=None):
300
        if mapped_refs is not None:
301
            stop_revisions = mapped_refs
302
        elif revision_id is not None:
303
            stop_revisions = [(None, revision_id)]
304
        elif fetch_spec is not None:
305
            recipe = fetch_spec.get_recipe()
306
            if recipe[0] in ("search", "proxy-search"):
307
                stop_revisions = [(None, revid) for revid in recipe[1]]
308
            else:
309
                raise AssertionError("Unsupported search result type %s" % recipe[0])
310
        else:
311
            stop_revisions = [(None, revid) for revid in self.source.all_revision_ids()]
312
        self._warn_slow()
313
        try:
314
            self.fetch_objects(stop_revisions, lossy=False)
315
        except NoPushSupport:
316
            raise NoRoundtrippingSupport(self.source, self.target)
317
318
    @staticmethod
319
    def is_compatible(source, target):
320
        """Be compatible with GitRepository."""
321
        return (not isinstance(source, GitRepository) and
322
                isinstance(target, LocalGitRepository))
323
324
325
class InterToRemoteGitRepository(InterToGitRepository):
326
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
327
    def fetch_refs(self, update_refs, lossy, overwrite=False):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
328
        """Import the gist of the ancestry of a particular revision."""
329
        if not lossy and not self.mapping.roundtripping:
330
            raise NoPushSupport(self.source, self.target, self.mapping)
331
        unpeel_map = UnpeelMap.from_repository(self.source)
332
        revidmap = {}
333
        def determine_wants(old_refs):
334
            ret = {}
335
            self.old_refs = dict([(k, (v, None)) for (k, v) in old_refs.iteritems()])
336
            self.new_refs = update_refs(self.old_refs)
337
            for name, (gitid, revid) in self.new_refs.iteritems():
338
                if gitid is None:
339
                    git_sha = self.source_store._lookup_revision_sha1(revid)
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
340
                    gitid = unpeel_map.re_unpeel_tag(git_sha, old_refs.get(name))
341
                if not overwrite:
342
                    if remote_divergence(old_refs.get(name), gitid, self.source_store):
343
                        raise DivergedBranches(self.source, self.target)
344
                ret[name] = gitid
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
345
            return ret
346
        self._warn_slow()
347
        with self.source_store.lock_read():
348
            new_refs = self.target.send_pack(determine_wants,
349
                    self.source_store.generate_lossy_pack_data)
350
        # FIXME: revidmap?
351
        return revidmap, self.old_refs, self.new_refs
352
353
    @staticmethod
354
    def is_compatible(source, target):
355
        """Be compatible with GitRepository."""
356
        return (not isinstance(source, GitRepository) and
357
                isinstance(target, RemoteGitRepository))
358
359
360
class InterFromGitRepository(InterRepository):
361
362
    _matching_repo_format = GitRepositoryFormat()
363
364
    def _target_has_shas(self, shas):
365
        raise NotImplementedError(self._target_has_shas)
366
367
    def get_determine_wants_heads(self, wants, include_tags=False):
368
        wants = set(wants)
369
        def determine_wants(refs):
370
            potential = set(wants)
371
            if include_tags:
372
                for k, unpeeled in refs.iteritems():
373
                    if k.endswith("^{}"):
374
                        continue
375
                    if not is_tag(k):
376
                        continue
377
                    if unpeeled == ZERO_SHA:
378
                        continue
379
                    potential.add(unpeeled)
380
            return list(potential - self._target_has_shas(potential))
381
        return determine_wants
382
383
    def determine_wants_all(self, refs):
384
        raise NotImplementedError(self.determine_wants_all)
385
386
    @staticmethod
387
    def _get_repo_format_to_test():
388
        return None
389
390
    def copy_content(self, revision_id=None):
391
        """See InterRepository.copy_content."""
392
        self.fetch(revision_id, find_ghosts=False)
393
394
    def search_missing_revision_ids(self,
395
            find_ghosts=True, revision_ids=None, if_present_ids=None,
396
            limit=None):
397
        if limit is not None:
398
            raise FetchLimitUnsupported(self)
399
        git_shas = []
400
        todo = []
401
        if revision_ids:
402
            todo.extend(revision_ids)
403
        if if_present_ids:
404
            todo.extend(revision_ids)
0.401.3 by Jelmer Vernooij
Formatting fixes.
405
        with self.lock_read():
406
            for revid in revision_ids:
407
                if revid == NULL_REVISION:
408
                    continue
409
                git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
410
                git_shas.append(git_sha)
411
            walker = Walker(self.source._git.object_store,
412
                include=git_shas, exclude=[
413
                    sha for sha in self.target.controldir.get_refs_container().as_dict().values()
414
                    if sha != ZERO_SHA])
415
            missing_revids = set()
416
            for entry in walker:
417
                missing_revids.add(self.source.lookup_foreign_revision_id(entry.commit.id))
418
            return self.source.revision_ids_to_search_result(missing_revids)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
419
420
421
class InterGitNonGitRepository(InterFromGitRepository):
422
    """Base InterRepository that copies revisions from a Git into a non-Git
423
    repository."""
424
425
    def _target_has_shas(self, shas):
426
        revids = {}
427
        for sha in shas:
428
            try:
429
                revid = self.source.lookup_foreign_revision_id(sha)
430
            except NotCommitError:
431
                # Commit is definitely not present
432
                continue
433
            else:
434
                revids[revid] = sha
435
        return set([revids[r] for r in self.target.has_revisions(revids)])
436
437
    def determine_wants_all(self, refs):
438
        potential = set()
439
        for k, v in refs.iteritems():
440
            # For non-git target repositories, only worry about peeled
441
            if v == ZERO_SHA:
442
                continue
443
            potential.add(self.source.controldir.get_peeled(k) or v)
444
        return list(potential - self._target_has_shas(potential))
445
446
    def get_determine_wants_heads(self, wants, include_tags=False):
447
        wants = set(wants)
448
        def determine_wants(refs):
449
            potential = set(wants)
450
            if include_tags:
451
                for k, unpeeled in refs.iteritems():
452
                    if not is_tag(k):
453
                        continue
454
                    if unpeeled == ZERO_SHA:
455
                        continue
456
                    potential.add(self.source.controldir.get_peeled(k) or unpeeled)
457
            return list(potential - self._target_has_shas(potential))
458
        return determine_wants
459
460
    def _warn_slow(self):
0.418.1 by Jelmer Vernooij
Support suppressing slow intervcs warnings.
461
        if not config.GlobalConfig().suppress_warning('slow_intervcs_push'):
462
            trace.warning(
463
                'Fetching from Git to Bazaar repository. '
464
                'For better performance, fetch into a Git repository.')
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
465
466
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
467
        """Fetch objects from a remote server.
468
469
        :param determine_wants: determine_wants callback
470
        :param mapping: BzrGitMapping to use
471
        :param limit: Maximum number of commits to import.
472
        :return: Tuple with pack hint, last imported revision id and remote refs
473
        """
474
        raise NotImplementedError(self.fetch_objects)
475
476
    def get_determine_wants_revids(self, revids, include_tags=False):
477
        wants = set()
478
        for revid in set(revids):
479
            if self.target.has_revision(revid):
480
                continue
481
            git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
482
            wants.add(git_sha)
483
        return self.get_determine_wants_heads(wants, include_tags=include_tags)
484
485
    def fetch(self, revision_id=None, find_ghosts=False,
486
              mapping=None, fetch_spec=None, include_tags=False):
487
        if mapping is None:
488
            mapping = self.source.get_mapping()
489
        if revision_id is not None:
490
            interesting_heads = [revision_id]
491
        elif fetch_spec is not None:
492
            recipe = fetch_spec.get_recipe()
493
            if recipe[0] in ("search", "proxy-search"):
494
                interesting_heads = recipe[1]
495
            else:
496
                raise AssertionError("Unsupported search result type %s" %
497
                        recipe[0])
498
        else:
499
            interesting_heads = None
500
501
        if interesting_heads is not None:
502
            determine_wants = self.get_determine_wants_revids(
503
                interesting_heads, include_tags=include_tags)
504
        else:
505
            determine_wants = self.determine_wants_all
506
507
        (pack_hint, _, remote_refs) = self.fetch_objects(determine_wants,
508
            mapping)
509
        if pack_hint is not None and self.target._format.pack_compresses:
510
            self.target.pack(hint=pack_hint)
511
        return remote_refs
512
513
514
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
515
    """InterRepository that copies revisions from a remote Git into a non-Git
516
    repository."""
517
518
    def get_target_heads(self):
519
        # FIXME: This should be more efficient
520
        all_revs = self.target.all_revision_ids()
521
        parent_map = self.target.get_parent_map(all_revs)
522
        all_parents = set()
523
        map(all_parents.update, parent_map.itervalues())
524
        return set(all_revs) - all_parents
525
526
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
527
        """See `InterGitNonGitRepository`."""
528
        self._warn_slow()
529
        store = get_object_store(self.target, mapping)
530
        with store.lock_write():
531
            heads = self.get_target_heads()
532
            graph_walker = ObjectStoreGraphWalker(
533
                [store._lookup_revision_sha1(head) for head in heads],
534
                lambda sha: store[sha].parents)
535
            wants_recorder = DetermineWantsRecorder(determine_wants)
536
537
            pb = ui.ui_factory.nested_progress_bar()
538
            try:
539
                objects_iter = self.source.fetch_objects(
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
540
                    wants_recorder, graph_walker, store.get_raw)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
541
                trace.mutter("Importing %d new revisions",
542
                             len(wants_recorder.wants))
543
                (pack_hint, last_rev) = import_git_objects(self.target,
544
                    mapping, objects_iter, store, wants_recorder.wants, pb,
545
                    limit)
546
                return (pack_hint, last_rev, wants_recorder.remote_refs)
547
            finally:
548
                pb.finished()
549
550
    @staticmethod
551
    def is_compatible(source, target):
552
        """Be compatible with GitRepository."""
553
        if not isinstance(source, RemoteGitRepository):
554
            return False
555
        if not target.supports_rich_root():
556
            return False
557
        if isinstance(target, GitRepository):
558
            return False
559
        if not getattr(target._format, "supports_full_versioned_files", True):
560
            return False
561
        return True
562
563
564
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
565
    """InterRepository that copies revisions from a local Git into a non-Git
566
    repository."""
567
568
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
569
        """See `InterGitNonGitRepository`."""
570
        self._warn_slow()
571
        remote_refs = self.source.controldir.get_refs_container().as_dict()
572
        wants = determine_wants(remote_refs)
573
        create_pb = None
574
        pb = ui.ui_factory.nested_progress_bar()
575
        target_git_object_retriever = get_object_store(self.target, mapping)
576
        try:
577
            target_git_object_retriever.lock_write()
578
            try:
579
                (pack_hint, last_rev) = import_git_objects(self.target,
580
                    mapping, self.source._git.object_store,
581
                    target_git_object_retriever, wants, pb, limit)
582
                return (pack_hint, last_rev, remote_refs)
583
            finally:
584
                target_git_object_retriever.unlock()
585
        finally:
586
            pb.finished()
587
588
    @staticmethod
589
    def is_compatible(source, target):
590
        """Be compatible with GitRepository."""
591
        if not isinstance(source, LocalGitRepository):
592
            return False
593
        if not target.supports_rich_root():
594
            return False
595
        if isinstance(target, GitRepository):
596
            return False
597
        if not getattr(target._format, "supports_full_versioned_files", True):
598
            return False
599
        return True
600
601
602
class InterGitGitRepository(InterFromGitRepository):
603
    """InterRepository that copies between Git repositories."""
604
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
605
    def fetch_refs(self, update_refs, lossy, overwrite=False):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
606
        if lossy:
607
            raise LossyPushToSameVCS(self.source, self.target)
608
        old_refs = self.target.controldir.get_refs_container()
609
        ref_changes = {}
610
        def determine_wants(heads):
611
            old_refs = dict([(k, (v, None)) for (k, v) in heads.as_dict().iteritems()])
612
            new_refs = update_refs(old_refs)
613
            ref_changes.update(new_refs)
614
            return [sha1 for (sha1, bzr_revid) in new_refs.itervalues()]
615
        self.fetch_objects(determine_wants, lossy=lossy)
616
        for k, (git_sha, bzr_revid) in ref_changes.iteritems():
617
            self.target._git.refs[k] = git_sha
618
        new_refs = self.target.controldir.get_refs_container()
619
        return None, old_refs, new_refs
620
621
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
622
        raise NotImplementedError(self.fetch_objects)
623
624
    def _target_has_shas(self, shas):
625
        return set([sha for sha in shas if sha in self.target._git.object_store])
626
627
    def fetch(self, revision_id=None, find_ghosts=False,
628
              mapping=None, fetch_spec=None, branches=None, limit=None, include_tags=False):
629
        if mapping is None:
630
            mapping = self.source.get_mapping()
631
        if revision_id is not None:
632
            args = [revision_id]
633
        elif fetch_spec is not None:
634
            recipe = fetch_spec.get_recipe()
635
            if recipe[0] in ("search", "proxy-search"):
636
                heads = recipe[1]
637
            else:
638
                raise AssertionError(
639
                    "Unsupported search result type %s" % recipe[0])
640
            args = heads
641
        if branches is not None:
642
            def determine_wants(refs):
643
                ret = []
644
                for name, value in refs.iteritems():
645
                    if value == ZERO_SHA:
646
                        continue
647
648
                    if name in branches or (include_tags and is_tag(name)):
649
                        ret.append(value)
650
                return ret
651
        elif fetch_spec is None and revision_id is None:
652
            determine_wants = self.determine_wants_all
653
        else:
654
            determine_wants = self.get_determine_wants_revids(args, include_tags=include_tags)
655
        wants_recorder = DetermineWantsRecorder(determine_wants)
656
        self.fetch_objects(wants_recorder, mapping, limit=limit)
657
        return wants_recorder.remote_refs
658
659
    def get_determine_wants_revids(self, revids, include_tags=False):
660
        wants = set()
661
        for revid in set(revids):
662
            if revid == NULL_REVISION:
663
                continue
664
            git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
665
            wants.add(git_sha)
666
        return self.get_determine_wants_heads(wants, include_tags=include_tags)
667
668
    def determine_wants_all(self, refs):
669
        potential = set([v for v in refs.values() if not v == ZERO_SHA])
670
        return list(potential - self._target_has_shas(potential))
671
672
673
class InterLocalGitLocalGitRepository(InterGitGitRepository):
674
675
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
676
        if lossy:
677
            raise LossyPushToSameVCS(self.source, self.target)
678
        if limit is not None:
679
            raise FetchLimitUnsupported(self)
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
680
        refs = self.source._git.fetch(self.target._git, determine_wants)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
681
        return (None, None, refs)
682
683
    @staticmethod
684
    def is_compatible(source, target):
685
        """Be compatible with GitRepository."""
686
        return (isinstance(source, LocalGitRepository) and
687
                isinstance(target, LocalGitRepository))
688
689
690
class InterRemoteGitLocalGitRepository(InterGitGitRepository):
691
692
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
693
        if lossy:
694
            raise LossyPushToSameVCS(self.source, self.target)
695
        if limit is not None:
696
            raise FetchLimitUnsupported(self)
697
        graphwalker = self.target._git.get_graph_walker()
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
698
        if CAPABILITY_THIN_PACK in self.source.controldir._client._fetch_capabilities:
699
            # TODO(jelmer): Avoid reading entire file into memory and
700
            # only processing it after the whole file has been fetched.
701
            f = BytesIO()
702
703
            def commit():
704
                if f.tell():
705
                    f.seek(0)
706
                    self.target._git.object_store.move_in_thin_pack(f)
707
708
            def abort():
709
                pass
710
        else:
711
            f, commit, abort = self.target._git.object_store.add_pack()
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
712
        try:
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
713
            refs = self.source.controldir.fetch_pack(
714
                determine_wants, graphwalker, f.write)
715
            commit()
716
            return (None, None, refs)
717
        except BaseException:
718
            abort()
719
            raise
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
720
721
    @staticmethod
722
    def is_compatible(source, target):
723
        """Be compatible with GitRepository."""
724
        return (isinstance(source, RemoteGitRepository) and
725
                isinstance(target, LocalGitRepository))