77
73
RemoteGitRepository,
79
75
from bzrlib.plugins.git.repository import (
81
77
GitRepositoryFormat,
82
78
LocalGitRepository,
86
def import_git_blob(texts, mapping, path, hexsha, base_inv, parent_id,
82
def import_git_blob(texts, mapping, path, hexsha, base_inv, base_ie, parent_id,
87
83
revision_id, parent_invs, shagitmap, lookup_object, executable, symlink):
88
84
"""Import a git blob object into a bzr repository.
160
153
shamap = [(hexsha, "blob", (ie.file_id, ie.revision))]
163
if file_id in base_inv:
157
if base_ie is not None:
164
158
old_path = base_inv.id2path(file_id)
159
if base_ie.kind == "directory":
160
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, []))
163
invdelta.append((old_path, path, file_id, ie))
169
164
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':
167
class SubmodulesRequireSubtrees(BzrError):
168
_fmt = """The repository you are fetching from contains submodules. Please run 'bzr upgrade --development-subtree'."""
172
def import_git_submodule(texts, mapping, path, hexsha, base_inv, base_ie,
173
parent_id, revision_id, parent_invs, shagitmap, lookup_object):
174
file_id = mapping.generate_file_id(path)
175
ie = TreeReference(file_id, urlutils.basename(path.decode("utf-8")),
177
ie.revision = revision_id
182
if base_ie.kind == ie.kind and base_ie.reference_revision == ie.reference_revision:
183
ie.revision = base_ie.revision
184
ie.reference_revision = mapping.revision_id_foreign_to_bzr(hexsha)
185
texts.insert_record_stream([FulltextContentFactory((file_id, ie.revision), (), None, "")])
186
invdelta = [(oldpath, path, file_id, ie)]
187
return invdelta, {}, {}
190
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]
192
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))
194
(path, ie) = deletable.pop()
195
ret.append((path, None, ie.file_id, None))
185
196
if ie.kind == "directory":
186
deletable.extend(ie.children.values())
197
for name, child_ie in ie.children.iteritems():
198
deletable.append((osutils.pathjoin(path, name), child_ie))
190
def import_git_tree(texts, mapping, path, hexsha, base_inv, parent_id,
202
def import_git_tree(texts, mapping, path, hexsha, base_inv, base_ie, parent_id,
191
203
revision_id, parent_invs, shagitmap, lookup_object):
192
204
"""Import a git tree object into a bzr repository.
201
213
file_id = mapping.generate_file_id(path)
202
214
# We just have to hope this is indeed utf-8:
203
ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")),
215
ie = InventoryDirectory(file_id, urlutils.basename(path.decode("utf-8")),
206
base_ie = base_inv[file_id]
208
218
# Newly appeared here
210
219
ie.revision = revision_id
211
texts.add_lines((file_id, ie.revision), (), [])
220
texts.insert_record_stream([FulltextContentFactory((file_id, ie.revision), (), None, "")])
212
221
invdelta.append((None, path, file_id, ie))
214
223
# See if this has changed at all
222
231
return [], {}, []
223
232
if base_ie.kind != "directory":
224
233
ie.revision = revision_id
225
texts.add_lines((ie.file_id, ie.revision), (), [])
234
texts.insert_record_stream([FulltextContentFactory((ie.file_id, ie.revision), (), None, "")])
226
235
invdelta.append((base_inv.id2path(ie.file_id), path, ie.file_id, ie))
236
if base_ie is not None and base_ie.kind == "directory":
237
base_children = base_ie.children
227
240
# Remember for next time
228
241
existing_children = set()
235
248
child_path = osutils.pathjoin(path, name)
236
249
if stat.S_ISDIR(mode):
237
250
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)
251
texts, mapping, child_path, child_hexsha, base_inv,
252
base_children.get(basename), file_id, revision_id, parent_invs, shagitmap,
240
254
invdelta.extend(subinvdelta)
241
255
child_modes.update(grandchildmodes)
242
256
shamap.extend(subshamap)
243
257
elif S_ISGITLINK(mode): # submodule
244
258
subinvdelta, grandchildmodes, subshamap = import_git_submodule(
245
texts, mapping, child_path, child_hexsha, base_inv,
259
texts, mapping, child_path, child_hexsha, base_inv, base_children.get(basename),
246
260
file_id, revision_id, parent_invs, shagitmap, lookup_object)
247
261
invdelta.extend(subinvdelta)
248
262
child_modes.update(grandchildmodes)
249
263
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,
265
subinvdelta, subshamap = import_git_blob(texts, mapping,
266
child_path, child_hexsha, base_inv, base_children.get(basename), file_id,
267
revision_id, parent_invs, shagitmap, lookup_object,
254
268
mode_is_executable(mode), stat.S_ISLNK(mode))
255
269
invdelta.extend(subinvdelta)
256
270
shamap.extend(subshamap)
258
272
stat.S_IFLNK, DEFAULT_FILE_MODE|0111):
259
273
child_modes[child_path] = mode
260
274
# Remove any children that have disappeared
261
invdelta.extend(remove_disappeared_children(base_inv, base_ie, existing_children))
275
if base_ie is not None and base_ie.kind == "directory":
276
invdelta.extend(remove_disappeared_children(base_inv.id2path(file_id),
277
base_children, existing_children))
262
278
shamap.append((hexsha, "tree", (file_id, revision_id)))
263
279
return invdelta, child_modes, shamap
266
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
282
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
268
284
"""Import a set of git objects into a bzr repository.
328
345
parent_invs_cache[parent_id] = parent_inv
329
346
if parent_invs == []:
330
347
base_inv = Inventory(root_id=None)
332
350
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,
351
base_ie = base_inv.root
352
inv_delta, unusual_modes, shamap = import_git_tree(repo.texts,
353
mapping, "", root_trees[revid], base_inv, base_ie, None, revid,
335
354
parent_invs, target_git_object_retriever._idmap, lookup_object)
336
355
target_git_object_retriever._idmap.add_entries(shamap)
337
356
if unusual_modes != {}:
401
422
ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads if revid not in (None, NULL_REVISION)]
402
423
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)
424
pack_hint = self.fetch_objects(determine_wants, mapping, pb)
425
if pack_hint is not None and self.target._format.pack_compresses:
426
self.target.pack(hint=pack_hint)
427
if interesting_heads is not None:
428
present_interesting_heads = self.target.has_revisions(interesting_heads)
429
missing_interesting_heads = set(interesting_heads) - present_interesting_heads
430
if missing_interesting_heads:
431
raise AssertionError("Missing interesting heads: %r" % missing_interesting_heads)
404
432
return self._refs
435
_GIT_PROGRESS_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
436
def report_git_progress(pb, text):
437
text = text.rstrip("\r\n")
438
g = _GIT_PROGRESS_RE.match(text)
440
(text, pct, current, total) = g.groups()
441
pb.update(text, int(current), int(total))
443
pb.update(text, 0, 0)
407
446
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
408
"""InterRepository that copies revisions from a remote Git into a non-Git
447
"""InterRepository that copies revisions from a remote Git into a non-Git
450
def get_target_heads(self):
451
# FIXME: This should be more efficient
452
all_revs = self.target.all_revision_ids()
453
parent_map = self.target.get_parent_map(all_revs)
455
map(all_parents.update, parent_map.itervalues())
456
return set(all_revs) - all_parents
411
458
def fetch_objects(self, determine_wants, mapping, pb=None):
412
459
def progress(text):
413
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
460
report_git_progress(pb, text)
414
461
store = BazaarObjectStore(self.target, mapping)
415
462
self.target.lock_write()
417
heads = self.target.get_graph().heads(self.target.all_revision_ids())
464
heads = self.get_target_heads()
418
465
graph_walker = store.get_graph_walker(
419
466
[store._lookup_revision_sha1(head) for head in heads])
420
467
recorded_wants = []
431
478
self.target.start_write_group()
433
480
objects_iter = self.source.fetch_objects(
434
record_determine_wants, graph_walker,
481
record_determine_wants, graph_walker,
435
482
store.get_raw, progress)
436
import_git_objects(self.target, mapping, objects_iter,
483
import_git_objects(self.target, mapping, objects_iter,
437
484
store, recorded_wants, pb)
439
self.target.commit_write_group()
486
pack_hint = self.target.commit_write_group()
442
490
create_pb.finished()
447
495
def is_compatible(source, target):
448
496
"""Be compatible with GitRepository."""
449
497
# FIXME: Also check target uses VersionedFile
450
return (isinstance(source, RemoteGitRepository) and
498
return (isinstance(source, RemoteGitRepository) and
451
499
target.supports_rich_root() and
452
500
not isinstance(target, GitRepository))
455
503
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
456
"""InterRepository that copies revisions from a local Git into a non-Git
504
"""InterRepository that copies revisions from a local Git into a non-Git
459
507
def fetch_objects(self, determine_wants, mapping, pb=None):
490
539
class InterGitGitRepository(InterGitRepository):
491
540
"""InterRepository that copies between Git repositories."""
493
def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
542
def fetch_objects(self, determine_wants, mapping, pb=None):
544
trace.note("git: %s", text)
545
graphwalker = self.target._git.get_graph_walker()
546
if isinstance(self.source, LocalGitRepository) and isinstance(self.target, LocalGitRepository):
547
return self.source._git.fetch(self.target._git, determine_wants,
549
elif isinstance(self.source, LocalGitRepository) and isinstance(self.target, RemoteGitRepository):
550
raise NotImplementedError
551
elif isinstance(self.source, RemoteGitRepository) and isinstance(self.target, LocalGitRepository):
552
f, commit = self.target._git.object_store.add_thin_pack()
554
refs = self.source._git.fetch_pack(determine_wants, graphwalker,
564
def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
494
565
mapping=None, fetch_spec=None, branches=None):
495
566
if mapping is None:
496
567
mapping = self.source.get_mapping()
498
trace.info("git: %s", text)
499
568
r = self.target._git
500
569
if revision_id is not None:
501
570
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
507
576
determine_wants = r.object_store.determine_wants_all
509
578
determine_wants = lambda x: [y for y in args if not y in r.object_store]
579
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
583
def is_compatible(source, target):
524
584
"""Be compatible with GitRepository."""
525
return (isinstance(source, GitRepository) and
585
return (isinstance(source, GitRepository) and
526
586
isinstance(target, GitRepository))