63
108
self.heads.update([p for p in ps if not p in self.done])
65
110
self.done.add(ret)
66
return self.mapping.revision_id_bzr_to_foreign(ret)
111
return self.mapping.revision_id_bzr_to_foreign(ret)[0]
67
112
except InvalidRevisionId:
72
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
117
def import_git_blob(texts, mapping, path, hexsha, base_inv, parent_id,
118
revision_id, parent_invs, shagitmap, lookup_object, executable, symlink):
73
119
"""Import a git blob object into a bzr repository.
75
:param repo: bzr repository
121
:param texts: VersionedFiles to add to
76
122
:param path: Path in the tree
77
123
:param blob: A git blob
124
:return: Inventory delta for this file
79
126
file_id = mapping.generate_file_id(path)
80
text_revision = inv.revision_id
81
repo.texts.add_lines((file_id, text_revision),
82
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
83
osutils.split_lines(blob.data))
84
ie = inv.add_path(path, "file", file_id)
85
ie.revision = text_revision
86
ie.text_size = len(blob.data)
87
ie.text_sha1 = osutils.sha_string(blob.data)
131
# We just have to hope this is indeed utf-8:
132
ie = cls(file_id, urlutils.basename(path).decode("utf-8"),
88
134
ie.executable = executable
91
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
135
# See if this has changed at all
137
base_sha = shagitmap.lookup_blob(file_id, base_inv.revision_id)
141
if (base_sha == hexsha and base_inv[file_id].executable == ie.executable
142
and base_inv[file_id].kind == ie.kind):
143
# If nothing has changed since the base revision, we're done
145
if base_sha == hexsha:
146
ie.text_size = base_inv[file_id].text_size
147
ie.text_sha1 = base_inv[file_id].text_sha1
148
ie.symlink_target = base_inv[file_id].symlink_target
149
ie.revision = base_inv[file_id].revision
151
blob = lookup_object(hexsha)
152
if ie.kind == "symlink":
153
ie.symlink_target = blob.data
157
ie.text_size = len(blob.data)
158
ie.text_sha1 = osutils.sha_string(blob.data)
159
# Check what revision we should store
161
for pinv in parent_invs:
162
if not file_id in pinv:
164
if pinv[file_id].text_sha1 == ie.text_sha1:
165
# found a revision in one of the parents to use
166
ie.revision = pinv[file_id].revision
168
parent_keys.append((file_id, pinv[file_id].revision))
169
if ie.revision is None:
170
# Need to store a new revision
171
ie.revision = revision_id
172
assert file_id is not None
173
assert ie.revision is not None
174
texts.add_lines((file_id, ie.revision), parent_keys,
175
osutils.split_lines(blob.data))
176
if "verify" in debug.debug_flags:
177
assert text_to_blob(blob.data).id == hexsha
178
shagitmap.add_entry(hexsha, "blob", (ie.file_id, ie.revision))
179
if file_id in base_inv:
180
old_path = base_inv.id2path(file_id)
183
return [(old_path, path, file_id, ie)]
186
def import_git_tree(texts, mapping, path, hexsha, base_inv, parent_id,
187
revision_id, parent_invs, shagitmap, lookup_object):
92
188
"""Import a git tree object into a bzr repository.
94
:param repo: A Bzr repository object
190
:param texts: VersionedFiles object to add to
95
191
:param path: Path in the tree
96
192
:param tree: A git tree object
97
:param inv: Inventory object
193
:param base_inv: Base inventory against which to return inventory delta
194
:return: Inventory delta for this subtree
99
197
file_id = mapping.generate_file_id(path)
100
text_revision = inv.revision_id
101
repo.texts.add_lines((file_id, text_revision),
102
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
104
ie = inv.add_path(path, "directory", file_id)
105
ie.revision = text_revision
198
# We just have to hope this is indeed utf-8:
199
ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")),
201
if not file_id in base_inv:
202
# Newly appeared here
203
ie.revision = revision_id
204
texts.add_lines((file_id, ie.revision), [], [])
205
invdelta.append((None, path, file_id, ie))
207
# See if this has changed at all
209
base_sha = shagitmap.lookup_tree(file_id, base_inv.revision_id)
213
if base_sha == hexsha:
214
# If nothing has changed since the base revision, we're done
216
# Remember for next time
217
existing_children = set()
218
if "verify" in debug.debug_flags:
221
shagitmap.add_entry(hexsha, "tree", (file_id, revision_id))
223
tree = lookup_object(hexsha)
106
224
for mode, name, hexsha in tree.entries():
107
225
entry_kind = (mode & 0700000) / 0100000
108
226
basename = name.decode("utf-8")
112
child_path = urlutils.join(path, name)
227
existing_children.add(basename)
228
child_path = osutils.pathjoin(path, name)
113
229
if entry_kind == 0:
114
tree = lookup_object(hexsha)
115
import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
230
if mode != DEFAULT_TREE_MODE:
231
child_modes[child_path] = mode
232
subinvdelta, grandchildmodes = import_git_tree(texts, mapping, child_path, hexsha,
233
base_inv, file_id, revision_id, parent_invs, shagitmap,
235
invdelta.extend(subinvdelta)
236
child_modes.update(grandchildmodes)
116
237
elif entry_kind == 1:
117
blob = lookup_object(hexsha)
118
238
fs_mode = mode & 0777
119
import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
239
file_kind = (mode & 070000) / 010000
240
if file_kind == 0: # regular file
242
if mode != DEFAULT_FILE_MODE:
243
child_modes[child_path] = mode
246
if mode not in (DEFAULT_SYMLINK_MODE, DEFAULT_TREE_MODE | 0111):
247
child_modes[child_path] = mode
249
raise AssertionError("Unknown file kind, mode=%r" % (mode,))
250
subinvdelta = import_git_blob(texts, mapping, child_path, hexsha,
251
base_inv, file_id, revision_id, parent_invs, shagitmap,
252
lookup_object, bool(fs_mode & 0111), symlink))
253
invdelta.extend(subinvdelta)
121
raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
124
def import_git_objects(repo, mapping, object_iter, pb=None):
255
raise AssertionError("Unknown object kind, perms=%r." % (mode,))
256
# Remove any children that have disappeared
257
if file_id in base_inv:
258
deletable = [v for k,v in base_inv[file_id].children.iteritems() if k not in existing_children]
261
invdelta.append((base_inv.id2path(ie.file_id), None, ie.file_id, None))
262
if ie.kind == "directory":
263
deletable.extend(ie.children.values())
264
return invdelta, child_modes
267
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
125
269
"""Import a set of git objects into a bzr repository.
127
271
:param repo: Bazaar repository
129
273
:param object_iter: Iterator over Git objects.
131
275
# TODO: a more (memory-)efficient implementation of this
133
for i, o in enumerate(object_iter):
135
pb.update("fetching objects", i)
281
parent_invs_cache = LRUCache(50)
140
282
# Find and convert commit objects
141
for o in objects.itervalues():
285
pb.update("finding revisions to fetch", len(graph), None)
287
assert isinstance(head, str)
289
o = object_iter[head]
142
292
if isinstance(o, Commit):
143
293
rev = mapping.import_commit(o)
144
root_trees[rev.revision_id] = objects[o.tree]
294
if repo.has_revision(rev.revision_id):
296
root_trees[rev.revision_id] = o.tree
145
297
revisions[rev.revision_id] = rev
146
298
graph.append((rev.revision_id, rev.parent_ids))
299
target_git_object_retriever._idmap.add_entry(o.sha().hexdigest(),
300
"commit", (rev.revision_id, o._tree))
301
heads.extend([p for p in o.parents if p not in checked])
302
elif isinstance(o, Tag):
303
heads.append(o.object[1])
305
trace.warning("Unable to import head object %r" % o)
147
307
# Order the revisions
148
308
# Create the inventory objects
149
309
for i, revid in enumerate(topo_sort(graph)):
150
310
if pb is not None:
151
311
pb.update("fetching revisions", i, len(graph))
152
root_tree = root_trees[revid]
153
312
rev = revisions[revid]
154
313
# We have to do this here, since we have to walk the tree and
155
# we need to make sure to import the blobs / trees with the riht
314
# we need to make sure to import the blobs / trees with the right
156
315
# path; this may involve adding them more than once.
158
inv.revision_id = rev.revision_id
159
316
def lookup_object(sha):
162
return reconstruct_git_object(repo, mapping, sha)
163
parent_invs = [repo.get_inventory(r) for r in rev.parent_ids]
164
import_git_tree(repo, mapping, "", root_tree, inv, parent_invs, lookup_object)
165
repo.add_revision(rev.revision_id, rev, inv)
168
def reconstruct_git_commit(repo, rev):
169
raise NotImplementedError(self.reconstruct_git_commit)
172
def reconstruct_git_object(repo, mapping, sha):
174
revid = mapping.revision_id_foreign_to_bzr(sha)
176
rev = repo.get_revision(revid)
177
except NoSuchRevision:
180
return reconstruct_git_commit(rev)
184
raise KeyError("No such object %s" % sha)
318
return object_iter[sha]
320
return target_git_object_retriever[sha]
322
for parent_id in rev.parent_ids:
324
parent_invs.append(parent_invs_cache[parent_id])
326
parent_inv = repo.get_inventory(parent_id)
327
parent_invs.append(parent_inv)
328
parent_invs_cache[parent_id] = parent_inv
329
if parent_invs == []:
330
base_inv = Inventory(root_id=None)
332
base_inv = parent_invs[0]
333
inv_delta = import_git_tree(repo.texts, mapping, "",
334
root_trees[revid], base_inv, None, revid, parent_invs,
335
target_git_object_retriever._idmap, lookup_object)
337
basis_id = rev.parent_ids[0]
339
basis_id = NULL_REVISION
340
rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
341
inv_delta, rev.revision_id, rev.parent_ids)
342
parent_invs_cache[rev.revision_id] = inv
343
repo.add_revision(rev.revision_id, rev)
344
target_git_object_retriever._idmap.commit()
347
class InterGitNonGitRepository(InterRepository):
348
"""Base InterRepository that copies revisions from a Git into a non-Git
351
_matching_repo_format = GitRepositoryFormat()
354
def _get_repo_format_to_test():
357
def copy_content(self, revision_id=None, pb=None):
358
"""See InterRepository.copy_content."""
359
self.fetch(revision_id, pb, find_ghosts=False)
361
def fetch(self, revision_id=None, pb=None, find_ghosts=False, mapping=None,
363
self.fetch_refs(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts,
364
mapping=mapping, fetch_spec=fetch_spec)
366
def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
367
mapping=None, fetch_spec=None):
369
mapping = self.source.get_mapping()
370
if revision_id is not None:
371
interesting_heads = [revision_id]
372
elif fetch_spec is not None:
373
interesting_heads = fetch_spec.heads
375
interesting_heads = None
377
def determine_wants(refs):
379
if interesting_heads is None:
380
ret = [sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")]
382
ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads if revid != NULL_REVISION]
383
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
384
self.fetch_objects(determine_wants, mapping, pb)
389
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
390
"""InterRepository that copies revisions from a remote Git into a non-Git
393
def fetch_objects(self, determine_wants, mapping, pb=None):
395
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
396
graph_walker = BzrFetchGraphWalker(self.target, mapping)
399
create_pb = pb = ui.ui_factory.nested_progress_bar()
400
target_git_object_retriever = BazaarObjectStore(self.target, mapping)
403
def record_determine_wants(heads):
404
wants = determine_wants(heads)
405
recorded_wants.extend(wants)
409
self.target.lock_write()
411
self.target.start_write_group()
413
objects_iter = self.source.fetch_objects(
414
record_determine_wants,
416
target_git_object_retriever.get_raw,
418
import_git_objects(self.target, mapping, objects_iter,
419
target_git_object_retriever, recorded_wants, pb)
421
self.target.commit_write_group()
429
def is_compatible(source, target):
430
"""Be compatible with GitRepository."""
431
# FIXME: Also check target uses VersionedFile
432
return (isinstance(source, RemoteGitRepository) and
433
target.supports_rich_root() and
434
not isinstance(target, GitRepository))
437
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
438
"""InterRepository that copies revisions from a remote Git into a non-Git
441
def fetch_objects(self, determine_wants, mapping, pb=None):
442
wants = determine_wants(self.source._git.get_refs())
445
create_pb = pb = ui.ui_factory.nested_progress_bar()
446
target_git_object_retriever = BazaarObjectStore(self.target, mapping)
448
self.target.lock_write()
450
self.target.start_write_group()
452
import_git_objects(self.target, mapping,
453
self.source._git.object_store,
454
target_git_object_retriever, wants, pb)
456
self.target.commit_write_group()
464
def is_compatible(source, target):
465
"""Be compatible with GitRepository."""
466
# FIXME: Also check target uses VersionedFile
467
return (isinstance(source, LocalGitRepository) and
468
target.supports_rich_root() and
469
not isinstance(target, GitRepository))
187
472
class InterGitRepository(InterRepository):
473
"""InterRepository that copies between Git repositories."""
189
_matching_repo_format = GitFormat()
475
_matching_repo_format = GitRepositoryFormat()
192
478
def _get_repo_format_to_test():
197
483
self.fetch(revision_id, pb, find_ghosts=False)
199
485
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
486
mapping=None, fetch_spec=None):
201
487
if mapping is None:
202
488
mapping = self.source.get_mapping()
203
489
def progress(text):
204
pb.note("git: %s", text)
205
def determine_wants(heads):
206
if revision_id is None:
209
ret = [mapping.revision_id_bzr_to_foreign(revision_id)]
210
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
211
graph_walker = BzrFetchGraphWalker(self.target, mapping)
214
create_pb = pb = ui.ui_factory.nested_progress_bar()
490
trace.info("git: %s", text)
492
if revision_id is not None:
493
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
494
elif fetch_spec is not None:
495
args = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in fetch_spec.heads]
496
if fetch_spec is None and revision_id is None:
497
determine_wants = r.object_store.determine_wants_all
499
determine_wants = lambda x: [y for y in args if not y in r.object_store]
501
graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)
502
f, commit = r.object_store.add_thin_pack()
216
self.target.lock_write()
218
self.target.start_write_group()
220
import_git_objects(self.target, mapping,
221
iter(self.source.fetch_objects(determine_wants, graph_walker,
224
self.target.commit_write_group()
504
self.source.fetch_pack(determine_wants, graphwalker, f.write, progress)
232
511
def is_compatible(source, target):
233
512
"""Be compatible with GitRepository."""
234
# FIXME: Also check target uses VersionedFile
235
513
return (isinstance(source, GitRepository) and
236
target.supports_rich_root())
514
isinstance(target, GitRepository))