/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
    )
6987.1.2 by Jelmer Vernooij
Correctly deal with symbolic refs when pushing.
33
from dulwich.refs import SYMREF
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
34
from dulwich.walk import Walker
35
36
from ...errors import (
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
37
    DivergedBranches,
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
38
    FetchLimitUnsupported,
39
    InvalidRevisionId,
40
    LossyPushToSameVCS,
41
    NoRoundtrippingSupport,
42
    NoSuchRevision,
43
    )
44
from ...repository import (
45
    InterRepository,
46
    )
47
from ...revision import (
48
    NULL_REVISION,
49
    )
50
from ... import (
0.418.1 by Jelmer Vernooij
Support suppressing slow intervcs warnings.
51
    config,
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
52
    trace,
53
    ui,
54
    )
55
56
from .errors import (
57
    NoPushSupport,
58
    )
59
from .fetch import (
60
    import_git_objects,
61
    DetermineWantsRecorder,
62
    )
63
from .mapping import (
64
    needs_roundtripping,
65
    )
66
from .object_store import (
67
    get_object_store,
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:
6987.1.2 by Jelmer Vernooij
Correctly deal with symbolic refs when pushing.
234
                v = self.target._git.refs.read_ref(k)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
235
            except KeyError:
236
                # broken symref?
237
                continue
6987.1.2 by Jelmer Vernooij
Correctly deal with symbolic refs when pushing.
238
            revid = None
239
            if not v.startswith(SYMREF):
240
                try:
241
                    for (kind, type_data) in self.source_store.lookup_git_sha(v):
242
                        if kind == "commit" and self.source.has_revision(type_data[0]):
243
                            revid = type_data[0]
244
                            break
245
                except KeyError:
246
                    pass
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
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(
6987.1.2 by Jelmer Vernooij
Correctly deal with symbolic refs when pushing.
256
                [(git_sha, bzr_revid) for (git_sha, bzr_revid) in new_refs.values() if git_sha is None or not git_sha.startswith(SYMREF)], lossy=lossy)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
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)
6987.1.2 by Jelmer Vernooij
Correctly deal with symbolic refs when pushing.
263
                if gitid.startswith(SYMREF):
264
                    self.target_refs.set_symbolic_ref(name, gitid[len(SYMREF):])
6987.1.1 by Jelmer Vernooij
Guard against race conditions.
265
                else:
6987.1.2 by Jelmer Vernooij
Correctly deal with symbolic refs when pushing.
266
                    try:
6988 by Jelmer Vernooij
Merge lp:~jelmer/brz/git-fixes.
267
                        old_git_id = old_refs[name][0]
6987.1.2 by Jelmer Vernooij
Correctly deal with symbolic refs when pushing.
268
                    except KeyError:
269
                        self.target_refs.add_if_new(name, gitid)
270
                    else:
271
                        self.target_refs.set_if_equals(name, old_git_id, gitid)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
272
        return revidmap, old_refs, new_refs
273
274
    def fetch_objects(self, revs, lossy, limit=None):
275
        if not lossy and not self.mapping.roundtripping:
276
            for git_sha, bzr_revid in revs:
277
                if bzr_revid is not None and needs_roundtripping(self.source, bzr_revid):
278
                    raise NoPushSupport(self.source, self.target, self.mapping,
279
                                        bzr_revid)
280
        with self.source_store.lock_read():
281
            todo = list(self.missing_revisions(revs))[:limit]
282
            revidmap = {}
283
            pb = ui.ui_factory.nested_progress_bar()
284
            try:
285
                object_generator = MissingObjectsIterator(
286
                    self.source_store, self.source, pb)
287
                for (old_revid, git_sha) in object_generator.import_revisions(
288
                    todo, lossy=lossy):
289
                    if lossy:
290
                        new_revid = self.mapping.revision_id_foreign_to_bzr(git_sha)
291
                    else:
292
                        new_revid = old_revid
293
                        try:
294
                            self.mapping.revision_id_bzr_to_foreign(old_revid)
295
                        except InvalidRevisionId:
296
                            refname = self.mapping.revid_as_refname(old_revid)
297
                            self.target_refs[refname] = git_sha
298
                    revidmap[old_revid] = (git_sha, new_revid)
299
                self.target_store.add_objects(object_generator)
300
                return revidmap
301
            finally:
302
                pb.finished()
303
304
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
305
            fetch_spec=None, mapped_refs=None):
306
        if mapped_refs is not None:
307
            stop_revisions = mapped_refs
308
        elif revision_id is not None:
309
            stop_revisions = [(None, revision_id)]
310
        elif fetch_spec is not None:
311
            recipe = fetch_spec.get_recipe()
312
            if recipe[0] in ("search", "proxy-search"):
313
                stop_revisions = [(None, revid) for revid in recipe[1]]
314
            else:
315
                raise AssertionError("Unsupported search result type %s" % recipe[0])
316
        else:
317
            stop_revisions = [(None, revid) for revid in self.source.all_revision_ids()]
318
        self._warn_slow()
319
        try:
320
            self.fetch_objects(stop_revisions, lossy=False)
321
        except NoPushSupport:
322
            raise NoRoundtrippingSupport(self.source, self.target)
323
324
    @staticmethod
325
    def is_compatible(source, target):
326
        """Be compatible with GitRepository."""
327
        return (not isinstance(source, GitRepository) and
328
                isinstance(target, LocalGitRepository))
329
330
331
class InterToRemoteGitRepository(InterToGitRepository):
332
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
333
    def fetch_refs(self, update_refs, lossy, overwrite=False):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
334
        """Import the gist of the ancestry of a particular revision."""
335
        if not lossy and not self.mapping.roundtripping:
336
            raise NoPushSupport(self.source, self.target, self.mapping)
337
        unpeel_map = UnpeelMap.from_repository(self.source)
338
        revidmap = {}
339
        def determine_wants(old_refs):
340
            ret = {}
341
            self.old_refs = dict([(k, (v, None)) for (k, v) in old_refs.iteritems()])
342
            self.new_refs = update_refs(self.old_refs)
343
            for name, (gitid, revid) in self.new_refs.iteritems():
344
                if gitid is None:
345
                    git_sha = self.source_store._lookup_revision_sha1(revid)
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
346
                    gitid = unpeel_map.re_unpeel_tag(git_sha, old_refs.get(name))
347
                if not overwrite:
348
                    if remote_divergence(old_refs.get(name), gitid, self.source_store):
349
                        raise DivergedBranches(self.source, self.target)
350
                ret[name] = gitid
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
351
            return ret
352
        self._warn_slow()
353
        with self.source_store.lock_read():
354
            new_refs = self.target.send_pack(determine_wants,
355
                    self.source_store.generate_lossy_pack_data)
356
        # FIXME: revidmap?
357
        return revidmap, self.old_refs, self.new_refs
358
359
    @staticmethod
360
    def is_compatible(source, target):
361
        """Be compatible with GitRepository."""
362
        return (not isinstance(source, GitRepository) and
363
                isinstance(target, RemoteGitRepository))
364
365
366
class InterFromGitRepository(InterRepository):
367
368
    _matching_repo_format = GitRepositoryFormat()
369
370
    def _target_has_shas(self, shas):
371
        raise NotImplementedError(self._target_has_shas)
372
373
    def get_determine_wants_heads(self, wants, include_tags=False):
374
        wants = set(wants)
375
        def determine_wants(refs):
376
            potential = set(wants)
377
            if include_tags:
378
                for k, unpeeled in refs.iteritems():
379
                    if k.endswith("^{}"):
380
                        continue
381
                    if not is_tag(k):
382
                        continue
383
                    if unpeeled == ZERO_SHA:
384
                        continue
385
                    potential.add(unpeeled)
386
            return list(potential - self._target_has_shas(potential))
387
        return determine_wants
388
389
    def determine_wants_all(self, refs):
390
        raise NotImplementedError(self.determine_wants_all)
391
392
    @staticmethod
393
    def _get_repo_format_to_test():
394
        return None
395
396
    def copy_content(self, revision_id=None):
397
        """See InterRepository.copy_content."""
398
        self.fetch(revision_id, find_ghosts=False)
399
400
    def search_missing_revision_ids(self,
401
            find_ghosts=True, revision_ids=None, if_present_ids=None,
402
            limit=None):
403
        if limit is not None:
404
            raise FetchLimitUnsupported(self)
405
        git_shas = []
406
        todo = []
407
        if revision_ids:
408
            todo.extend(revision_ids)
409
        if if_present_ids:
410
            todo.extend(revision_ids)
0.401.3 by Jelmer Vernooij
Formatting fixes.
411
        with self.lock_read():
412
            for revid in revision_ids:
413
                if revid == NULL_REVISION:
414
                    continue
415
                git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
416
                git_shas.append(git_sha)
417
            walker = Walker(self.source._git.object_store,
418
                include=git_shas, exclude=[
419
                    sha for sha in self.target.controldir.get_refs_container().as_dict().values()
420
                    if sha != ZERO_SHA])
421
            missing_revids = set()
422
            for entry in walker:
423
                missing_revids.add(self.source.lookup_foreign_revision_id(entry.commit.id))
424
            return self.source.revision_ids_to_search_result(missing_revids)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
425
426
427
class InterGitNonGitRepository(InterFromGitRepository):
428
    """Base InterRepository that copies revisions from a Git into a non-Git
429
    repository."""
430
431
    def _target_has_shas(self, shas):
432
        revids = {}
433
        for sha in shas:
434
            try:
435
                revid = self.source.lookup_foreign_revision_id(sha)
436
            except NotCommitError:
437
                # Commit is definitely not present
438
                continue
439
            else:
440
                revids[revid] = sha
441
        return set([revids[r] for r in self.target.has_revisions(revids)])
442
443
    def determine_wants_all(self, refs):
444
        potential = set()
445
        for k, v in refs.iteritems():
446
            # For non-git target repositories, only worry about peeled
447
            if v == ZERO_SHA:
448
                continue
449
            potential.add(self.source.controldir.get_peeled(k) or v)
450
        return list(potential - self._target_has_shas(potential))
451
452
    def get_determine_wants_heads(self, wants, include_tags=False):
453
        wants = set(wants)
454
        def determine_wants(refs):
455
            potential = set(wants)
456
            if include_tags:
457
                for k, unpeeled in refs.iteritems():
458
                    if not is_tag(k):
459
                        continue
460
                    if unpeeled == ZERO_SHA:
461
                        continue
462
                    potential.add(self.source.controldir.get_peeled(k) or unpeeled)
463
            return list(potential - self._target_has_shas(potential))
464
        return determine_wants
465
466
    def _warn_slow(self):
0.418.1 by Jelmer Vernooij
Support suppressing slow intervcs warnings.
467
        if not config.GlobalConfig().suppress_warning('slow_intervcs_push'):
468
            trace.warning(
469
                'Fetching from Git to Bazaar repository. '
470
                'For better performance, fetch into a Git repository.')
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
471
472
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
473
        """Fetch objects from a remote server.
474
475
        :param determine_wants: determine_wants callback
476
        :param mapping: BzrGitMapping to use
477
        :param limit: Maximum number of commits to import.
478
        :return: Tuple with pack hint, last imported revision id and remote refs
479
        """
480
        raise NotImplementedError(self.fetch_objects)
481
482
    def get_determine_wants_revids(self, revids, include_tags=False):
483
        wants = set()
484
        for revid in set(revids):
485
            if self.target.has_revision(revid):
486
                continue
487
            git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
488
            wants.add(git_sha)
489
        return self.get_determine_wants_heads(wants, include_tags=include_tags)
490
491
    def fetch(self, revision_id=None, find_ghosts=False,
492
              mapping=None, fetch_spec=None, include_tags=False):
493
        if mapping is None:
494
            mapping = self.source.get_mapping()
495
        if revision_id is not None:
496
            interesting_heads = [revision_id]
497
        elif fetch_spec is not None:
498
            recipe = fetch_spec.get_recipe()
499
            if recipe[0] in ("search", "proxy-search"):
500
                interesting_heads = recipe[1]
501
            else:
502
                raise AssertionError("Unsupported search result type %s" %
503
                        recipe[0])
504
        else:
505
            interesting_heads = None
506
507
        if interesting_heads is not None:
508
            determine_wants = self.get_determine_wants_revids(
509
                interesting_heads, include_tags=include_tags)
510
        else:
511
            determine_wants = self.determine_wants_all
512
513
        (pack_hint, _, remote_refs) = self.fetch_objects(determine_wants,
514
            mapping)
515
        if pack_hint is not None and self.target._format.pack_compresses:
516
            self.target.pack(hint=pack_hint)
517
        return remote_refs
518
519
520
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
521
    """InterRepository that copies revisions from a remote Git into a non-Git
522
    repository."""
523
524
    def get_target_heads(self):
525
        # FIXME: This should be more efficient
526
        all_revs = self.target.all_revision_ids()
527
        parent_map = self.target.get_parent_map(all_revs)
528
        all_parents = set()
529
        map(all_parents.update, parent_map.itervalues())
530
        return set(all_revs) - all_parents
531
532
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
533
        """See `InterGitNonGitRepository`."""
534
        self._warn_slow()
535
        store = get_object_store(self.target, mapping)
536
        with store.lock_write():
537
            heads = self.get_target_heads()
538
            graph_walker = ObjectStoreGraphWalker(
539
                [store._lookup_revision_sha1(head) for head in heads],
540
                lambda sha: store[sha].parents)
541
            wants_recorder = DetermineWantsRecorder(determine_wants)
542
543
            pb = ui.ui_factory.nested_progress_bar()
544
            try:
545
                objects_iter = self.source.fetch_objects(
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
546
                    wants_recorder, graph_walker, store.get_raw)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
547
                trace.mutter("Importing %d new revisions",
548
                             len(wants_recorder.wants))
549
                (pack_hint, last_rev) = import_git_objects(self.target,
550
                    mapping, objects_iter, store, wants_recorder.wants, pb,
551
                    limit)
552
                return (pack_hint, last_rev, wants_recorder.remote_refs)
553
            finally:
554
                pb.finished()
555
556
    @staticmethod
557
    def is_compatible(source, target):
558
        """Be compatible with GitRepository."""
559
        if not isinstance(source, RemoteGitRepository):
560
            return False
561
        if not target.supports_rich_root():
562
            return False
563
        if isinstance(target, GitRepository):
564
            return False
565
        if not getattr(target._format, "supports_full_versioned_files", True):
566
            return False
567
        return True
568
569
570
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
571
    """InterRepository that copies revisions from a local Git into a non-Git
572
    repository."""
573
574
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
575
        """See `InterGitNonGitRepository`."""
576
        self._warn_slow()
577
        remote_refs = self.source.controldir.get_refs_container().as_dict()
578
        wants = determine_wants(remote_refs)
579
        create_pb = None
580
        pb = ui.ui_factory.nested_progress_bar()
581
        target_git_object_retriever = get_object_store(self.target, mapping)
582
        try:
583
            target_git_object_retriever.lock_write()
584
            try:
585
                (pack_hint, last_rev) = import_git_objects(self.target,
586
                    mapping, self.source._git.object_store,
587
                    target_git_object_retriever, wants, pb, limit)
588
                return (pack_hint, last_rev, remote_refs)
589
            finally:
590
                target_git_object_retriever.unlock()
591
        finally:
592
            pb.finished()
593
594
    @staticmethod
595
    def is_compatible(source, target):
596
        """Be compatible with GitRepository."""
597
        if not isinstance(source, LocalGitRepository):
598
            return False
599
        if not target.supports_rich_root():
600
            return False
601
        if isinstance(target, GitRepository):
602
            return False
603
        if not getattr(target._format, "supports_full_versioned_files", True):
604
            return False
605
        return True
606
607
608
class InterGitGitRepository(InterFromGitRepository):
609
    """InterRepository that copies between Git repositories."""
610
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
611
    def fetch_refs(self, update_refs, lossy, overwrite=False):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
612
        if lossy:
613
            raise LossyPushToSameVCS(self.source, self.target)
614
        old_refs = self.target.controldir.get_refs_container()
615
        ref_changes = {}
616
        def determine_wants(heads):
617
            old_refs = dict([(k, (v, None)) for (k, v) in heads.as_dict().iteritems()])
618
            new_refs = update_refs(old_refs)
619
            ref_changes.update(new_refs)
620
            return [sha1 for (sha1, bzr_revid) in new_refs.itervalues()]
621
        self.fetch_objects(determine_wants, lossy=lossy)
622
        for k, (git_sha, bzr_revid) in ref_changes.iteritems():
623
            self.target._git.refs[k] = git_sha
624
        new_refs = self.target.controldir.get_refs_container()
625
        return None, old_refs, new_refs
626
627
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
628
        raise NotImplementedError(self.fetch_objects)
629
630
    def _target_has_shas(self, shas):
631
        return set([sha for sha in shas if sha in self.target._git.object_store])
632
633
    def fetch(self, revision_id=None, find_ghosts=False,
634
              mapping=None, fetch_spec=None, branches=None, limit=None, include_tags=False):
635
        if mapping is None:
636
            mapping = self.source.get_mapping()
637
        if revision_id is not None:
638
            args = [revision_id]
639
        elif fetch_spec is not None:
640
            recipe = fetch_spec.get_recipe()
641
            if recipe[0] in ("search", "proxy-search"):
642
                heads = recipe[1]
643
            else:
644
                raise AssertionError(
645
                    "Unsupported search result type %s" % recipe[0])
646
            args = heads
647
        if branches is not None:
648
            def determine_wants(refs):
649
                ret = []
650
                for name, value in refs.iteritems():
651
                    if value == ZERO_SHA:
652
                        continue
653
654
                    if name in branches or (include_tags and is_tag(name)):
655
                        ret.append(value)
656
                return ret
657
        elif fetch_spec is None and revision_id is None:
658
            determine_wants = self.determine_wants_all
659
        else:
660
            determine_wants = self.get_determine_wants_revids(args, include_tags=include_tags)
661
        wants_recorder = DetermineWantsRecorder(determine_wants)
662
        self.fetch_objects(wants_recorder, mapping, limit=limit)
663
        return wants_recorder.remote_refs
664
665
    def get_determine_wants_revids(self, revids, include_tags=False):
666
        wants = set()
667
        for revid in set(revids):
668
            if revid == NULL_REVISION:
669
                continue
670
            git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
671
            wants.add(git_sha)
672
        return self.get_determine_wants_heads(wants, include_tags=include_tags)
673
674
    def determine_wants_all(self, refs):
675
        potential = set([v for v in refs.values() if not v == ZERO_SHA])
676
        return list(potential - self._target_has_shas(potential))
677
678
679
class InterLocalGitLocalGitRepository(InterGitGitRepository):
680
681
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
682
        if lossy:
683
            raise LossyPushToSameVCS(self.source, self.target)
684
        if limit is not None:
685
            raise FetchLimitUnsupported(self)
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
686
        refs = self.source._git.fetch(self.target._git, determine_wants)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
687
        return (None, None, refs)
688
689
    @staticmethod
690
    def is_compatible(source, target):
691
        """Be compatible with GitRepository."""
692
        return (isinstance(source, LocalGitRepository) and
693
                isinstance(target, LocalGitRepository))
694
695
696
class InterRemoteGitLocalGitRepository(InterGitGitRepository):
697
698
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
699
        if lossy:
700
            raise LossyPushToSameVCS(self.source, self.target)
701
        if limit is not None:
702
            raise FetchLimitUnsupported(self)
703
        graphwalker = self.target._git.get_graph_walker()
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
704
        if CAPABILITY_THIN_PACK in self.source.controldir._client._fetch_capabilities:
705
            # TODO(jelmer): Avoid reading entire file into memory and
706
            # only processing it after the whole file has been fetched.
707
            f = BytesIO()
708
709
            def commit():
710
                if f.tell():
711
                    f.seek(0)
712
                    self.target._git.object_store.move_in_thin_pack(f)
713
714
            def abort():
715
                pass
716
        else:
717
            f, commit, abort = self.target._git.object_store.add_pack()
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
718
        try:
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
719
            refs = self.source.controldir.fetch_pack(
720
                determine_wants, graphwalker, f.write)
721
            commit()
722
            return (None, None, refs)
723
        except BaseException:
724
            abort()
725
            raise
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
726
727
    @staticmethod
728
    def is_compatible(source, target):
729
        """Be compatible with GitRepository."""
730
        return (isinstance(source, RemoteGitRepository) and
731
                isinstance(target, LocalGitRepository))