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 cStringIO import StringIO
19
from dulwich.client import (
20
SimpleFetchGraphWalker,
22
from dulwich.objects import (
32
from bzrlib.errors import (
36
from bzrlib.inventory import (
39
from bzrlib.repository 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
42
22
from bzrlib.tsort import topo_sort
44
from bzrlib.plugins.git.converter import (
47
24
from bzrlib.plugins.git.repository import (
52
from bzrlib.plugins.git.remote import (
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
57
38
class BzrFetchGraphWalker(object):
96
def import_git_blob(texts, mapping, path, blob, inv, parent_invs, shagitmap,
77
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
98
78
"""Import a git blob object into a bzr repository.
100
:param texts: VersionedFiles to add to
80
:param repo: bzr repository
101
81
:param path: Path in the tree
102
82
:param blob: A git blob
104
84
file_id = mapping.generate_file_id(path)
105
85
text_revision = inv.revision_id
106
assert file_id is not None
107
assert text_revision is not None
108
texts.add_lines((file_id, text_revision),
86
repo.texts.add_lines((file_id, text_revision),
109
87
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
110
88
osutils.split_lines(blob.data))
111
89
ie = inv.add_path(path, "file", file_id)
113
91
ie.text_size = len(blob.data)
114
92
ie.text_sha1 = osutils.sha_string(blob.data)
115
93
ie.executable = executable
116
shagitmap.add_entry(blob.sha().hexdigest(), "blob",
117
(ie.file_id, ie.revision))
120
def import_git_tree(texts, mapping, path, tree, inv, parent_invs, shagitmap,
96
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
122
97
"""Import a git tree object into a bzr repository.
124
:param texts: VersionedFiles object to add to
99
:param repo: A Bzr repository object
125
100
:param path: Path in the tree
126
101
:param tree: A git tree object
127
102
:param inv: Inventory object
129
104
file_id = mapping.generate_file_id(path)
130
105
text_revision = inv.revision_id
131
texts.add_lines((file_id, text_revision),
106
repo.texts.add_lines((file_id, text_revision),
132
107
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
134
109
ie = inv.add_path(path, "directory", file_id)
135
110
ie.revision = text_revision
136
shagitmap.add_entry(tree.id, "tree", (file_id, text_revision))
137
111
for mode, name, hexsha in tree.entries():
138
112
entry_kind = (mode & 0700000) / 0100000
139
113
basename = name.decode("utf-8")
141
115
child_path = name
143
117
child_path = urlutils.join(path, name)
144
obj = lookup_object(hexsha)
145
118
if entry_kind == 0:
146
import_git_tree(texts, mapping, child_path, obj, inv, parent_invs,
147
shagitmap, lookup_object)
119
tree = lookup_object(hexsha)
120
import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
148
121
elif entry_kind == 1:
122
blob = lookup_object(hexsha)
149
123
fs_mode = mode & 0777
150
import_git_blob(texts, mapping, child_path, obj, inv, parent_invs,
151
shagitmap, bool(fs_mode & 0111))
124
import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
153
126
raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
156
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
129
def import_git_objects(repo, mapping, num_objects, object_iter, pb=None):
158
130
"""Import a set of git objects into a bzr repository.
160
132
:param repo: Bazaar repository
161
133
:param mapping: Mapping to use
134
:param num_objects: Number of objects.
162
135
:param object_iter: Iterator over Git objects.
164
137
# TODO: a more (memory-)efficient implementation of this
139
for i, (o, _) in enumerate(object_iter):
141
pb.update("fetching objects", i, num_objects)
168
146
# Find and convert commit objects
169
for o in object_iter.iterobjects():
147
for o in objects.itervalues():
170
148
if isinstance(o, Commit):
171
149
rev = mapping.import_commit(o)
172
root_trees[rev.revision_id] = object_iter[o.tree]
150
root_trees[rev.revision_id] = objects[o.tree]
173
151
revisions[rev.revision_id] = rev
174
152
graph.append((rev.revision_id, rev.parent_ids))
175
target_git_object_retriever._idmap.add_entry(o.sha().hexdigest(),
176
"commit", (rev.revision_id, o._tree))
177
153
# Order the revisions
178
154
# Create the inventory objects
179
155
for i, revid in enumerate(topo_sort(graph)):
187
163
inv = Inventory()
188
164
inv.revision_id = rev.revision_id
189
165
def lookup_object(sha):
190
if sha in object_iter:
191
return object_iter[sha]
192
return target_git_object_retriever[sha]
168
return reconstruct_git_object(repo, mapping, sha)
193
169
parent_invs = [repo.get_inventory(r) for r in rev.parent_ids]
194
import_git_tree(repo.texts, mapping, "", root_tree, inv, parent_invs,
195
target_git_object_retriever._idmap, lookup_object)
170
import_git_tree(repo, mapping, "", root_tree, inv, parent_invs,
196
172
repo.add_revision(rev.revision_id, rev, inv)
175
def reconstruct_git_commit(repo, rev):
176
raise NotImplementedError(self.reconstruct_git_commit)
179
def reconstruct_git_object(repo, mapping, sha):
181
revid = mapping.revision_id_foreign_to_bzr(sha)
183
rev = repo.get_revision(revid)
184
except NoSuchRevision:
187
return reconstruct_git_commit(rev)
191
raise KeyError("No such object %s" % sha)
199
194
class InterGitNonGitRepository(InterRepository):
201
196
_matching_repo_format = GitFormat()
208
203
"""See InterRepository.copy_content."""
209
204
self.fetch(revision_id, pb, find_ghosts=False)
211
def fetch_objects(self, determine_wants, mapping, pb=None):
206
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
209
mapping = self.source.get_mapping()
212
210
def progress(text):
213
211
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
212
def determine_wants(heads):
213
if revision_id is None:
216
ret = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
217
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
214
218
graph_walker = BzrFetchGraphWalker(self.target, mapping)
217
221
create_pb = pb = ui.ui_factory.nested_progress_bar()
218
target_git_object_retriever = GitObjectConverter(self.target, mapping)
221
223
self.target.lock_write()
223
225
self.target.start_write_group()
225
objects_iter = self.source.fetch_objects(determine_wants,
227
target_git_object_retriever.__getitem__,
229
import_git_objects(self.target, mapping, objects_iter,
230
target_git_object_retriever, pb)
227
(num_objects, objects_iter) = \
228
self.source.fetch_objects(determine_wants,
229
graph_walker, progress)
230
import_git_objects(self.target, mapping, num_objects,
232
233
self.target.commit_write_group()
237
238
create_pb.finished()
239
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
240
mapping=None, fetch_spec=None):
241
self.fetch_refs(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts,
242
mapping=mapping, fetch_spec=fetch_spec)
244
def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
245
mapping=None, fetch_spec=None):
247
mapping = self.source.get_mapping()
248
if revision_id is not None:
249
interesting_heads = [revision_id]
250
elif fetch_spec is not None:
251
interesting_heads = fetch_spec.heads
253
interesting_heads = None
255
def determine_wants(refs):
257
if interesting_heads is None:
258
ret = [sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")]
260
ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads]
261
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
262
self.fetch_objects(determine_wants, mapping, pb)
266
241
def is_compatible(source, target):
267
242
"""Be compatible with GitRepository."""
284
259
self.fetch(revision_id, pb, find_ghosts=False)
286
261
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
287
mapping=None, fetch_spec=None):
288
263
if mapping is None:
289
264
mapping = self.source.get_mapping()
290
265
def progress(text):
291
trace.info("git: %s", text)
266
info("git: %s", text)
292
267
r = self.target._git
293
if revision_id is not None:
268
if revision_id is None:
269
determine_wants = lambda x: [y for y in x.values() if not y in r.object_store]
294
271
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
295
elif fetch_spec is not None:
296
args = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in fetch_spec.heads]
297
if fetch_spec is None and revision_id is None:
298
determine_wants = r.object_store.determine_wants_all
300
272
determine_wants = lambda x: [y for y in args if not y in r.object_store]
302
274
graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)