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