/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):
7143.12.1 by Jelmer Vernooij
Support cloning revisions referenced only by an annotated tag.
401
            unpeel_lookup = {}
402
            for k, v in viewitems(refs):
403
                if k.endswith(ANNOTATED_TAG_SUFFIX):
404
                    unpeel_lookup[v] = refs[k[:-len(ANNOTATED_TAG_SUFFIX)]]
405
            potential = set([unpeel_lookup.get(w, w) for w in wants])
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
406
            if include_tags:
7143.12.1 by Jelmer Vernooij
Support cloning revisions referenced only by an annotated tag.
407
                for k, sha in viewitems(refs):
7103.2.1 by Jelmer Vernooij
Don't attempt to retrieve peeled SHAs.
408
                    if k.endswith(ANNOTATED_TAG_SUFFIX):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
409
                        continue
410
                    if not is_tag(k):
411
                        continue
7143.12.1 by Jelmer Vernooij
Support cloning revisions referenced only by an annotated tag.
412
                    if sha == ZERO_SHA:
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
413
                        continue
7143.12.1 by Jelmer Vernooij
Support cloning revisions referenced only by an annotated tag.
414
                    potential.add(sha)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
415
            return list(potential - self._target_has_shas(potential))
416
        return determine_wants
417
418
    def determine_wants_all(self, refs):
419
        raise NotImplementedError(self.determine_wants_all)
420
421
    @staticmethod
422
    def _get_repo_format_to_test():
423
        return None
424
425
    def copy_content(self, revision_id=None):
426
        """See InterRepository.copy_content."""
427
        self.fetch(revision_id, find_ghosts=False)
428
429
    def search_missing_revision_ids(self,
430
            find_ghosts=True, revision_ids=None, if_present_ids=None,
431
            limit=None):
432
        if limit is not None:
433
            raise FetchLimitUnsupported(self)
6989.2.7 by Jelmer Vernooij
hackishly fix search_missing_revision_ids.
434
        if revision_ids is None and if_present_ids is None:
6989.2.5 by Jelmer Vernooij
More test fixes.
435
            todo = set(self.source.all_revision_ids())
6989.2.7 by Jelmer Vernooij
hackishly fix search_missing_revision_ids.
436
        else:
437
            todo = set()
438
            if revision_ids is not None:
439
                for revid in revision_ids:
440
                    if not self.source.has_revision(revid):
441
                        raise NoSuchRevision(revid, self.source)
442
                todo.update(revision_ids)
443
            if if_present_ids is not None:
444
                todo.update(if_present_ids)
445
        result_set = todo.difference(self.target.all_revision_ids())
446
        result_parents = set(itertools.chain.from_iterable(viewvalues(
447
            self.source.get_graph().get_parent_map(result_set))))
448
        included_keys = result_set.intersection(result_parents)
449
        start_keys = result_set.difference(included_keys)
450
        exclude_keys = result_parents.difference(result_set)
451
        return GitSearchResult(start_keys, exclude_keys, result_set)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
452
453
454
class InterGitNonGitRepository(InterFromGitRepository):
455
    """Base InterRepository that copies revisions from a Git into a non-Git
456
    repository."""
457
458
    def _target_has_shas(self, shas):
459
        revids = {}
460
        for sha in shas:
461
            try:
462
                revid = self.source.lookup_foreign_revision_id(sha)
463
            except NotCommitError:
464
                # Commit is definitely not present
465
                continue
466
            else:
467
                revids[revid] = sha
468
        return set([revids[r] for r in self.target.has_revisions(revids)])
469
470
    def determine_wants_all(self, refs):
471
        potential = set()
7018.3.2 by Jelmer Vernooij
Fix some git tests.
472
        for k, v in viewitems(refs):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
473
            # For non-git target repositories, only worry about peeled
474
            if v == ZERO_SHA:
475
                continue
476
            potential.add(self.source.controldir.get_peeled(k) or v)
477
        return list(potential - self._target_has_shas(potential))
478
479
    def _warn_slow(self):
0.418.1 by Jelmer Vernooij
Support suppressing slow intervcs warnings.
480
        if not config.GlobalConfig().suppress_warning('slow_intervcs_push'):
481
            trace.warning(
482
                'Fetching from Git to Bazaar repository. '
483
                'For better performance, fetch into a Git repository.')
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
484
485
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
486
        """Fetch objects from a remote server.
487
488
        :param determine_wants: determine_wants callback
489
        :param mapping: BzrGitMapping to use
490
        :param limit: Maximum number of commits to import.
491
        :return: Tuple with pack hint, last imported revision id and remote refs
492
        """
493
        raise NotImplementedError(self.fetch_objects)
494
495
    def get_determine_wants_revids(self, revids, include_tags=False):
496
        wants = set()
497
        for revid in set(revids):
498
            if self.target.has_revision(revid):
499
                continue
500
            git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
501
            wants.add(git_sha)
502
        return self.get_determine_wants_heads(wants, include_tags=include_tags)
503
504
    def fetch(self, revision_id=None, find_ghosts=False,
505
              mapping=None, fetch_spec=None, include_tags=False):
506
        if mapping is None:
507
            mapping = self.source.get_mapping()
508
        if revision_id is not None:
509
            interesting_heads = [revision_id]
510
        elif fetch_spec is not None:
511
            recipe = fetch_spec.get_recipe()
512
            if recipe[0] in ("search", "proxy-search"):
513
                interesting_heads = recipe[1]
514
            else:
515
                raise AssertionError("Unsupported search result type %s" %
516
                        recipe[0])
517
        else:
518
            interesting_heads = None
519
520
        if interesting_heads is not None:
521
            determine_wants = self.get_determine_wants_revids(
522
                interesting_heads, include_tags=include_tags)
523
        else:
524
            determine_wants = self.determine_wants_all
525
526
        (pack_hint, _, remote_refs) = self.fetch_objects(determine_wants,
527
            mapping)
528
        if pack_hint is not None and self.target._format.pack_compresses:
529
            self.target.pack(hint=pack_hint)
530
        return remote_refs
531
532
533
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
534
    """InterRepository that copies revisions from a remote Git into a non-Git
535
    repository."""
536
537
    def get_target_heads(self):
538
        # FIXME: This should be more efficient
539
        all_revs = self.target.all_revision_ids()
540
        parent_map = self.target.get_parent_map(all_revs)
541
        all_parents = set()
7045.4.5 by Jelmer Vernooij
Don't use map as a way to avoid loops.
542
        for values in viewvalues(parent_map):
543
            all_parents.update(values)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
544
        return set(all_revs) - all_parents
545
546
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
547
        """See `InterGitNonGitRepository`."""
548
        self._warn_slow()
549
        store = get_object_store(self.target, mapping)
550
        with store.lock_write():
551
            heads = self.get_target_heads()
552
            graph_walker = ObjectStoreGraphWalker(
553
                [store._lookup_revision_sha1(head) for head in heads],
554
                lambda sha: store[sha].parents)
555
            wants_recorder = DetermineWantsRecorder(determine_wants)
556
557
            pb = ui.ui_factory.nested_progress_bar()
558
            try:
559
                objects_iter = self.source.fetch_objects(
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
560
                    wants_recorder, graph_walker, store.get_raw)
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
561
                trace.mutter("Importing %d new revisions",
562
                             len(wants_recorder.wants))
563
                (pack_hint, last_rev) = import_git_objects(self.target,
564
                    mapping, objects_iter, store, wants_recorder.wants, pb,
565
                    limit)
566
                return (pack_hint, last_rev, wants_recorder.remote_refs)
567
            finally:
568
                pb.finished()
569
570
    @staticmethod
571
    def is_compatible(source, target):
572
        """Be compatible with GitRepository."""
573
        if not isinstance(source, RemoteGitRepository):
574
            return False
575
        if not target.supports_rich_root():
576
            return False
577
        if isinstance(target, GitRepository):
578
            return False
579
        if not getattr(target._format, "supports_full_versioned_files", True):
580
            return False
581
        return True
582
583
584
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
585
    """InterRepository that copies revisions from a local Git into a non-Git
586
    repository."""
587
588
    def fetch_objects(self, determine_wants, mapping, limit=None, lossy=False):
589
        """See `InterGitNonGitRepository`."""
590
        self._warn_slow()
591
        remote_refs = self.source.controldir.get_refs_container().as_dict()
592
        wants = determine_wants(remote_refs)
593
        create_pb = None
594
        pb = ui.ui_factory.nested_progress_bar()
595
        target_git_object_retriever = get_object_store(self.target, mapping)
596
        try:
597
            target_git_object_retriever.lock_write()
598
            try:
599
                (pack_hint, last_rev) = import_git_objects(self.target,
600
                    mapping, self.source._git.object_store,
601
                    target_git_object_retriever, wants, pb, limit)
602
                return (pack_hint, last_rev, remote_refs)
603
            finally:
604
                target_git_object_retriever.unlock()
605
        finally:
606
            pb.finished()
607
608
    @staticmethod
609
    def is_compatible(source, target):
610
        """Be compatible with GitRepository."""
611
        if not isinstance(source, LocalGitRepository):
612
            return False
613
        if not target.supports_rich_root():
614
            return False
615
        if isinstance(target, GitRepository):
616
            return False
617
        if not getattr(target._format, "supports_full_versioned_files", True):
618
            return False
619
        return True
620
621
622
class InterGitGitRepository(InterFromGitRepository):
623
    """InterRepository that copies between Git repositories."""
624
0.404.5 by Jelmer Vernooij
Check for diverged branches during push.
625
    def fetch_refs(self, update_refs, lossy, overwrite=False):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
626
        if lossy:
627
            raise LossyPushToSameVCS(self.source, self.target)
628
        old_refs = self.target.controldir.get_refs_container()
629
        ref_changes = {}
630
        def determine_wants(heads):
7018.3.2 by Jelmer Vernooij
Fix some git tests.
631
            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.
632
            new_refs = update_refs(old_refs)
633
            ref_changes.update(new_refs)
7018.3.2 by Jelmer Vernooij
Fix some git tests.
634
            return [sha1 for (sha1, bzr_revid) in viewvalues(new_refs)]
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
635
        self.fetch_objects(determine_wants, lossy=lossy)
7018.3.2 by Jelmer Vernooij
Fix some git tests.
636
        for k, (git_sha, bzr_revid) in viewitems(ref_changes):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
637
            self.target._git.refs[k] = git_sha
638
        new_refs = self.target.controldir.get_refs_container()
639
        return None, old_refs, new_refs
640
641
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
642
        raise NotImplementedError(self.fetch_objects)
643
644
    def _target_has_shas(self, shas):
645
        return set([sha for sha in shas if sha in self.target._git.object_store])
646
647
    def fetch(self, revision_id=None, find_ghosts=False,
648
              mapping=None, fetch_spec=None, branches=None, limit=None, include_tags=False):
649
        if mapping is None:
650
            mapping = self.source.get_mapping()
651
        if revision_id is not None:
652
            args = [revision_id]
653
        elif fetch_spec is not None:
654
            recipe = fetch_spec.get_recipe()
655
            if recipe[0] in ("search", "proxy-search"):
656
                heads = recipe[1]
657
            else:
658
                raise AssertionError(
659
                    "Unsupported search result type %s" % recipe[0])
660
            args = heads
661
        if branches is not None:
662
            def determine_wants(refs):
663
                ret = []
7018.3.2 by Jelmer Vernooij
Fix some git tests.
664
                for name, value in viewitems(refs):
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
665
                    if value == ZERO_SHA:
666
                        continue
667
668
                    if name in branches or (include_tags and is_tag(name)):
669
                        ret.append(value)
670
                return ret
671
        elif fetch_spec is None and revision_id is None:
672
            determine_wants = self.determine_wants_all
673
        else:
674
            determine_wants = self.get_determine_wants_revids(args, include_tags=include_tags)
675
        wants_recorder = DetermineWantsRecorder(determine_wants)
676
        self.fetch_objects(wants_recorder, mapping, limit=limit)
677
        return wants_recorder.remote_refs
678
679
    def get_determine_wants_revids(self, revids, include_tags=False):
680
        wants = set()
681
        for revid in set(revids):
682
            if revid == NULL_REVISION:
683
                continue
684
            git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
685
            wants.add(git_sha)
686
        return self.get_determine_wants_heads(wants, include_tags=include_tags)
687
688
    def determine_wants_all(self, refs):
7103.2.1 by Jelmer Vernooij
Don't attempt to retrieve peeled SHAs.
689
        potential = set([
690
            v for k, v in refs.items()
691
            if not v == ZERO_SHA and not k.endswith(ANNOTATED_TAG_SUFFIX)])
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
692
        return list(potential - self._target_has_shas(potential))
693
694
695
class InterLocalGitLocalGitRepository(InterGitGitRepository):
696
697
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
698
        if lossy:
699
            raise LossyPushToSameVCS(self.source, self.target)
700
        if limit is not None:
701
            raise FetchLimitUnsupported(self)
7131.6.1 by Jelmer Vernooij
Add progress bar for local git->git fetches.
702
        from .remote import DefaultProgressReporter
703
        pb = ui.ui_factory.nested_progress_bar()
704
        progress = DefaultProgressReporter(pb).progress
705
        try:
706
            refs = self.source._git.fetch(
707
                    self.target._git, determine_wants,
708
                    progress=progress)
709
        finally:
710
            pb.finished()
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
711
        return (None, None, refs)
712
713
    @staticmethod
714
    def is_compatible(source, target):
715
        """Be compatible with GitRepository."""
716
        return (isinstance(source, LocalGitRepository) and
717
                isinstance(target, LocalGitRepository))
718
719
720
class InterRemoteGitLocalGitRepository(InterGitGitRepository):
721
722
    def fetch_objects(self, determine_wants, mapping=None, limit=None, lossy=False):
723
        if lossy:
724
            raise LossyPushToSameVCS(self.source, self.target)
725
        if limit is not None:
726
            raise FetchLimitUnsupported(self)
727
        graphwalker = self.target._git.get_graph_walker()
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
728
        if CAPABILITY_THIN_PACK in self.source.controldir._client._fetch_capabilities:
729
            # TODO(jelmer): Avoid reading entire file into memory and
730
            # only processing it after the whole file has been fetched.
731
            f = BytesIO()
732
733
            def commit():
734
                if f.tell():
735
                    f.seek(0)
736
                    self.target._git.object_store.move_in_thin_pack(f)
737
738
            def abort():
739
                pass
740
        else:
741
            f, commit, abort = self.target._git.object_store.add_pack()
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
742
        try:
0.405.1 by Jelmer Vernooij
Use same logic for interpreting progress reports everywhere.
743
            refs = self.source.controldir.fetch_pack(
744
                determine_wants, graphwalker, f.write)
745
            commit()
746
            return (None, None, refs)
747
        except BaseException:
748
            abort()
749
            raise
0.401.2 by Jelmer Vernooij
Move all InterRepository implementations into interrepo.
750
751
    @staticmethod
752
    def is_compatible(source, target):
753
        """Be compatible with GitRepository."""
754
        return (isinstance(source, RemoteGitRepository) and
755
                isinstance(target, LocalGitRepository))