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