14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from bzrlib import osutils
18
from bzrlib.errors import InvalidRevisionId
19
from bzrlib.inventory import Inventory
20
from bzrlib.repository import InterRepository
21
from bzrlib.trace import info
23
from bzrlib.plugins.git import git
24
from bzrlib.plugins.git.repository import LocalGitRepository, GitRepository, GitFormat
25
from bzrlib.plugins.git.remote import RemoteGitRepository
27
from dulwich.objects import Commit
29
from cStringIO import StringIO
32
class BzrFetchGraphWalker(object):
34
def __init__(self, repository, mapping):
35
self.repository = repository
36
self.mapping = mapping
38
self.heads = set(repository.all_revision_ids())
42
revid = self.mapping.revision_id_foreign_to_bzr(sha)
45
def remove(self, revid):
48
self.heads.remove(revid)
49
if revid in self.parents:
50
for p in self.parents[revid]:
55
ret = self.heads.pop()
56
ps = self.repository.get_parent_map([ret])[ret]
57
self.parents[ret] = ps
58
self.heads.update([p for p in ps if not p in self.done])
61
return self.mapping.revision_id_bzr_to_foreign(ret)
62
except InvalidRevisionId:
67
def import_git_blob(repo, mapping, path, blob):
17
from dulwich.objects import (
24
from dulwich.object_store import (
27
from itertools import (
40
from bzrlib.errors import (
44
from bzrlib.inventory import (
51
from bzrlib.repository import (
54
from bzrlib.revision import (
58
from bzrlib.revisiontree import InventoryRevisionTree
59
except ImportError: # bzr < 2.4
60
from bzrlib.revisiontree import RevisionTree as InventoryRevisionTree
61
from bzrlib.testament import (
64
from bzrlib.tsort import (
67
from bzrlib.versionedfile import (
68
ChunkedContentFactory,
71
from bzrlib.plugins.git.mapping import (
77
from bzrlib.plugins.git.object_store import (
82
from bzrlib.plugins.git.refs import extract_tags
83
from bzrlib.plugins.git.remote import (
86
from bzrlib.plugins.git.repository import (
93
def import_git_blob(texts, mapping, path, name, (base_hexsha, hexsha),
94
base_inv, parent_id, revision_id,
95
parent_invs, lookup_object, (base_mode, mode), store_updater,
68
97
"""Import a git blob object into a bzr repository.
70
:param repo: bzr repository
99
:param texts: VersionedFiles to add to
71
100
:param path: Path in the tree
72
101
:param blob: A git blob
74
file_id = mapping.generate_file_id(path)
75
repo.texts.add_lines((file_id, blob.id),
77
osutils.split_lines(blob.data))
78
inv.add_path(path, "file", file_id)
81
def import_git_tree(repo, mapping, path, tree, inv, lookup_object):
102
:return: Inventory delta for this file
104
if mapping.is_control_file(path):
106
if base_hexsha == hexsha and base_mode == mode:
107
# If nothing has changed since the base revision, we're done
109
file_id = lookup_file_id(path)
110
if stat.S_ISLNK(mode):
114
ie = cls(file_id, name.decode("utf-8"), parent_id)
115
if ie.kind == "file":
116
ie.executable = mode_is_executable(mode)
117
if base_hexsha == hexsha and mode_kind(base_mode) == mode_kind(mode):
118
base_ie = base_inv[base_inv.path2id(path)]
119
ie.text_size = base_ie.text_size
120
ie.text_sha1 = base_ie.text_sha1
121
if ie.kind == "symlink":
122
ie.symlink_target = base_ie.symlink_target
123
if ie.executable == base_ie.executable:
124
ie.revision = base_ie.revision
126
blob = lookup_object(hexsha)
128
blob = lookup_object(hexsha)
129
if ie.kind == "symlink":
131
ie.symlink_target = blob.data
133
ie.text_size = sum(imap(len, blob.chunked))
134
ie.text_sha1 = osutils.sha_strings(blob.chunked)
135
# Check what revision we should store
137
for pinv in parent_invs:
142
if (pie.text_sha1 == ie.text_sha1 and
143
pie.executable == ie.executable and
144
pie.symlink_target == ie.symlink_target):
145
# found a revision in one of the parents to use
146
ie.revision = pie.revision
148
parent_key = (file_id, pie.revision)
149
if not parent_key in parent_keys:
150
parent_keys.append(parent_key)
151
if ie.revision is None:
152
# Need to store a new revision
153
ie.revision = revision_id
154
assert ie.revision is not None
155
if ie.kind == 'symlink':
158
chunks = blob.chunked
159
texts.insert_record_stream([
160
ChunkedContentFactory((file_id, ie.revision),
161
tuple(parent_keys), ie.text_sha1, chunks)])
163
if base_hexsha is not None:
164
old_path = path.decode("utf-8") # Renames are not supported yet
165
if stat.S_ISDIR(base_mode):
166
invdelta.extend(remove_disappeared_children(base_inv, old_path,
167
lookup_object(base_hexsha), [], lookup_object))
170
new_path = path.decode("utf-8")
171
invdelta.append((old_path, new_path, file_id, ie))
172
if base_hexsha != hexsha:
173
store_updater.add_object(blob, ie, path)
177
class SubmodulesRequireSubtrees(BzrError):
178
_fmt = """The repository you are fetching from contains submodules. To continue, upgrade your Bazaar repository to a format that supports nested trees, such as 'development-subtree'."""
182
def import_git_submodule(texts, mapping, path, name, (base_hexsha, hexsha),
183
base_inv, parent_id, revision_id, parent_invs, lookup_object,
184
(base_mode, mode), store_updater, lookup_file_id):
185
if base_hexsha == hexsha and base_mode == mode:
187
file_id = lookup_file_id(path)
188
ie = TreeReference(file_id, name.decode("utf-8"), parent_id)
189
ie.revision = revision_id
190
if base_hexsha is None:
194
ie.reference_revision = mapping.revision_id_foreign_to_bzr(hexsha)
195
texts.insert_record_stream([
196
ChunkedContentFactory((file_id, ie.revision), (), None, [])])
197
invdelta = [(oldpath, path, file_id, ie)]
201
def remove_disappeared_children(base_inv, path, base_tree, existing_children,
203
"""Generate an inventory delta for removed children.
205
:param base_inv: Base inventory against which to generate the
207
:param path: Path to process (unicode)
208
:param base_tree: Git Tree base object
209
:param existing_children: Children that still exist
210
:param lookup_object: Lookup a git object by its SHA1
211
:return: Inventory delta, as list
213
assert type(path) is unicode
215
for name, mode, hexsha in base_tree.iteritems():
216
if name in existing_children:
218
c_path = posixpath.join(path, name.decode("utf-8"))
219
file_id = base_inv.path2id(c_path)
220
assert file_id is not None
221
ret.append((c_path, None, file_id, None))
222
if stat.S_ISDIR(mode):
223
ret.extend(remove_disappeared_children(
224
base_inv, c_path, lookup_object(hexsha), [], lookup_object))
228
def import_git_tree(texts, mapping, path, name, (base_hexsha, hexsha),
229
base_inv, parent_id, revision_id, parent_invs,
230
lookup_object, (base_mode, mode), store_updater,
231
lookup_file_id, allow_submodules=False):
82
232
"""Import a git tree object into a bzr repository.
84
:param repo: A Bzr repository object
85
:param path: Path in the tree
234
:param texts: VersionedFiles object to add to
235
:param path: Path in the tree (str)
236
:param name: Name of the tree (str)
86
237
:param tree: A git tree object
87
:param inv: Inventory object
238
:param base_inv: Base inventory against which to return inventory delta
239
:return: Inventory delta for this subtree
89
file_id = mapping.generate_file_id(path)
90
repo.texts.add_lines((file_id, tree.id),
93
inv.add_path(path, "directory", file_id)
94
for mode, name, hexsha in tree.entries():
95
entry_kind = (mode & 0700000) / 0100000
96
basename = name.decode("utf-8")
100
child_path = urlutils.join(path, name)
102
import_git_tree(repo, mapping, child_path, lookup_object, inv)
103
elif entry_kind == 1:
104
import_git_blob(repo, mapping, child_path, lookup_object, inv)
106
raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
109
def import_git_objects(repo, mapping, object_iter):
241
assert type(path) is str
242
assert type(name) is str
243
if base_hexsha == hexsha and base_mode == mode:
244
# If nothing has changed since the base revision, we're done
247
file_id = lookup_file_id(path)
248
# We just have to hope this is indeed utf-8:
249
ie = InventoryDirectory(file_id, name.decode("utf-8"), parent_id)
250
tree = lookup_object(hexsha)
251
if base_hexsha is None:
253
old_path = None # Newly appeared here
255
base_tree = lookup_object(base_hexsha)
256
old_path = path.decode("utf-8") # Renames aren't supported yet
257
new_path = path.decode("utf-8")
258
if base_tree is None or type(base_tree) is not Tree:
259
ie.revision = revision_id
260
invdelta.append((old_path, new_path, ie.file_id, ie))
261
texts.insert_record_stream([
262
ChunkedContentFactory((ie.file_id, ie.revision), (), None, [])])
263
# Remember for next time
264
existing_children = set()
266
for name, child_mode, child_hexsha in tree.iteritems():
267
existing_children.add(name)
268
child_path = posixpath.join(path, name)
269
if type(base_tree) is Tree:
271
child_base_mode, child_base_hexsha = base_tree[name]
273
child_base_hexsha = None
276
child_base_hexsha = None
278
if stat.S_ISDIR(child_mode):
279
subinvdelta, grandchildmodes = import_git_tree(texts, mapping,
280
child_path, name, (child_base_hexsha, child_hexsha), base_inv,
281
file_id, revision_id, parent_invs, lookup_object,
282
(child_base_mode, child_mode), store_updater, lookup_file_id,
283
allow_submodules=allow_submodules)
284
elif S_ISGITLINK(child_mode): # submodule
285
if not allow_submodules:
286
raise SubmodulesRequireSubtrees()
287
subinvdelta, grandchildmodes = import_git_submodule(texts, mapping,
288
child_path, name, (child_base_hexsha, child_hexsha), base_inv,
289
file_id, revision_id, parent_invs, lookup_object,
290
(child_base_mode, child_mode), store_updater, lookup_file_id)
292
subinvdelta = import_git_blob(texts, mapping, child_path, name,
293
(child_base_hexsha, child_hexsha), base_inv, file_id,
294
revision_id, parent_invs, lookup_object,
295
(child_base_mode, child_mode), store_updater, lookup_file_id)
297
child_modes.update(grandchildmodes)
298
invdelta.extend(subinvdelta)
299
if child_mode not in (stat.S_IFDIR, DEFAULT_FILE_MODE,
300
stat.S_IFLNK, DEFAULT_FILE_MODE|0111):
301
child_modes[child_path] = child_mode
302
# Remove any children that have disappeared
303
if base_tree is not None and type(base_tree) is Tree:
304
invdelta.extend(remove_disappeared_children(base_inv, old_path,
305
base_tree, existing_children, lookup_object))
306
store_updater.add_object(tree, ie, path)
307
return invdelta, child_modes
310
def verify_commit_reconstruction(target_git_object_retriever, lookup_object,
311
o, rev, ret_tree, parent_trees, mapping, unusual_modes, verifiers):
312
new_unusual_modes = mapping.export_unusual_file_modes(rev)
313
if new_unusual_modes != unusual_modes:
314
raise AssertionError("unusual modes don't match: %r != %r" % (
315
unusual_modes, new_unusual_modes))
316
# Verify that we can reconstruct the commit properly
317
rec_o = target_git_object_retriever._reconstruct_commit(rev, o.tree, True,
320
raise AssertionError("Reconstructed commit differs: %r != %r" % (
324
for path, obj, ie in _tree_to_objects(ret_tree, parent_trees,
325
target_git_object_retriever._cache.idmap, unusual_modes, mapping.BZR_DUMMY_FILE):
326
old_obj_id = tree_lookup_path(lookup_object, o.tree, path)[1]
328
if obj.id != old_obj_id:
329
diff.append((path, lookup_object(old_obj_id), obj))
330
for (path, old_obj, new_obj) in diff:
331
while (old_obj.type_name == "tree" and
332
new_obj.type_name == "tree" and
333
sorted(old_obj) == sorted(new_obj)):
335
if old_obj[name][0] != new_obj[name][0]:
336
raise AssertionError("Modes for %s differ: %o != %o" %
337
(path, old_obj[name][0], new_obj[name][0]))
338
if old_obj[name][1] != new_obj[name][1]:
339
# Found a differing child, delve deeper
340
path = posixpath.join(path, name)
341
old_obj = lookup_object(old_obj[name][1])
342
new_obj = new_objs[path]
344
raise AssertionError("objects differ for %s: %r != %r" % (path,
348
def import_git_commit(repo, mapping, head, lookup_object,
349
target_git_object_retriever, trees_cache):
350
o = lookup_object(head)
351
# Note that this uses mapping.revision_id_foreign_to_bzr. If the parents
352
# were bzr roundtripped revisions they would be specified in the
354
rev, roundtrip_revid, verifiers = mapping.import_commit(
355
o, mapping.revision_id_foreign_to_bzr)
356
# We have to do this here, since we have to walk the tree and
357
# we need to make sure to import the blobs / trees with the right
358
# path; this may involve adding them more than once.
359
parent_trees = trees_cache.revision_trees(rev.parent_ids)
360
if parent_trees == []:
361
base_inv = Inventory(root_id=None)
365
base_inv = parent_trees[0].inventory
366
base_tree = lookup_object(o.parents[0]).tree
367
base_mode = stat.S_IFDIR
368
store_updater = target_git_object_retriever._get_updater(rev)
369
fileid_map = mapping.get_fileid_map(lookup_object, o.tree)
370
inv_delta, unusual_modes = import_git_tree(repo.texts,
371
mapping, "", "", (base_tree, o.tree), base_inv,
372
None, rev.revision_id, [p.inventory for p in parent_trees],
373
lookup_object, (base_mode, stat.S_IFDIR), store_updater,
374
fileid_map.lookup_file_id,
375
allow_submodules=getattr(repo._format, "supports_tree_reference", False))
376
if unusual_modes != {}:
377
for path, mode in unusual_modes.iteritems():
378
warn_unusual_mode(rev.foreign_revid, path, mode)
379
mapping.import_unusual_file_modes(rev, unusual_modes)
381
basis_id = rev.parent_ids[0]
383
basis_id = NULL_REVISION
385
rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
386
inv_delta, rev.revision_id, rev.parent_ids, base_inv)
388
testament = StrictTestament3(rev, inv)
389
if roundtrip_revid is not None:
390
calculated_verifiers = { "testament3-sha1": testament.as_sha1() }
391
original_revid = rev.revision_id
392
rev.revision_id = roundtrip_revid
393
if calculated_verifiers != verifiers:
394
trace.mutter("Testament SHA1 %r for %r did not match %r.",
395
calculated_verifiers["testament3-sha1"],
396
rev.revision_id, verifiers["testament3-sha1"])
397
rev.revision_id = original_revid
399
calculated_verifiers = {}
400
store_updater.add_object(o, calculated_verifiers, None)
401
store_updater.finish()
402
ret_tree = InventoryRevisionTree(repo, inv, rev.revision_id)
403
trees_cache.add(ret_tree)
404
repo.add_revision(rev.revision_id, rev)
405
if "verify" in debug.debug_flags:
406
verify_commit_reconstruction(target_git_object_retriever,
407
lookup_object, o, rev, ret_tree, parent_trees, mapping,
408
unusual_modes, verifiers)
411
def import_git_objects(repo, mapping, object_iter,
412
target_git_object_retriever, heads, pb=None, limit=None):
110
413
"""Import a set of git objects into a bzr repository.
112
:param repo: Bazaar repository
415
:param repo: Target Bazaar repository
113
416
:param mapping: Mapping to use
114
417
:param object_iter: Iterator over Git objects.
418
:return: Tuple with pack hints and last imported revision id
116
# TODO: a more (memory-)efficient implementation of this
118
for o in object_iter:
420
def lookup_object(sha):
422
return object_iter[sha]
424
return target_git_object_retriever[sha]
427
heads = list(set(heads))
428
trees_cache = LRUTreeCache(repo)
121
429
# Find and convert commit objects
122
for o in objects.iterkeys():
432
pb.update("finding revisions to fetch", len(graph), None)
434
assert isinstance(head, str)
436
o = lookup_object(head)
123
439
if isinstance(o, Commit):
124
rev = mapping.import_commit(o)
125
root_trees[rev] = objects[o.tree_sha]
440
rev, roundtrip_revid, verifiers = mapping.import_commit(o,
441
mapping.revision_id_foreign_to_bzr)
442
if (repo.has_revision(rev.revision_id) or
443
(roundtrip_revid and repo.has_revision(roundtrip_revid))):
445
graph.append((o.id, o.parents))
446
heads.extend([p for p in o.parents if p not in checked])
447
elif isinstance(o, Tag):
448
if o.object[1] not in checked:
449
heads.append(o.object[1])
451
trace.warning("Unable to import head object %r" % o)
454
# Order the revisions
126
455
# Create the inventory objects
127
for rev, root_tree in root_trees.iteritems():
128
# We have to do this here, since we have to walk the tree and
129
# we need to make sure to import the blobs / trees with the riht
130
# path; this may involve adding them more than once.
132
def lookup_object(sha):
135
return reconstruct_git_object(repo, mapping, sha)
136
import_git_tree(repo, mapping, "", tree, inv, lookup_object)
137
repo.add_revision(rev.revision_id, rev, inv)
140
def reconstruct_git_commit(repo, rev):
141
raise NotImplementedError(self.reconstruct_git_commit)
144
def reconstruct_git_object(repo, mapping, sha):
146
revid = mapping.revision_id_foreign_to_bzr(sha)
148
rev = repo.get_revision(revid)
149
except NoSuchRevision:
152
return reconstruct_git_commit(rev)
156
raise KeyError("No such object %s" % sha)
457
revision_ids = topo_sort(graph)
459
if limit is not None:
460
revision_ids = revision_ids[:limit]
462
for offset in range(0, len(revision_ids), batch_size):
463
target_git_object_retriever.start_write_group()
465
repo.start_write_group()
467
for i, head in enumerate(
468
revision_ids[offset:offset+batch_size]):
470
pb.update("fetching revisions", offset+i,
472
import_git_commit(repo, mapping, head, lookup_object,
473
target_git_object_retriever, trees_cache)
476
repo.abort_write_group()
479
hint = repo.commit_write_group()
481
pack_hints.extend(hint)
483
target_git_object_retriever.abort_write_group()
486
target_git_object_retriever.commit_write_group()
487
return pack_hints, last_imported
159
490
class InterGitRepository(InterRepository):
161
_matching_repo_format = GitFormat()
492
_matching_repo_format = GitRepositoryFormat()
494
def _target_has_shas(self, shas):
495
raise NotImplementedError(self._target_has_shas)
497
def get_determine_wants_heads(self, wants, include_tags=False):
499
def determine_wants(refs):
500
potential = set(wants)
502
potential.update([v[1] or v[0] for v in extract_tags(refs).itervalues()])
503
return list(potential - self._target_has_shas(potential))
504
return determine_wants
506
def determine_wants_all(self, refs):
507
potential = set([sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")])
508
return list(potential - self._target_has_shas(potential))
164
511
def _get_repo_format_to_test():
168
515
"""See InterRepository.copy_content."""
169
516
self.fetch(revision_id, pb, find_ghosts=False)
171
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
519
class InterGitNonGitRepository(InterGitRepository):
520
"""Base InterRepository that copies revisions from a Git into a non-Git
523
def _target_has_shas(self, shas):
524
revids = [self.source.lookup_foreign_revision_id(sha) for sha in shas]
525
return self.target.has_revisions(revids)
527
def get_determine_wants_revids(self, revids, include_tags=False):
529
for revid in set(revids):
530
git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
532
return self.get_determine_wants_heads(wants, include_tags=include_tags)
534
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
535
"""Fetch objects from a remote server.
537
:param determine_wants: determine_wants callback
538
:param mapping: BzrGitMapping to use
539
:param pb: Optional progress bar
540
:param limit: Maximum number of commits to import.
541
:return: Tuple with pack hint, last imported revision id and remote refs
543
raise NotImplementedError(self.fetch_objects)
545
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
546
mapping=None, fetch_spec=None):
173
547
if mapping is None:
174
548
mapping = self.source.get_mapping()
549
if revision_id is not None:
550
interesting_heads = [revision_id]
551
elif fetch_spec is not None:
552
recipe = fetch_spec.get_recipe()
553
if recipe[0] in ("search", "proxy-search"):
554
interesting_heads = recipe[1]
556
raise AssertionError("Unsupported search result type %s" % recipe[0])
558
interesting_heads = None
560
if interesting_heads is not None:
561
determine_wants = self.get_determine_wants_revids(interesting_heads,
564
determine_wants = self.determine_wants_all
566
(pack_hint, _, remote_refs) = self.fetch_objects(determine_wants,
568
if pack_hint is not None and self.target._format.pack_compresses:
569
self.target.pack(hint=pack_hint)
570
assert isinstance(remote_refs, dict)
574
_GIT_PROGRESS_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
575
def report_git_progress(pb, text):
576
text = text.rstrip("\r\n")
577
g = _GIT_PROGRESS_RE.match(text)
579
(text, pct, current, total) = g.groups()
580
pb.update(text, int(current), int(total))
582
pb.update(text, 0, 0)
585
class DetermineWantsRecorder(object):
587
def __init__(self, actual):
590
self.remote_refs = {}
592
def __call__(self, refs):
593
self.remote_refs = refs
594
self.wants = self.actual(refs)
598
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
599
"""InterRepository that copies revisions from a remote Git into a non-Git
602
def get_target_heads(self):
603
# FIXME: This should be more efficient
604
all_revs = self.target.all_revision_ids()
605
parent_map = self.target.get_parent_map(all_revs)
607
map(all_parents.update, parent_map.itervalues())
608
return set(all_revs) - all_parents
610
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
611
"""See `InterGitNonGitRepository`."""
175
612
def progress(text):
177
pb.note("git: %s" % text)
179
info("git: %s" % text)
180
def determine_wants(heads):
181
if revision_id is None:
184
ret = [mapping.revision_id_bzr_to_foreign(revision_id)]
185
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
186
graph_walker = BzrFetchGraphWalker(self.target, mapping)
613
report_git_progress(pb, text)
614
store = BazaarObjectStore(self.target, mapping)
187
615
self.target.lock_write()
189
import_git_objects(self.target, mapping,
190
self.source.fetch_objects(determine_wants, graph_walker,
617
heads = self.get_target_heads()
618
graph_walker = store.get_graph_walker(
619
[store._lookup_revision_sha1(head) for head in heads])
620
wants_recorder = DetermineWantsRecorder(determine_wants)
624
create_pb = pb = ui.ui_factory.nested_progress_bar()
626
objects_iter = self.source.fetch_objects(
627
wants_recorder, graph_walker, store.get_raw,
629
trace.mutter("Importing %d new revisions", len(wants_recorder.wants))
630
(pack_hint, last_rev) = import_git_objects(self.target, mapping,
631
objects_iter, store, wants_recorder.wants, pb, limit)
632
return (pack_hint, last_rev, wants_recorder.remote_refs)
193
637
self.target.unlock()
196
640
def is_compatible(source, target):
197
641
"""Be compatible with GitRepository."""
198
# FIXME: Also check target uses VersionedFile
199
return (isinstance(source, LocalGitRepository) and
200
target.supports_rich_root())
642
return (isinstance(source, RemoteGitRepository) and
643
target.supports_rich_root() and
644
not isinstance(target, GitRepository) and
645
target.texts is not None)
648
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
649
"""InterRepository that copies revisions from a local Git into a non-Git
652
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
653
"""See `InterGitNonGitRepository`."""
654
remote_refs = self.source._git.get_refs()
655
wants = determine_wants(remote_refs)
658
create_pb = pb = ui.ui_factory.nested_progress_bar()
659
target_git_object_retriever = BazaarObjectStore(self.target, mapping)
661
self.target.lock_write()
663
(pack_hint, last_rev) = import_git_objects(self.target, mapping,
664
self.source._git.object_store,
665
target_git_object_retriever, wants, pb, limit)
666
return (pack_hint, last_rev, remote_refs)
674
def is_compatible(source, target):
675
"""Be compatible with GitRepository."""
676
return (isinstance(source, LocalGitRepository) and
677
target.supports_rich_root() and
678
not isinstance(target, GitRepository) and
679
target.texts is not None)
682
class InterGitGitRepository(InterGitRepository):
683
"""InterRepository that copies between Git repositories."""
685
def fetch_objects(self, determine_wants, mapping, pb=None):
687
trace.note("git: %s", text)
688
graphwalker = self.target._git.get_graph_walker()
689
if (isinstance(self.source, LocalGitRepository) and
690
isinstance(self.target, LocalGitRepository)):
691
refs = self.source._git.fetch(self.target._git, determine_wants,
693
return (None, None, refs)
694
elif (isinstance(self.source, LocalGitRepository) and
695
isinstance(self.target, RemoteGitRepository)):
696
raise NotImplementedError
697
elif (isinstance(self.source, RemoteGitRepository) and
698
isinstance(self.target, LocalGitRepository)):
699
f, commit = self.target._git.object_store.add_thin_pack()
701
refs = self.source.bzrdir.root_transport.fetch_pack(
702
determine_wants, graphwalker, f.write, progress)
704
return (None, None, refs)
711
def _target_has_shas(self, shas):
712
return set([sha for sha in shas if self.target._git.object_store])
714
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
715
mapping=None, fetch_spec=None, branches=None):
717
mapping = self.source.get_mapping()
719
if revision_id is not None:
720
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
721
elif fetch_spec is not None:
722
recipe = fetch_spec.get_recipe()
723
if recipe[0] in ("search", "proxy-search"):
726
raise AssertionError("Unsupported search result type %s" % recipe[0])
727
args = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in heads]
728
if branches is not None:
729
determine_wants = lambda x: [x[y] for y in branches if not x[y] in r.object_store and x[y] != ZERO_SHA]
730
elif fetch_spec is None and revision_id is None:
731
determine_wants = self.determine_wants_all
733
determine_wants = lambda x: [y for y in args if not y in r.object_store and y != ZERO_SHA]
734
wants_recorder = DetermineWantsRecorder(determine_wants)
735
self.fetch_objects(wants_recorder, mapping)
736
return wants_recorder.remote_refs
739
def is_compatible(source, target):
740
"""Be compatible with GitRepository."""
741
return (isinstance(source, GitRepository) and
742
isinstance(target, GitRepository))
744
def get_determine_wants_revids(self, revids, include_tags=False):
746
for revid in set(revids):
747
git_sha, mapping = self.source.lookup_bzr_revision_id(revid)
749
return self.get_determine_wants_heads(wants, include_tags=include_tags)