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