/brz/remove-bazaar

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