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