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