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