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