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 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 (
17
from bzrlib import osutils, ui, urlutils
18
from bzrlib.errors import InvalidRevisionId
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
83
24
from bzrlib.plugins.git.repository import (
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,
29
from bzrlib.plugins.git.remote import RemoteGitRepository
32
from dulwich.client import SimpleFetchGraphWalker
33
from dulwich.objects import Commit
35
from cStringIO import StringIO
38
class BzrFetchGraphWalker(object):
39
"""GraphWalker implementation that uses a Bazaar repository."""
41
def __init__(self, repository, mapping):
42
self.repository = repository
43
self.mapping = mapping
45
self.heads = set(repository.all_revision_ids())
49
return iter(self.next, None)
52
revid = self.mapping.revision_id_foreign_to_bzr(sha)
55
def remove(self, revid):
57
if revid in self.heads:
58
self.heads.remove(revid)
59
if revid in self.parents:
60
for p in self.parents[revid]:
65
ret = self.heads.pop()
66
ps = self.repository.get_parent_map([ret])[ret]
67
self.parents[ret] = ps
68
self.heads.update([p for p in ps if not p in self.done])
71
return self.mapping.revision_id_bzr_to_foreign(ret)[0]
72
except InvalidRevisionId:
77
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
94
78
"""Import a git blob object into a bzr repository.
96
:param texts: VersionedFiles to add to
80
:param repo: bzr repository
97
81
:param path: Path in the tree
98
82
:param blob: A git blob
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):
84
file_id = mapping.generate_file_id(path)
85
text_revision = inv.revision_id
86
repo.texts.add_lines((file_id, text_revision),
87
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
88
osutils.split_lines(blob.data))
89
ie = inv.add_path(path, "file", file_id)
90
ie.revision = text_revision
91
ie.text_size = len(blob.data)
92
ie.text_sha1 = osutils.sha_string(blob.data)
93
ie.executable = executable
96
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
229
97
"""Import a git tree object into a bzr repository.
231
:param texts: VersionedFiles object to add to
232
:param path: Path in the tree (str)
233
:param name: Name of the tree (str)
99
:param repo: A Bzr repository object
100
:param path: Path in the tree
234
101
:param tree: A git tree object
235
:param base_inv: Base inventory against which to return inventory delta
236
:return: Inventory delta for this subtree
102
:param inv: Inventory object
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, verifiers = 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
fileid_map = mapping.get_fileid_map(lookup_object, o.tree)
363
inv_delta, unusual_modes = import_git_tree(repo.texts,
364
mapping, "", "", (base_tree, o.tree), base_inv,
365
None, rev.revision_id, [p.inventory for p in parent_trees],
366
lookup_object, (base_mode, stat.S_IFDIR), store_updater,
367
fileid_map.lookup_file_id,
368
allow_submodules=getattr(repo._format, "supports_tree_reference", False))
369
if unusual_modes != {}:
370
for path, mode in unusual_modes.iteritems():
371
warn_unusual_mode(rev.foreign_revid, path, mode)
372
mapping.import_unusual_file_modes(rev, unusual_modes)
374
basis_id = rev.parent_ids[0]
376
basis_id = NULL_REVISION
378
rev.inventory_sha1, inv = repo.add_inventory_by_delta(basis_id,
379
inv_delta, rev.revision_id, rev.parent_ids, base_inv)
380
# FIXME: Check verifiers
381
testament = StrictTestament3(rev, inv)
382
calculated_verifiers = { "testament3-sha1": testament.as_sha1() }
383
if roundtrip_revid is not None:
384
original_revid = rev.revision_id
385
rev.revision_id = roundtrip_revid
386
if calculated_verifiers != verifiers:
387
trace.mutter("Testament SHA1 %r for %r did not match %r.",
388
calculated_verifiers["testament3-sha1"],
389
rev.revision_id, verifiers["testament3-sha1"])
390
rev.revision_id = original_revid
391
store_updater.add_object(o, calculated_verifiers, None)
392
store_updater.finish()
393
ret_tree = RevisionTree(repo, inv, rev.revision_id)
394
trees_cache.add(ret_tree)
395
repo.add_revision(rev.revision_id, rev)
396
if "verify" in debug.debug_flags:
397
verify_commit_reconstruction(target_git_object_retriever,
398
lookup_object, o, rev, ret_tree, parent_trees, mapping,
402
def import_git_objects(repo, mapping, object_iter,
403
target_git_object_retriever, heads, pb=None, limit=None):
104
file_id = mapping.generate_file_id(path)
105
text_revision = inv.revision_id
106
repo.texts.add_lines((file_id, text_revision),
107
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
109
ie = inv.add_path(path, "directory", file_id)
110
ie.revision = text_revision
111
for mode, name, hexsha in tree.entries():
112
entry_kind = (mode & 0700000) / 0100000
113
basename = name.decode("utf-8")
117
child_path = urlutils.join(path, name)
119
tree = lookup_object(hexsha)
120
import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
121
elif entry_kind == 1:
122
blob = lookup_object(hexsha)
123
fs_mode = mode & 0777
124
import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
126
raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
129
def import_git_objects(repo, mapping, object_iter, pb=None):
404
130
"""Import a set of git objects into a bzr repository.
406
:param repo: Target Bazaar repository
132
:param repo: Bazaar repository
407
133
:param mapping: Mapping to use
408
134
:param object_iter: Iterator over Git objects.
409
:return: Tuple with pack hints and last imported revision id
411
def lookup_object(sha):
413
return object_iter[sha]
415
return target_git_object_retriever[sha]
136
# TODO: a more (memory-)efficient implementation of this
418
heads = list(set(heads))
419
trees_cache = LRUTreeCache(repo)
420
140
# Find and convert commit objects
423
pb.update("finding revisions to fetch", len(graph), None)
425
assert isinstance(head, str)
427
o = lookup_object(head)
141
for o in object_iter.iterobjects():
430
142
if isinstance(o, Commit):
431
rev, roundtrip_revid, verifiers = mapping.import_commit(o,
433
if (repo.has_revision(rev.revision_id) or
434
(roundtrip_revid and repo.has_revision(roundtrip_revid))):
436
graph.append((o.id, o.parents))
437
heads.extend([p for p in o.parents if p not in checked])
438
elif isinstance(o, Tag):
439
if o.object[1] not in checked:
440
heads.append(o.object[1])
442
trace.warning("Unable to import head object %r" % o)
143
rev = mapping.import_commit(o)
144
root_trees[rev.revision_id] = object_iter[o.tree]
145
revisions[rev.revision_id] = rev
146
graph.append((rev.revision_id, rev.parent_ids))
445
147
# Order the revisions
446
148
# Create the inventory objects
448
revision_ids = topo_sort(graph)
450
if limit is not None:
451
revision_ids = revision_ids[:limit]
453
for offset in range(0, len(revision_ids), batch_size):
454
target_git_object_retriever.start_write_group()
456
repo.start_write_group()
458
for i, head in enumerate(
459
revision_ids[offset:offset+batch_size]):
461
pb.update("fetching revisions", offset+i,
463
import_git_commit(repo, mapping, head, lookup_object,
464
target_git_object_retriever, trees_cache)
467
repo.abort_write_group()
470
hint = repo.commit_write_group()
472
pack_hints.extend(hint)
474
target_git_object_retriever.abort_write_group()
477
target_git_object_retriever.commit_write_group()
478
return pack_hints, last_imported
481
class InterGitRepository(InterRepository):
483
_matching_repo_format = GitRepositoryFormat()
149
for i, revid in enumerate(topo_sort(graph)):
151
pb.update("fetching revisions", i, len(graph))
152
root_tree = root_trees[revid]
153
rev = revisions[revid]
154
# 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
156
# path; this may involve adding them more than once.
158
inv.revision_id = rev.revision_id
159
def lookup_object(sha):
160
if sha in object_iter:
161
return object_iter[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,
166
repo.add_revision(rev.revision_id, rev, inv)
169
def reconstruct_git_commit(repo, rev):
170
raise NotImplementedError(self.reconstruct_git_commit)
173
def reconstruct_git_object(repo, mapping, sha):
175
revid = mapping.revision_id_foreign_to_bzr(sha)
177
rev = repo.get_revision(revid)
178
except NoSuchRevision:
181
return reconstruct_git_commit(rev)
185
raise KeyError("No such object %s" % sha)
188
class InterGitNonGitRepository(InterRepository):
190
_matching_repo_format = GitFormat()
486
193
def _get_repo_format_to_test():
490
197
"""See InterRepository.copy_content."""
491
198
self.fetch(revision_id, pb, find_ghosts=False)
494
class InterGitNonGitRepository(InterGitRepository):
495
"""Base InterRepository that copies revisions from a Git into a non-Git
498
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
499
"""Fetch objects from a remote server.
501
:param determine_wants: determine_wants callback
502
:param mapping: BzrGitMapping to use
503
:param pb: Optional progress bar
504
:param limit: Maximum number of commits to import.
505
:return: Tuple with pack hint, last imported revision id and remote refs
507
raise NotImplementedError(self.fetch_objects)
509
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
510
mapping=None, fetch_spec=None):
200
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
511
202
if mapping is None:
512
203
mapping = self.source.get_mapping()
513
if revision_id is not None:
514
interesting_heads = [revision_id]
515
elif fetch_spec is not None:
516
interesting_heads = fetch_spec.heads
518
interesting_heads = None
519
def determine_wants(refs):
520
if interesting_heads is None:
521
ret = [sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")]
523
ret = [self.source.lookup_bzr_revision_id(revid)[0] for revid in interesting_heads if revid not in (None, NULL_REVISION)]
524
return [rev for rev in ret if not self.target.has_revision(self.source.lookup_foreign_revision_id(rev))]
525
(pack_hint, _, remote_refs) = self.fetch_objects(determine_wants, mapping, pb)
526
if pack_hint is not None and self.target._format.pack_compresses:
527
self.target.pack(hint=pack_hint)
531
_GIT_PROGRESS_RE = re.compile(r"(.*?): +(\d+)% \((\d+)/(\d+)\)")
532
def report_git_progress(pb, text):
533
text = text.rstrip("\r\n")
534
g = _GIT_PROGRESS_RE.match(text)
536
(text, pct, current, total) = g.groups()
537
pb.update(text, int(current), int(total))
539
pb.update(text, 0, 0)
542
class DetermineWantsRecorder(object):
544
def __init__(self, actual):
547
self.remote_refs = {}
549
def __call__(self, refs):
550
self.remote_refs = refs
551
self.wants = self.actual(refs)
555
class InterRemoteGitNonGitRepository(InterGitNonGitRepository):
556
"""InterRepository that copies revisions from a remote Git into a non-Git
559
def get_target_heads(self):
560
# FIXME: This should be more efficient
561
all_revs = self.target.all_revision_ids()
562
parent_map = self.target.get_parent_map(all_revs)
564
map(all_parents.update, parent_map.itervalues())
565
return set(all_revs) - all_parents
567
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
568
"""See `InterGitNonGitRepository`."""
569
204
def progress(text):
570
report_git_progress(pb, text)
571
store = BazaarObjectStore(self.target, mapping)
572
self.target.lock_write()
574
heads = self.get_target_heads()
575
graph_walker = store.get_graph_walker(
576
[store._lookup_revision_sha1(head) for head in heads])
577
wants_recorder = DetermineWantsRecorder(determine_wants)
581
create_pb = pb = ui.ui_factory.nested_progress_bar()
583
objects_iter = self.source.fetch_objects(
584
wants_recorder, graph_walker, store.get_raw,
586
(pack_hint, last_rev) = import_git_objects(self.target, mapping,
587
objects_iter, store, wants_recorder.wants, pb, limit)
588
return (pack_hint, last_rev, wants_recorder.remote_refs)
596
def is_compatible(source, target):
597
"""Be compatible with GitRepository."""
598
return (isinstance(source, RemoteGitRepository) and
599
target.supports_rich_root() and
600
not isinstance(target, GitRepository) and
601
target.texts is not None)
604
class InterLocalGitNonGitRepository(InterGitNonGitRepository):
605
"""InterRepository that copies revisions from a local Git into a non-Git
608
def fetch_objects(self, determine_wants, mapping, pb=None, limit=None):
609
"""See `InterGitNonGitRepository`."""
610
remote_refs = self.source._git.get_refs()
611
wants = determine_wants(remote_refs)
205
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
206
def determine_wants(heads):
207
if revision_id is None:
210
ret = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
211
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
212
graph_walker = BzrFetchGraphWalker(self.target, mapping)
614
215
create_pb = pb = ui.ui_factory.nested_progress_bar()
615
target_git_object_retriever = BazaarObjectStore(self.target, mapping)
617
217
self.target.lock_write()
619
(pack_hint, last_rev) = import_git_objects(self.target, mapping,
620
self.source._git.object_store,
621
target_git_object_retriever, wants, pb, limit)
622
return (pack_hint, last_rev, remote_refs)
219
self.target.start_write_group()
221
objects_iter = self.source.fetch_objects(determine_wants,
222
graph_walker, progress)
223
import_git_objects(self.target, mapping, objects_iter, pb)
225
self.target.commit_write_group()
624
227
self.target.unlock()