77
71
RemoteGitRepository,
79
73
from bzrlib.plugins.git.repository import (
81
75
GitRepositoryFormat,
82
76
LocalGitRepository,
86
def import_git_blob(texts, mapping, path, hexsha, base_inv, parent_id,
80
MAX_INV_CACHE_SIZE = 50 * 1024 * 1024
83
def import_git_blob(texts, mapping, path, hexsha, base_inv, base_ie, parent_id,
87
84
revision_id, parent_invs, shagitmap, lookup_object, executable, symlink):
88
85
"""Import a git blob object into a bzr repository.
160
154
shamap = [(hexsha, "blob", (ie.file_id, ie.revision))]
163
if file_id in base_inv:
158
if base_ie is not None:
164
159
old_path = base_inv.id2path(file_id)
160
if base_ie.kind == "directory":
161
invdelta.extend(remove_disappeared_children(old_path, base_ie.children, []))
167
invdelta = [(old_path, path, file_id, ie)]
168
invdelta.extend(remove_disappeared_children(base_inv, base_ie, []))
164
invdelta.append((old_path, path, file_id, ie))
169
165
return (invdelta, shamap)
172
def import_git_submodule(texts, mapping, path, hexsha, base_inv, parent_id,
173
revision_id, parent_invs, shagitmap, lookup_object):
174
raise NotImplementedError(import_git_submodule)
177
def remove_disappeared_children(base_inv, base_ie, existing_children):
178
if base_ie is None or base_ie.kind != 'directory':
168
class SubmodulesRequireSubtrees(BzrError):
169
_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'."""
173
def import_git_submodule(texts, mapping, path, hexsha, base_inv, base_ie,
174
parent_id, revision_id, parent_invs, shagitmap, lookup_object):
175
file_id = mapping.generate_file_id(path)
176
ie = TreeReference(file_id, urlutils.basename(path.decode("utf-8")),
178
ie.revision = revision_id
183
if base_ie.kind == ie.kind and base_ie.reference_revision == ie.reference_revision:
184
ie.revision = base_ie.revision
185
ie.reference_revision = mapping.revision_id_foreign_to_bzr(hexsha)
186
texts.insert_record_stream([FulltextContentFactory((file_id, ie.revision), (), None, "")])
187
invdelta = [(oldpath, path, file_id, ie)]
188
return invdelta, {}, {}
191
def remove_disappeared_children(path, base_children, existing_children):
181
deletable = [v for k,v in base_ie.children.iteritems() if k not in existing_children]
193
deletable = [(osutils.pathjoin(path, k), v) for k,v in base_children.iteritems() if k not in existing_children]
184
ret.append((base_inv.id2path(ie.file_id), None, ie.file_id, None))
195
(path, ie) = deletable.pop()
196
ret.append((path, None, ie.file_id, None))
185
197
if ie.kind == "directory":
186
deletable.extend(ie.children.values())
198
for name, child_ie in ie.children.iteritems():
199
deletable.append((osutils.pathjoin(path, name), child_ie))
190
def import_git_tree(texts, mapping, path, hexsha, base_inv, parent_id,
191
revision_id, parent_invs, shagitmap, lookup_object):
203
def import_git_tree(texts, mapping, path, hexsha, base_inv, base_ie, parent_id,
204
revision_id, parent_invs, shagitmap, lookup_object, allow_submodules=False):
192
205
"""Import a git tree object into a bzr repository.
194
207
:param texts: VersionedFiles object to add to
201
214
file_id = mapping.generate_file_id(path)
202
215
# We just have to hope this is indeed utf-8:
203
ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")),
216
ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")),
206
base_ie = base_inv[file_id]
208
219
# Newly appeared here
210
220
ie.revision = revision_id
211
texts.add_lines((file_id, ie.revision), (), [])
221
texts.insert_record_stream([FulltextContentFactory((file_id, ie.revision), (), None, "")])
212
222
invdelta.append((None, path, file_id, ie))
214
224
# See if this has changed at all
222
232
return [], {}, []
223
233
if base_ie.kind != "directory":
224
234
ie.revision = revision_id
225
texts.add_lines((ie.file_id, ie.revision), (), [])
235
texts.insert_record_stream([FulltextContentFactory((ie.file_id, ie.revision), (), None, "")])
226
236
invdelta.append((base_inv.id2path(ie.file_id), path, ie.file_id, ie))
237
if base_ie is not None and base_ie.kind == "directory":
238
base_children = base_ie.children
227
241
# Remember for next time
228
242
existing_children = set()
235
249
child_path = osutils.pathjoin(path, name)
236
250
if stat.S_ISDIR(mode):
237
251
subinvdelta, grandchildmodes, subshamap = import_git_tree(
238
texts, mapping, child_path, child_hexsha, base_inv,
239
file_id, revision_id, parent_invs, shagitmap, lookup_object)
252
texts, mapping, child_path, child_hexsha, base_inv,
253
base_children.get(basename), file_id, revision_id,
254
parent_invs, shagitmap, lookup_object,
255
allow_submodules=allow_submodules)
240
256
invdelta.extend(subinvdelta)
241
257
child_modes.update(grandchildmodes)
242
258
shamap.extend(subshamap)
243
259
elif S_ISGITLINK(mode): # submodule
260
if not allow_submodules:
261
raise SubmodulesRequireSubtrees()
244
262
subinvdelta, grandchildmodes, subshamap = import_git_submodule(
245
texts, mapping, child_path, child_hexsha, base_inv,
263
texts, mapping, child_path, child_hexsha, base_inv, base_children.get(basename),
246
264
file_id, revision_id, parent_invs, shagitmap, lookup_object)
247
265
invdelta.extend(subinvdelta)
248
266
child_modes.update(grandchildmodes)
249
267
shamap.extend(subshamap)
251
subinvdelta, subshamap = import_git_blob(texts, mapping,
252
child_path, child_hexsha, base_inv, file_id, revision_id,
253
parent_invs, shagitmap, lookup_object,
269
subinvdelta, subshamap = import_git_blob(texts, mapping,
270
child_path, child_hexsha, base_inv, base_children.get(basename), file_id,
271
revision_id, parent_invs, shagitmap, lookup_object,
254
272
mode_is_executable(mode), stat.S_ISLNK(mode))
255
273
invdelta.extend(subinvdelta)
256
274
shamap.extend(subshamap)
258
276
stat.S_IFLNK, DEFAULT_FILE_MODE|0111):
259
277
child_modes[child_path] = mode
260
278
# Remove any children that have disappeared
261
invdelta.extend(remove_disappeared_children(base_inv, base_ie, existing_children))
279
if base_ie is not None and base_ie.kind == "directory":
280
invdelta.extend(remove_disappeared_children(base_inv.id2path(file_id),
281
base_children, existing_children))
262
282
shamap.append((hexsha, "tree", (file_id, revision_id)))
263
283
return invdelta, child_modes, shamap
266
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
286
def approx_inv_size(inv):
287
# Very rough estimate, 1k per inventory entry
288
return len(inv) * 1024
291
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
268
293
"""Import a set of git objects into a bzr repository.
293
317
o = lookup_object(head)
319
trace.mutter('missing head %s', head)
296
321
if isinstance(o, Commit):
297
322
rev = mapping.import_commit(o)
298
323
if repo.has_revision(rev.revision_id):
300
325
squash_revision(repo, rev)
301
root_trees[rev.revision_id] = o.tree
302
revisions[rev.revision_id] = rev
303
graph.append((rev.revision_id, rev.parent_ids))
304
target_git_object_retriever._idmap.add_entry(o.id, "commit",
326
graph.append((o.id, o.parents))
327
target_git_object_retriever._idmap.add_entry(o.id, "commit",
305
328
(rev.revision_id, o.tree))
306
329
heads.extend([p for p in o.parents if p not in checked])
307
330
elif isinstance(o, Tag):
308
331
heads.append(o.object[1])
310
333
trace.warning("Unable to import head object %r" % o)
312
336
# Order the revisions
313
337
# Create the inventory objects
314
for i, revid in enumerate(topo_sort(graph)):
338
for i, head in enumerate(topo_sort(graph)):
315
339
if pb is not None:
316
340
pb.update("fetching revisions", i, len(graph))
317
rev = revisions[revid]
318
# We have to do this here, since we have to walk the tree and
319
# we need to make sure to import the blobs / trees with the right
341
o = lookup_object(head)
342
rev = mapping.import_commit(o)
343
# We have to do this here, since we have to walk the tree and
344
# we need to make sure to import the blobs / trees with the right
320
345
# path; this may involve adding them more than once.
322
347
for parent_id in rev.parent_ids:
328
353
parent_invs_cache[parent_id] = parent_inv
329
354
if parent_invs == []:
330
355
base_inv = Inventory(root_id=None)
332
358
base_inv = parent_invs[0]
333
inv_delta, unusual_modes, shamap = import_git_tree(repo.texts,
334
mapping, "", root_trees[revid], base_inv, None, revid,
335
parent_invs, target_git_object_retriever._idmap, lookup_object)
359
base_ie = base_inv.root
360
inv_delta, unusual_modes, shamap = import_git_tree(repo.texts,
361
mapping, "", o.tree, base_inv, base_ie, None, rev.revision_id,
362
parent_invs, target_git_object_retriever._idmap, lookup_object,
363
allow_submodules=getattr(repo._format, "supports_tree_reference", False))
336
364
target_git_object_retriever._idmap.add_entries(shamap)
337
365
if unusual_modes != {}:
338
366
for path, mode in unusual_modes.iteritems():
401
431
ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads if revid not in (None, NULL_REVISION)]
402
432
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
403
self.fetch_objects(determine_wants, mapping, pb)
433
pack_hint = self.fetch_objects(determine_wants, mapping, pb)
434
if pack_hint is not None and self.target._format.pack_compresses:
435
self.target.pack(hint=pack_hint)
436
if interesting_heads is not None:
437
present_interesting_heads = self.target.has_revisions(interesting_heads)
438
missing_interesting_heads = set(interesting_heads) - present_interesting_heads
439
if missing_interesting_heads:
440
raise AssertionError("Missing interesting heads: %r" % missing_interesting_heads)
404
441
return self._refs
444
_GIT_PROGRESS_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
445
def report_git_progress(pb, text):
446
text = text.rstrip("\r\n")
447
g = _GIT_PROGRESS_RE.match(text)
449
(text, pct, current, total) = g.groups()
450
pb.update(text, int(current), int(total))
452
pb.update(text, 0, 0)
407
455
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
408
"""InterRepository that copies revisions from a remote Git into a non-Git
456
"""InterRepository that copies revisions from a remote Git into a non-Git
459
def get_target_heads(self):
460
# FIXME: This should be more efficient
461
all_revs = self.target.all_revision_ids()
462
parent_map = self.target.get_parent_map(all_revs)
464
map(all_parents.update, parent_map.itervalues())
465
return set(all_revs) - all_parents
411
467
def fetch_objects(self, determine_wants, mapping, pb=None):
412
468
def progress(text):
413
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
469
report_git_progress(pb, text)
414
470
store = BazaarObjectStore(self.target, mapping)
415
471
self.target.lock_write()
417
heads = self.target.get_graph().heads(self.target.all_revision_ids())
473
heads = self.get_target_heads()
418
474
graph_walker = store.get_graph_walker(
419
475
[store._lookup_revision_sha1(head) for head in heads])
420
476
recorded_wants = []
431
487
self.target.start_write_group()
433
489
objects_iter = self.source.fetch_objects(
434
record_determine_wants, graph_walker,
490
record_determine_wants, graph_walker,
435
491
store.get_raw, progress)
436
import_git_objects(self.target, mapping, objects_iter,
492
import_git_objects(self.target, mapping, objects_iter,
437
493
store, recorded_wants, pb)
439
self.target.commit_write_group()
495
pack_hint = self.target.commit_write_group()
442
499
create_pb.finished()
447
504
def is_compatible(source, target):
448
505
"""Be compatible with GitRepository."""
449
506
# FIXME: Also check target uses VersionedFile
450
return (isinstance(source, RemoteGitRepository) and
507
return (isinstance(source, RemoteGitRepository) and
451
508
target.supports_rich_root() and
452
509
not isinstance(target, GitRepository))
455
512
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
456
"""InterRepository that copies revisions from a local Git into a non-Git
513
"""InterRepository that copies revisions from a local Git into a non-Git
459
516
def fetch_objects(self, determine_wants, mapping, pb=None):
490
548
class InterGitGitRepository(InterGitRepository):
491
549
"""InterRepository that copies between Git repositories."""
493
def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
551
def fetch_objects(self, determine_wants, mapping, pb=None):
553
trace.note("git: %s", text)
554
graphwalker = self.target._git.get_graph_walker()
555
if isinstance(self.source, LocalGitRepository) and isinstance(self.target, LocalGitRepository):
556
return self.source._git.fetch(self.target._git, determine_wants,
558
elif isinstance(self.source, LocalGitRepository) and isinstance(self.target, RemoteGitRepository):
559
raise NotImplementedError
560
elif isinstance(self.source, RemoteGitRepository) and isinstance(self.target, LocalGitRepository):
561
f, commit = self.target._git.object_store.add_thin_pack()
563
refs = self.source._git.fetch_pack(determine_wants, graphwalker,
573
def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
494
574
mapping=None, fetch_spec=None, branches=None):
495
575
if mapping is None:
496
576
mapping = self.source.get_mapping()
498
trace.info("git: %s", text)
499
577
r = self.target._git
500
578
if revision_id is not None:
501
579
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
507
585
determine_wants = r.object_store.determine_wants_all
509
587
determine_wants = lambda x: [y for y in args if not y in r.object_store]
588
return self.fetch_objects(determine_wants, mapping)
511
graphwalker = r.get_graph_walker()
512
f, commit = r.object_store.add_thin_pack()
514
refs = self.source.fetch_pack(determine_wants, graphwalker,
523
592
def is_compatible(source, target):
524
593
"""Be compatible with GitRepository."""
525
return (isinstance(source, GitRepository) and
594
return (isinstance(source, GitRepository) and
526
595
isinstance(target, GitRepository))