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
19
from bzrlib.inventory import Inventory
20
from bzrlib.repository import InterRepository
21
from bzrlib.trace import info
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 (
22
42
from bzrlib.tsort import topo_sort
24
from bzrlib.plugins.git import git
44
from bzrlib.plugins.git.converter import (
25
47
from bzrlib.plugins.git.repository import (
30
from bzrlib.plugins.git.remote import RemoteGitRepository
32
from dulwich.objects import Commit
34
from cStringIO import StringIO
52
from bzrlib.plugins.git.remote import (
37
57
class BzrFetchGraphWalker(object):
58
"""GraphWalker implementation that uses a Bazaar repository."""
39
60
def __init__(self, repository, mapping):
40
61
self.repository = repository
63
87
self.heads.update([p for p in ps if not p in self.done])
66
return self.mapping.revision_id_bzr_to_foreign(ret)
90
return self.mapping.revision_id_bzr_to_foreign(ret)[0]
67
91
except InvalidRevisionId:
72
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
96
def import_git_blob(texts, mapping, path, blob, inv, parent_invs, shagitmap,
73
98
"""Import a git blob object into a bzr repository.
75
:param repo: bzr repository
100
:param texts: VersionedFiles to add to
76
101
:param path: Path in the tree
77
102
:param blob: A git blob
103
:return: Inventory entry
79
105
file_id = mapping.generate_file_id(path)
80
text_revision = inv.revision_id
81
repo.texts.add_lines((file_id, text_revision),
82
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
83
osutils.split_lines(blob.data))
84
106
ie = inv.add_path(path, "file", file_id)
85
ie.revision = text_revision
86
107
ie.text_size = len(blob.data)
87
108
ie.text_sha1 = osutils.sha_string(blob.data)
88
109
ie.executable = executable
91
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
110
# See if this is the same revision as one of the parents unchanged
112
for pinv in parent_invs:
113
if not file_id in pinv:
115
if pinv[file_id].text_sha1 == ie.text_sha1:
116
ie.revision = pinv[file_id].revision
118
parent_keys.append((file_id, pinv[file_id].revision))
119
ie.revision = inv.revision_id
120
assert file_id is not None
121
assert ie.revision is not None
122
texts.add_lines((file_id, ie.revision), parent_keys,
123
osutils.split_lines(blob.data))
124
shagitmap.add_entry(blob.sha().hexdigest(), "blob",
125
(ie.file_id, ie.revision))
129
def import_git_tree(texts, mapping, path, tree, inv, parent_invs, shagitmap,
92
131
"""Import a git tree object into a bzr repository.
94
:param repo: A Bzr repository object
133
:param texts: VersionedFiles object to add to
95
134
:param path: Path in the tree
96
135
:param tree: A git tree object
97
136
:param inv: Inventory object
99
138
file_id = mapping.generate_file_id(path)
100
text_revision = inv.revision_id
101
repo.texts.add_lines((file_id, text_revision),
102
[(file_id, p[file_id].revision) for p in parent_invs if file_id in p],
104
139
ie = inv.add_path(path, "directory", file_id)
105
ie.revision = text_revision
142
for pinv in parent_invs:
143
if not file_id in pinv:
146
tree_sha = shagitmap.lookup_tree(path, pinv[file_id].revision)
150
if tree_sha == tree.id:
151
ie.revision = pinv[file_id].revision
153
parent_keys.append((file_id, pinv[file_id].revision))
154
if ie.revision is None:
155
ie.revision = inv.revision_id
156
texts.add_lines((file_id, ie.revision), parent_keys, [])
157
shagitmap.add_entry(tree.id, "tree", (file_id, ie.revision))
106
158
for mode, name, hexsha in tree.entries():
107
159
entry_kind = (mode & 0700000) / 0100000
108
160
basename = name.decode("utf-8")
110
162
child_path = name
112
164
child_path = urlutils.join(path, name)
165
obj = lookup_object(hexsha)
113
166
if entry_kind == 0:
114
tree = lookup_object(hexsha)
115
import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
167
import_git_tree(texts, mapping, child_path, obj, inv, parent_invs,
168
shagitmap, lookup_object)
116
169
elif entry_kind == 1:
117
blob = lookup_object(hexsha)
118
170
fs_mode = mode & 0777
119
import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
171
import_git_blob(texts, mapping, child_path, obj, inv, parent_invs,
172
shagitmap, bool(fs_mode & 0111))
121
174
raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
124
def import_git_objects(repo, mapping, object_iter, pb=None):
178
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
125
180
"""Import a set of git objects into a bzr repository.
127
182
:param repo: Bazaar repository
129
184
:param object_iter: Iterator over Git objects.
131
186
# TODO: a more (memory-)efficient implementation of this
133
for i, o in enumerate(object_iter):
135
pb.update("fetching objects", i)
140
190
# Find and convert commit objects
141
for o in objects.itervalues():
191
for o in object_iter.iterobjects():
142
192
if isinstance(o, Commit):
143
193
rev = mapping.import_commit(o)
144
root_trees[rev.revision_id] = objects[o.tree]
194
root_trees[rev.revision_id] = object_iter[o.tree]
145
195
revisions[rev.revision_id] = rev
146
196
graph.append((rev.revision_id, rev.parent_ids))
197
target_git_object_retriever._idmap.add_entry(o.sha().hexdigest(),
198
"commit", (rev.revision_id, o._tree))
147
199
# Order the revisions
148
200
# Create the inventory objects
149
201
for i, revid in enumerate(topo_sort(graph)):
157
209
inv = Inventory()
158
210
inv.revision_id = rev.revision_id
159
211
def lookup_object(sha):
162
return reconstruct_git_object(repo, mapping, sha)
212
if sha in object_iter:
213
return object_iter[sha]
214
return target_git_object_retriever[sha]
163
215
parent_invs = [repo.get_inventory(r) for r in rev.parent_ids]
164
import_git_tree(repo, mapping, "", root_tree, inv, parent_invs, lookup_object)
216
import_git_tree(repo.texts, mapping, "", root_tree, inv, parent_invs,
217
target_git_object_retriever._idmap, lookup_object)
165
218
repo.add_revision(rev.revision_id, rev, inv)
168
def reconstruct_git_commit(repo, rev):
169
raise NotImplementedError(self.reconstruct_git_commit)
172
def reconstruct_git_object(repo, mapping, sha):
174
revid = mapping.revision_id_foreign_to_bzr(sha)
176
rev = repo.get_revision(revid)
177
except NoSuchRevision:
180
return reconstruct_git_commit(rev)
184
raise KeyError("No such object %s" % sha)
187
class InterGitRepository(InterRepository):
189
_matching_repo_format = GitFormat()
219
target_git_object_retriever._idmap.commit()
222
class InterGitNonGitRepository(InterRepository):
223
"""InterRepository that copies revisions from a Git into a non-Git
226
_matching_repo_format = GitRepositoryFormat()
192
229
def _get_repo_format_to_test():
196
233
"""See InterRepository.copy_content."""
197
234
self.fetch(revision_id, pb, find_ghosts=False)
199
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
202
mapping = self.source.get_mapping()
236
def fetch_objects(self, determine_wants, mapping, pb=None):
203
237
def progress(text):
204
pb.note("git: %s", text)
205
def determine_wants(heads):
206
if revision_id is None:
209
ret = [mapping.revision_id_bzr_to_foreign(revision_id)]
210
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
238
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
211
239
graph_walker = BzrFetchGraphWalker(self.target, mapping)
214
242
create_pb = pb = ui.ui_factory.nested_progress_bar()
243
target_git_object_retriever = GitObjectConverter(self.target, mapping)
216
246
self.target.lock_write()
218
248
self.target.start_write_group()
220
import_git_objects(self.target, mapping,
221
iter(self.source.fetch_objects(determine_wants, graph_walker,
250
objects_iter = self.source.fetch_objects(determine_wants,
252
target_git_object_retriever.__getitem__,
254
import_git_objects(self.target, mapping, objects_iter,
255
target_git_object_retriever, pb)
224
257
self.target.commit_write_group()
229
262
create_pb.finished()
264
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
265
mapping=None, fetch_spec=None):
266
self.fetch_refs(revision_id=revision_id, pb=pb, find_ghosts=find_ghosts,
267
mapping=mapping, fetch_spec=fetch_spec)
269
def fetch_refs(self, revision_id=None, pb=None, find_ghosts=False,
270
mapping=None, fetch_spec=None):
272
mapping = self.source.get_mapping()
273
if revision_id is not None:
274
interesting_heads = [revision_id]
275
elif fetch_spec is not None:
276
interesting_heads = fetch_spec.heads
278
interesting_heads = None
280
def determine_wants(refs):
282
if interesting_heads is None:
283
ret = [sha for (ref, sha) in refs.iteritems() if not ref.endswith("^{}")]
285
ret = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in interesting_heads]
286
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
287
self.fetch_objects(determine_wants, mapping, pb)
232
291
def is_compatible(source, target):
233
292
"""Be compatible with GitRepository."""
234
293
# FIXME: Also check target uses VersionedFile
235
294
return (isinstance(source, GitRepository) and
236
target.supports_rich_root())
295
target.supports_rich_root() and
296
not isinstance(target, GitRepository))
299
class InterGitRepository(InterRepository):
300
"""InterRepository that copies between Git repositories."""
302
_matching_repo_format = GitRepositoryFormat()
305
def _get_repo_format_to_test():
308
def copy_content(self, revision_id=None, pb=None):
309
"""See InterRepository.copy_content."""
310
self.fetch(revision_id, pb, find_ghosts=False)
312
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
313
mapping=None, fetch_spec=None):
315
mapping = self.source.get_mapping()
317
trace.info("git: %s", text)
319
if revision_id is not None:
320
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
321
elif fetch_spec is not None:
322
args = [mapping.revision_id_bzr_to_foreign(revid)[0] for revid in fetch_spec.heads]
323
if fetch_spec is None and revision_id is None:
324
determine_wants = r.object_store.determine_wants_all
326
determine_wants = lambda x: [y for y in args if not y in r.object_store]
328
graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)
329
f, commit = r.object_store.add_pack()
331
self.source._git.fetch_pack(path, determine_wants, graphwalker, f.write, progress)
339
def is_compatible(source, target):
340
"""Be compatible with GitRepository."""
341
return (isinstance(source, GitRepository) and
342
isinstance(target, GitRepository))