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, ui, urlutils
18
from bzrlib.errors import InvalidRevisionId, NoSuchRevision
19
from bzrlib.inventory import Inventory
20
from bzrlib.repository import InterRepository
21
from bzrlib.trace import info
22
from bzrlib.tsort import topo_sort
17
from dulwich.objects import (
23
from dulwich.object_store import (
26
from itertools import (
39
from bzrlib.errors import (
43
from bzrlib.inventory import (
50
from bzrlib.repository import (
53
from bzrlib.revision import (
56
from bzrlib.revisiontree import (
59
from bzrlib.testament import (
62
from bzrlib.tsort import (
65
from bzrlib.versionedfile import (
66
ChunkedContentFactory,
69
from bzrlib.plugins.git.mapping import (
75
from bzrlib.plugins.git.object_store import (
80
from bzrlib.plugins.git.remote import (
24
83
from bzrlib.plugins.git.repository import (
29
from bzrlib.plugins.git.shamap import GitObjectConverter
30
from bzrlib.plugins.git.remote import RemoteGitRepository
33
from dulwich.client import SimpleFetchGraphWalker
34
from dulwich.objects import Commit
36
from cStringIO import StringIO
39
class BzrFetchGraphWalker(object):
40
"""GraphWalker implementation that uses a Bazaar repository."""
42
def __init__(self, repository, mapping):
43
self.repository = repository
44
self.mapping = mapping
46
self.heads = set(repository.all_revision_ids())
50
return iter(self.next, None)
53
revid = self.mapping.revision_id_foreign_to_bzr(sha)
56
def remove(self, revid):
58
if revid in self.heads:
59
self.heads.remove(revid)
60
if revid in self.parents:
61
for p in self.parents[revid]:
66
ret = self.heads.pop()
67
ps = self.repository.get_parent_map([ret])[ret]
68
self.parents[ret] = ps
69
self.heads.update([p for p in ps if not p in self.done])
72
return self.mapping.revision_id_bzr_to_foreign(ret)[0]
73
except InvalidRevisionId:
78
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
90
def import_git_blob(texts, mapping, path, name, (base_hexsha, hexsha),
91
base_inv, parent_id, revision_id,
92
parent_invs, lookup_object, (base_mode, mode), store_updater,
79
94
"""Import a git blob object into a bzr repository.
81
:param repo: bzr repository
96
:param texts: VersionedFiles to add to
82
97
:param path: Path in the tree
83
98
:param blob: A git blob
85
file_id = mapping.generate_file_id(path)
86
text_revision = inv.revision_id
87
repo.texts.add_lines((file_id, text_revision),
88
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
89
osutils.split_lines(blob.data))
90
ie = inv.add_path(path, "file", file_id)
91
ie.revision = text_revision
92
ie.text_size = len(blob.data)
93
ie.text_sha1 = osutils.sha_string(blob.data)
94
ie.executable = executable
97
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
99
:return: Inventory delta for this file
101
if mapping.is_control_file(path):
103
if base_hexsha == hexsha and base_mode == mode:
104
# If nothing has changed since the base revision, we're done
106
file_id = lookup_file_id(path)
107
if stat.S_ISLNK(mode):
111
ie = cls(file_id, name.decode("utf-8"), parent_id)
112
if ie.kind == "file":
113
ie.executable = mode_is_executable(mode)
114
if base_hexsha == hexsha and mode_kind(base_mode) == mode_kind(mode):
115
base_ie = base_inv[base_inv.path2id(path)]
116
ie.text_size = base_ie.text_size
117
ie.text_sha1 = base_ie.text_sha1
118
if ie.kind == "symlink":
119
ie.symlink_target = base_ie.symlink_target
120
if ie.executable == base_ie.executable:
121
ie.revision = base_ie.revision
123
blob = lookup_object(hexsha)
125
blob = lookup_object(hexsha)
126
if ie.kind == "symlink":
128
ie.symlink_target = blob.data
130
ie.text_size = sum(imap(len, blob.chunked))
131
ie.text_sha1 = osutils.sha_strings(blob.chunked)
132
# Check what revision we should store
134
for pinv in parent_invs:
139
if (pie.text_sha1 == ie.text_sha1 and
140
pie.executable == ie.executable and
141
pie.symlink_target == ie.symlink_target):
142
# found a revision in one of the parents to use
143
ie.revision = pie.revision
145
parent_key = (file_id, pie.revision)
146
if not parent_key in parent_keys:
147
parent_keys.append(parent_key)
148
if ie.revision is None:
149
# Need to store a new revision
150
ie.revision = revision_id
151
assert ie.revision is not None
152
if ie.kind == 'symlink':
155
chunks = blob.chunked
156
texts.insert_record_stream([
157
ChunkedContentFactory((file_id, ie.revision),
158
tuple(parent_keys), ie.text_sha1, chunks)])
160
if base_hexsha is not None:
161
old_path = path.decode("utf-8") # Renames are not supported yet
162
if stat.S_ISDIR(base_mode):
163
invdelta.extend(remove_disappeared_children(base_inv, old_path,
164
lookup_object(base_hexsha), [], lookup_object))
167
new_path = path.decode("utf-8")
168
invdelta.append((old_path, new_path, file_id, ie))
169
if base_hexsha != hexsha:
170
store_updater.add_object(blob, ie, path)
174
class SubmodulesRequireSubtrees(BzrError):
175
_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'."""
179
def import_git_submodule(texts, mapping, path, name, (base_hexsha, hexsha),
180
base_inv, parent_id, revision_id, parent_invs, lookup_object,
181
(base_mode, mode), store_updater, lookup_file_id):
182
if base_hexsha == hexsha and base_mode == mode:
184
file_id = lookup_file_id(path)
185
ie = TreeReference(file_id, name.decode("utf-8"), parent_id)
186
ie.revision = revision_id
187
if base_hexsha is None:
191
ie.reference_revision = mapping.revision_id_foreign_to_bzr(hexsha)
192
texts.insert_record_stream([
193
ChunkedContentFactory((file_id, ie.revision), (), None, [])])
194
invdelta = [(oldpath, path, file_id, ie)]
198
def remove_disappeared_children(base_inv, path, base_tree, existing_children,
200
"""Generate an inventory delta for removed children.
202
:param base_inv: Base inventory against which to generate the
204
:param path: Path to process (unicode)
205
:param base_tree: Git Tree base object
206
:param existing_children: Children that still exist
207
:param lookup_object: Lookup a git object by its SHA1
208
:return: Inventory delta, as list
210
assert type(path) is unicode
212
for name, mode, hexsha in base_tree.iteritems():
213
if name in existing_children:
215
c_path = posixpath.join(path, name.decode("utf-8"))
216
file_id = base_inv.path2id(c_path)
217
assert file_id is not None
218
ret.append((c_path, None, file_id, None))
219
if stat.S_ISDIR(mode):
220
ret.extend(remove_disappeared_children(
221
base_inv, c_path, lookup_object(hexsha), [], lookup_object))
225
def import_git_tree(texts, mapping, path, name, (base_hexsha, hexsha),
226
base_inv, parent_id, revision_id, parent_invs,
227
lookup_object, (base_mode, mode), store_updater,
228
lookup_file_id, allow_submodules=False):
98
229
"""Import a git tree object into a bzr repository.
100
:param repo: A Bzr repository object
101
:param path: Path in the tree
231
:param texts: VersionedFiles object to add to
232
:param path: Path in the tree (str)
233
:param name: Name of the tree (str)
102
234
:param tree: A git tree object
103
:param inv: Inventory object
235
:param base_inv: Base inventory against which to return inventory delta
236
:return: Inventory delta for this subtree
105
file_id = mapping.generate_file_id(path)
106
text_revision = inv.revision_id
107
repo.texts.add_lines((file_id, text_revision),
108
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
110
ie = inv.add_path(path, "directory", file_id)
111
ie.revision = text_revision
112
for mode, name, hexsha in tree.entries():
113
entry_kind = (mode & 0700000) / 0100000
114
basename = name.decode("utf-8")
118
child_path = urlutils.join(path, name)
120
tree = lookup_object(hexsha)
121
import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
122
elif entry_kind == 1:
123
blob = lookup_object(hexsha)
124
fs_mode = mode & 0777
125
import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
127
raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
130
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
238
assert type(path) is str
239
assert type(name) is str
240
if base_hexsha == hexsha and base_mode == mode:
241
# If nothing has changed since the base revision, we're done
244
file_id = lookup_file_id(path)
245
# We just have to hope this is indeed utf-8:
246
ie = InventoryDirectory(file_id, name.decode("utf-8"), parent_id)
247
tree = lookup_object(hexsha)
248
if base_hexsha is None:
250
old_path = None # Newly appeared here
252
base_tree = lookup_object(base_hexsha)
253
old_path = path.decode("utf-8") # Renames aren't supported yet
254
new_path = path.decode("utf-8")
255
if base_tree is None or type(base_tree) is not Tree:
256
ie.revision = revision_id
257
invdelta.append((old_path, new_path, ie.file_id, ie))
258
texts.insert_record_stream([
259
ChunkedContentFactory((ie.file_id, ie.revision), (), None, [])])
260
# Remember for next time
261
existing_children = set()
263
for child_mode, name, child_hexsha in tree.entries():
264
existing_children.add(name)
265
child_path = posixpath.join(path, name)
266
if type(base_tree) is Tree:
268
child_base_mode, child_base_hexsha = base_tree[name]
270
child_base_hexsha = None
273
child_base_hexsha = None
275
if stat.S_ISDIR(child_mode):
276
subinvdelta, grandchildmodes = import_git_tree(texts, mapping,
277
child_path, name, (child_base_hexsha, child_hexsha), base_inv,
278
file_id, revision_id, parent_invs, lookup_object,
279
(child_base_mode, child_mode), store_updater, lookup_file_id,
280
allow_submodules=allow_submodules)
281
elif S_ISGITLINK(child_mode): # submodule
282
if not allow_submodules:
283
raise SubmodulesRequireSubtrees()
284
subinvdelta, grandchildmodes = import_git_submodule(texts, mapping,
285
child_path, name, (child_base_hexsha, child_hexsha), base_inv,
286
file_id, revision_id, parent_invs, lookup_object,
287
(child_base_mode, child_mode), store_updater, lookup_file_id)
289
subinvdelta = import_git_blob(texts, mapping, child_path, name,
290
(child_base_hexsha, child_hexsha), base_inv, file_id,
291
revision_id, parent_invs, lookup_object,
292
(child_base_mode, child_mode), store_updater, lookup_file_id)
294
child_modes.update(grandchildmodes)
295
invdelta.extend(subinvdelta)
296
if child_mode not in (stat.S_IFDIR, DEFAULT_FILE_MODE,
297
stat.S_IFLNK, DEFAULT_FILE_MODE|0111):
298
child_modes[child_path] = child_mode
299
# Remove any children that have disappeared
300
if base_tree is not None and type(base_tree) is Tree:
301
invdelta.extend(remove_disappeared_children(base_inv, old_path,
302
base_tree, existing_children, lookup_object))
303
store_updater.add_object(tree, ie, path)
304
return invdelta, child_modes
307
def verify_commit_reconstruction(target_git_object_retriever, lookup_object,
308
o, rev, ret_tree, parent_trees, mapping, unusual_modes):
309
new_unusual_modes = mapping.export_unusual_file_modes(rev)
310
if new_unusual_modes != unusual_modes:
311
raise AssertionError("unusual modes don't match: %r != %r" % (
312
unusual_modes, new_unusual_modes))
313
# Verify that we can reconstruct the commit properly
314
rec_o = target_git_object_retriever._reconstruct_commit(rev, o.tree, True)
316
raise AssertionError("Reconstructed commit differs: %r != %r" % (
320
for path, obj, ie in _tree_to_objects(ret_tree, parent_trees,
321
target_git_object_retriever._cache.idmap, unusual_modes, mapping.BZR_DUMMY_FILE):
322
old_obj_id = tree_lookup_path(lookup_object, o.tree, path)[1]
324
if obj.id != old_obj_id:
325
diff.append((path, lookup_object(old_obj_id), obj))
326
for (path, old_obj, new_obj) in diff:
327
while (old_obj.type_name == "tree" and
328
new_obj.type_name == "tree" and
329
sorted(old_obj) == sorted(new_obj)):
331
if old_obj[name][0] != new_obj[name][0]:
332
raise AssertionError("Modes for %s differ: %o != %o" %
333
(path, old_obj[name][0], new_obj[name][0]))
334
if old_obj[name][1] != new_obj[name][1]:
335
# Found a differing child, delve deeper
336
path = posixpath.join(path, name)
337
old_obj = lookup_object(old_obj[name][1])
338
new_obj = new_objs[path]
340
raise AssertionError("objects differ for %s: %r != %r" % (path,
344
def import_git_commit(repo, mapping, head, lookup_object,
345
target_git_object_retriever, trees_cache):
346
o = lookup_object(head)
347
rev, roundtrip_revid, testament3_sha1 = mapping.import_commit(o,
348
lambda x: target_git_object_retriever.lookup_git_sha(x)[1][0])
349
# We have to do this here, since we have to walk the tree and
350
# we need to make sure to import the blobs / trees with the right
351
# path; this may involve adding them more than once.
352
parent_trees = trees_cache.revision_trees(rev.parent_ids)
353
if parent_trees == []:
354
base_inv = Inventory(root_id=None)
358
base_inv = parent_trees[0].inventory
359
base_tree = lookup_object(o.parents[0]).tree
360
base_mode = stat.S_IFDIR
361
store_updater = target_git_object_retriever._get_updater(rev)
362
store_updater.add_object(o, None, None)
363
fileid_map = mapping.get_fileid_map(lookup_object, o.tree)
364
inv_delta, unusual_modes = import_git_tree(repo.texts,
365
mapping, "", "", (base_tree, o.tree), base_inv,
366
None, rev.revision_id, [p.inventory for p in parent_trees],
367
lookup_object, (base_mode, stat.S_IFDIR), store_updater,
368
fileid_map.lookup_file_id,
369
allow_submodules=getattr(repo._format, "supports_tree_reference", False))
370
store_updater.finish()
371
if unusual_modes != {}:
372
for path, mode in unusual_modes.iteritems():
373
warn_unusual_mode(rev.foreign_revid, path, mode)
374
mapping.import_unusual_file_modes(rev, unusual_modes)
376
basis_id = rev.parent_ids[0]
378
basis_id = NULL_REVISION
380
rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
381
inv_delta, rev.revision_id, rev.parent_ids, base_inv)
382
# FIXME: Check testament3_sha1
383
if roundtrip_revid is not None:
384
original_revid = rev.revision_id
385
rev.revision_id = roundtrip_revid
386
testament = StrictTestament3(rev, inv)
387
if testament.as_sha1() != testament3_sha1:
388
trace.mutter("Testament SHA1 %r for %r did not match %r.",
389
testament.as_sha1(), rev.revision_id,
391
rev.revision_id = original_revid
392
ret_tree = RevisionTree(repo, inv, rev.revision_id)
393
trees_cache.add(ret_tree)
394
repo.add_revision(rev.revision_id, rev)
395
if "verify" in debug.debug_flags:
396
verify_commit_reconstruction(target_git_object_retriever,
397
lookup_object, o, rev, ret_tree, parent_trees, mapping,
401
def import_git_objects(repo, mapping, object_iter,
402
target_git_object_retriever, heads, pb=None, limit=None):
132
403
"""Import a set of git objects into a bzr repository.
134
:param repo: Bazaar repository
405
:param repo: Target Bazaar repository
135
406
:param mapping: Mapping to use
136
407
:param object_iter: Iterator over Git objects.
408
:return: Tuple with pack hints and last imported revision id
138
# TODO: a more (memory-)efficient implementation of this
410
def lookup_object(sha):
412
return object_iter[sha]
414
return target_git_object_retriever[sha]
417
heads = list(set(heads))
418
trees_cache = LRUTreeCache(repo)
142
419
# Find and convert commit objects
143
for o in object_iter.iterobjects():
422
pb.update("finding revisions to fetch", len(graph), None)
424
assert isinstance(head, str)
426
o = lookup_object(head)
144
429
if isinstance(o, Commit):
145
rev = mapping.import_commit(o)
146
root_trees[rev.revision_id] = object_iter[o.tree]
147
revisions[rev.revision_id] = rev
148
graph.append((rev.revision_id, rev.parent_ids))
430
rev, roundtrip_revid, testament3_sha1 = mapping.import_commit(o,
432
if (repo.has_revision(rev.revision_id) or
433
(roundtrip_revid and repo.has_revision(roundtrip_revid))):
435
graph.append((o.id, o.parents))
436
heads.extend([p for p in o.parents if p not in checked])
437
elif isinstance(o, Tag):
438
if o.object[1] not in checked:
439
heads.append(o.object[1])
441
trace.warning("Unable to import head object %r" % o)
149
444
# Order the revisions
150
445
# Create the inventory objects
151
for i, revid in enumerate(topo_sort(graph)):
153
pb.update("fetching revisions", i, len(graph))
154
root_tree = root_trees[revid]
155
rev = revisions[revid]
156
# We have to do this here, since we have to walk the tree and
157
# we need to make sure to import the blobs / trees with the riht
158
# path; this may involve adding them more than once.
160
inv.revision_id = rev.revision_id
161
def lookup_object(sha):
162
if sha in object_iter:
163
return object_iter[sha]
164
return target_git_object_retriever(sha)
165
parent_invs = [repo.get_inventory(r) for r in rev.parent_ids]
166
import_git_tree(repo, mapping, "", root_tree, inv, parent_invs,
168
repo.add_revision(rev.revision_id, rev, inv)
171
def reconstruct_git_object(repo, mapping, sha):
172
import pdb; pdb.set_trace()
176
raise KeyError("No such object %s" % sha)
179
class InterGitNonGitRepository(InterRepository):
181
_matching_repo_format = GitFormat()
447
revision_ids = topo_sort(graph)
449
if limit is not None:
450
revision_ids = revision_ids[:limit]
452
for offset in range(0, len(revision_ids), batch_size):
453
target_git_object_retriever.start_write_group()
455
repo.start_write_group()
457
for i, head in enumerate(
458
revision_ids[offset:offset+batch_size]):
460
pb.update("fetching revisions", offset+i,
462
import_git_commit(repo, mapping, head, lookup_object,
463
target_git_object_retriever, trees_cache)
466
repo.abort_write_group()
469
hint = repo.commit_write_group()
471
pack_hints.extend(hint)
473
target_git_object_retriever.abort_write_group()
476
target_git_object_retriever.commit_write_group()
477
return pack_hints, last_imported
480
class InterGitRepository(InterRepository):
482
_matching_repo_format = GitRepositoryFormat()
184
485
def _get_repo_format_to_test():
188
489
"""See InterRepository.copy_content."""
189
490
self.fetch(revision_id, pb, find_ghosts=False)
191
def fetch_objects(self, determine_wants, mapping, pb=None):
493
class InterGitNonGitRepository(InterGitRepository):
494
"""Base InterRepository that copies revisions from a Git into a non-Git
497
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
498
"""Fetch objects from a remote server.
500
:param determine_wants: determine_wants callback
501
:param mapping: BzrGitMapping to use
502
:param pb: Optional progress bar
503
:param limit: Maximum number of commits to import.
504
:return: Tuple with pack hint, last imported revision id and remote refs
506
raise NotImplementedError(self.fetch_objects)
508
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
509
mapping=None, fetch_spec=None):
511
mapping = self.source.get_mapping()
512
if revision_id is not None:
513
interesting_heads = [revision_id]
514
elif fetch_spec is not None:
515
interesting_heads = fetch_spec.heads
517
interesting_heads = None
518
def determine_wants(refs):
519
if interesting_heads is None:
520
ret = [sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")]
522
ret = [self.source.lookup_bzr_revision_id(revid)[0] for revid in interesting_heads if revid not in (None, NULL_REVISION)]
523
return [rev for rev in ret if not self.target.has_revision(self.source.lookup_foreign_revision_id(rev))]
524
(pack_hint, _, remote_refs) = self.fetch_objects(determine_wants, mapping, pb)
525
if pack_hint is not None and self.target._format.pack_compresses:
526
self.target.pack(hint=pack_hint)
530
_GIT_PROGRESS_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
531
def report_git_progress(pb, text):
532
text = text.rstrip("\r\n")
533
g = _GIT_PROGRESS_RE.match(text)
535
(text, pct, current, total) = g.groups()
536
pb.update(text, int(current), int(total))
538
pb.update(text, 0, 0)
541
class DetermineWantsRecorder(object):
543
def __init__(self, actual):
546
self.remote_refs = {}
548
def __call__(self, refs):
549
self.remote_refs = refs
550
self.wants = self.actual(refs)
554
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
555
"""InterRepository that copies revisions from a remote Git into a non-Git
558
def get_target_heads(self):
559
# FIXME: This should be more efficient
560
all_revs = self.target.all_revision_ids()
561
parent_map = self.target.get_parent_map(all_revs)
563
map(all_parents.update, parent_map.itervalues())
564
return set(all_revs) - all_parents
566
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
567
"""See `InterGitNonGitRepository`."""
192
568
def progress(text):
193
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
194
graph_walker = BzrFetchGraphWalker(self.target, mapping)
569
report_git_progress(pb, text)
570
store = BazaarObjectStore(self.target, mapping)
571
self.target.lock_write()
573
heads = self.get_target_heads()
574
graph_walker = store.get_graph_walker(
575
[store._lookup_revision_sha1(head) for head in heads])
576
wants_recorder = DetermineWantsRecorder(determine_wants)
580
create_pb = pb = ui.ui_factory.nested_progress_bar()
582
objects_iter = self.source.fetch_objects(
583
wants_recorder, graph_walker, store.get_raw,
585
(pack_hint, last_rev) = import_git_objects(self.target, mapping,
586
objects_iter, store, wants_recorder.wants, pb, limit)
587
return (pack_hint, last_rev, wants_recorder.remote_refs)
595
def is_compatible(source, target):
596
"""Be compatible with GitRepository."""
597
return (isinstance(source, RemoteGitRepository) and
598
target.supports_rich_root() and
599
not isinstance(target, GitRepository) and
600
target.texts is not None)
603
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
604
"""InterRepository that copies revisions from a local Git into a non-Git
607
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
608
"""See `InterGitNonGitRepository`."""
609
remote_refs = self.source._git.get_refs()
610
wants = determine_wants(remote_refs)
197
613
create_pb = pb = ui.ui_factory.nested_progress_bar()
198
target_git_object_retriever = GitObjectConverter(self.target, mapping)
614
target_git_object_retriever = BazaarObjectStore(self.target, mapping)
201
616
self.target.lock_write()
203
self.target.start_write_group()
205
objects_iter = self.source.fetch_objects(determine_wants,
207
target_git_object_retriever.__getitem__,
209
import_git_objects(self.target, mapping, objects_iter,
210
target_git_object_retriever, pb)
212
self.target.commit_write_group()
618
(pack_hint, last_rev) = import_git_objects(self.target, mapping,
619
self.source._git.object_store,
620
target_git_object_retriever, wants, pb, limit)
621
return (pack_hint, last_rev, remote_refs)
214
623
self.target.unlock()
217
626
create_pb.finished()
219
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
222
mapping = self.source.get_mapping()
223
def determine_wants(heads):
224
if revision_id is None:
227
ret = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
228
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
229
return self.fetch_objects(determine_wants, mapping, pb)
232
629
def is_compatible(source, target):
233
630
"""Be compatible with GitRepository."""
234
# FIXME: Also check target uses VersionedFile
235
return (isinstance(source, GitRepository) and
631
return (isinstance(source, LocalGitRepository) and
236
632
target.supports_rich_root() and
237
not isinstance(target, GitRepository))
240
class InterGitRepository(InterRepository):
242
_matching_repo_format = GitFormat()
245
def _get_repo_format_to_test():
248
def copy_content(self, revision_id=None, pb=None):
249
"""See InterRepository.copy_content."""
250
self.fetch(revision_id, pb, find_ghosts=False)
252
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
255
mapping = self.source.get_mapping()
633
not isinstance(target, GitRepository) and
634
target.texts is not None)
637
class InterGitGitRepository(InterGitRepository):
638
"""InterRepository that copies between Git repositories."""
640
def fetch_objects(self, determine_wants, mapping, pb=None):
256
641
def progress(text):
257
info("git: %s", text)
642
trace.note("git: %s", text)
643
graphwalker = self.target._git.get_graph_walker()
644
if (isinstance(self.source, LocalGitRepository) and
645
isinstance(self.target, LocalGitRepository)):
646
refs = self.source._git.fetch(self.target._git, determine_wants,
648
return (None, None, refs)
649
elif (isinstance(self.source, LocalGitRepository) and
650
isinstance(self.target, RemoteGitRepository)):
651
raise NotImplementedError
652
elif (isinstance(self.source, RemoteGitRepository) and
653
isinstance(self.target, LocalGitRepository)):
654
f, commit = self.target._git.object_store.add_thin_pack()
656
refs = self.source.bzrdir.root_transport.fetch_pack(
657
determine_wants, graphwalker, f.write, progress)
659
return (None, None, refs)
666
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
667
mapping=None, fetch_spec=None, branches=None):
669
mapping = self.source.get_mapping()
258
670
r = self.target._git
259
if revision_id is None:
260
determine_wants = lambda x: [y for y in x.values() if not y in r.object_store]
671
if revision_id is not None:
262
672
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
673
elif fetch_spec is not None:
674
args = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in fetch_spec.heads]
675
if branches is not None:
676
determine_wants = lambda x: [x[y] for y in branches if not x[y] in r.object_store]
677
elif fetch_spec is None and revision_id is None:
678
determine_wants = r.object_store.determine_wants_all
263
680
determine_wants = lambda x: [y for y in args if not y in r.object_store]
265
graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)
266
f, commit = r.object_store.add_pack()
268
self.source._git.fetch_pack(path, determine_wants, graphwalker, f.write, progress)
681
self.fetch_objects(determine_wants, mapping)
276
684
def is_compatible(source, target):
277
685
"""Be compatible with GitRepository."""
278
return (isinstance(source, GitRepository) and
686
return (isinstance(source, GitRepository) and
279
687
isinstance(target, GitRepository))