15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
from bzrlib import osutils, ui, urlutils
18
from bzrlib.errors import InvalidRevisionId
18
from bzrlib.errors import InvalidRevisionId, NoSuchRevision
19
19
from bzrlib.inventory import Inventory
20
20
from bzrlib.repository import InterRepository
21
21
from bzrlib.trace import info
22
22
from bzrlib.tsort import topo_sort
24
from bzrlib.plugins.git import git
25
24
from bzrlib.plugins.git.repository import (
26
25
LocalGitRepository,
29
from bzrlib.plugins.git.converter import GitObjectConverter
30
30
from bzrlib.plugins.git.remote import RemoteGitRepository
33
from dulwich.client import SimpleFetchGraphWalker
32
34
from dulwich.objects import Commit
34
36
from cStringIO import StringIO
37
39
class BzrFetchGraphWalker(object):
40
"""GraphWalker implementation that uses a Bazaar repository."""
39
42
def __init__(self, repository, mapping):
40
43
self.repository = repository
43
46
self.heads = set(repository.all_revision_ids())
50
return iter(self.next, None)
46
52
def ack(self, sha):
47
53
revid = self.mapping.revision_id_foreign_to_bzr(sha)
50
56
def remove(self, revid):
51
57
self.done.add(revid)
58
if revid in self.heads:
53
59
self.heads.remove(revid)
54
60
if revid in self.parents:
55
61
for p in self.parents[revid]:
63
69
self.heads.update([p for p in ps if not p in self.done])
66
return self.mapping.revision_id_bzr_to_foreign(ret)
72
return self.mapping.revision_id_bzr_to_foreign(ret)[0]
67
73
except InvalidRevisionId:
72
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, executable):
78
def import_git_blob(repo, mapping, path, blob, inv, parent_invs, gitmap, executable):
73
79
"""Import a git blob object into a bzr repository.
75
81
:param repo: bzr repository
86
92
ie.text_size = len(blob.data)
87
93
ie.text_sha1 = osutils.sha_string(blob.data)
88
94
ie.executable = executable
91
def import_git_tree(repo, mapping, path, tree, inv, parent_invs, lookup_object):
95
gitmap._idmap.add_entry(blob.sha().hexdigest(), "blob", (ie.file_id, ie.revision))
98
def import_git_tree(repo, mapping, path, tree, inv, parent_invs,
99
gitmap, lookup_object):
92
100
"""Import a git tree object into a bzr repository.
94
102
:param repo: A Bzr repository object
104
112
ie = inv.add_path(path, "directory", file_id)
105
113
ie.revision = text_revision
114
gitmap._idmap.add_entry(tree.sha().hexdigest(), "tree", (file_id, text_revision))
106
115
for mode, name, hexsha in tree.entries():
107
116
entry_kind = (mode & 0700000) / 0100000
108
117
basename = name.decode("utf-8")
112
121
child_path = urlutils.join(path, name)
113
122
if entry_kind == 0:
114
123
tree = lookup_object(hexsha)
115
import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, lookup_object)
124
import_git_tree(repo, mapping, child_path, tree, inv, parent_invs, gitmap, lookup_object)
116
125
elif entry_kind == 1:
117
126
blob = lookup_object(hexsha)
118
127
fs_mode = mode & 0777
119
import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, bool(fs_mode & 0111))
128
import_git_blob(repo, mapping, child_path, blob, inv, parent_invs, gitmap, bool(fs_mode & 0111))
121
130
raise AssertionError("Unknown blob kind, perms=%r." % (mode,))
124
def import_git_objects(repo, mapping, object_iter, pb=None):
133
def import_git_objects(repo, mapping, object_iter, target_git_object_retriever,
125
135
"""Import a set of git objects into a bzr repository.
127
137
:param repo: Bazaar repository
129
139
:param object_iter: Iterator over Git objects.
131
141
# TODO: a more (memory-)efficient implementation of this
133
for i, o in enumerate(object_iter):
135
pb.update("fetching objects", i)
140
145
# Find and convert commit objects
141
for o in objects.itervalues():
146
for o in object_iter.iterobjects():
142
147
if isinstance(o, Commit):
143
148
rev = mapping.import_commit(o)
144
root_trees[rev.revision_id] = objects[o.tree]
149
root_trees[rev.revision_id] = object_iter[o.tree]
145
150
revisions[rev.revision_id] = rev
146
151
graph.append((rev.revision_id, rev.parent_ids))
152
target_git_object_retriever._idmap.add_entry(o.sha().hexdigest(), "commit", (rev.revision_id, o._tree))
147
153
# Order the revisions
148
154
# Create the inventory objects
149
155
for i, revid in enumerate(topo_sort(graph)):
157
163
inv = Inventory()
158
164
inv.revision_id = rev.revision_id
159
165
def lookup_object(sha):
162
return reconstruct_git_object(repo, mapping, sha)
166
if sha in object_iter:
167
return object_iter[sha]
168
return target_git_object_retriever[sha]
163
169
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)
170
import_git_tree(repo, mapping, "", root_tree, inv, parent_invs,
171
target_git_object_retriever, lookup_object)
165
172
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):
175
class InterGitNonGitRepository(InterRepository):
189
177
_matching_repo_format = GitFormat()
196
184
"""See InterRepository.copy_content."""
197
185
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()
187
def fetch_objects(self, determine_wants, mapping, pb=None):
203
188
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))]
189
pb.update("git: %s" % text.rstrip("\r\n"), 0, 0)
211
190
graph_walker = BzrFetchGraphWalker(self.target, mapping)
214
193
create_pb = pb = ui.ui_factory.nested_progress_bar()
194
target_git_object_retriever = GitObjectConverter(self.target, mapping)
216
197
self.target.lock_write()
218
199
self.target.start_write_group()
220
import_git_objects(self.target, mapping,
221
iter(self.source.fetch_objects(determine_wants, graph_walker,
201
objects_iter = self.source.fetch_objects(determine_wants,
203
target_git_object_retriever.__getitem__,
205
import_git_objects(self.target, mapping, objects_iter,
206
target_git_object_retriever, pb)
224
208
self.target.commit_write_group()
229
213
create_pb.finished()
215
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
218
mapping = self.source.get_mapping()
219
def determine_wants(heads):
220
if revision_id is None:
223
ret = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
224
return [rev for rev in ret if not self.target.has_revision(mapping.revision_id_foreign_to_bzr(rev))]
225
return self.fetch_objects(determine_wants, mapping, pb)
232
228
def is_compatible(source, target):
233
229
"""Be compatible with GitRepository."""
234
230
# FIXME: Also check target uses VersionedFile
235
231
return (isinstance(source, GitRepository) and
236
target.supports_rich_root())
232
target.supports_rich_root() and
233
not isinstance(target, GitRepository))
236
class InterGitRepository(InterRepository):
238
_matching_repo_format = GitFormat()
241
def _get_repo_format_to_test():
244
def copy_content(self, revision_id=None, pb=None):
245
"""See InterRepository.copy_content."""
246
self.fetch(revision_id, pb, find_ghosts=False)
248
def fetch(self, revision_id=None, pb=None, find_ghosts=False,
251
mapping = self.source.get_mapping()
253
info("git: %s", text)
255
if revision_id is None:
256
determine_wants = lambda x: [y for y in x.values() if not y in r.object_store]
258
args = [mapping.revision_id_bzr_to_foreign(revision_id)[0]]
259
determine_wants = lambda x: [y for y in args if not y in r.object_store]
261
graphwalker = SimpleFetchGraphWalker(r.heads().values(), r.get_parents)
262
f, commit = r.object_store.add_pack()
264
self.source._git.fetch_pack(path, determine_wants, graphwalker, f.write, progress)
272
def is_compatible(source, target):
273
"""Be compatible with GitRepository."""
274
return (isinstance(source, GitRepository) and
275
isinstance(target, GitRepository))