Mercurial > hg > hg-git
comparison git_handler.py @ 51:1421d04f1ad2
merged from dulwich
author | Scott Chacon <schacon@gmail.com> |
---|---|
date | Wed, 29 Apr 2009 11:04:45 -0700 |
parents | 3b62270c1fad d274092e3b24 |
children | 87d462a6b796 |
comparison
equal
deleted
inserted
replaced
47:3b62270c1fad | 51:1421d04f1ad2 |
---|---|
12 ShaFile, | 12 ShaFile, |
13 Tag, | 13 Tag, |
14 Tree, | 14 Tree, |
15 hex_to_sha | 15 hex_to_sha |
16 ) | 16 ) |
17 | 17 |
18 import math | 18 import math |
19 | 19 |
20 def seconds_to_offset(time): | 20 def seconds_to_offset(time): |
21 hours = (float(time) / 60 / 60) | 21 hours = (float(time) / 60 / 60) |
22 hour_diff = math.fmod(time, 60) | 22 hour_diff = math.fmod(time, 60) |
76 def map_git_get(self, hgsha): | 76 def map_git_get(self, hgsha): |
77 if hgsha in self._map_hg: | 77 if hgsha in self._map_hg: |
78 return self._map_hg[hgsha] | 78 return self._map_hg[hgsha] |
79 else: | 79 else: |
80 return None | 80 return None |
81 | 81 |
82 def load_map(self): | 82 def load_map(self): |
83 self._map_git = {} | 83 self._map_git = {} |
84 self._map_hg = {} | 84 self._map_hg = {} |
85 if os.path.exists(self.repo.join('git-mapfile')): | 85 if os.path.exists(self.repo.join('git-mapfile')): |
86 for line in self.repo.opener('git-mapfile'): | 86 for line in self.repo.opener('git-mapfile'): |
139 if key in self._config: | 139 if key in self._config: |
140 name = self._config[key] | 140 name = self._config[key] |
141 print "URL for " + remote_name + " : " + name | 141 print "URL for " + remote_name + " : " + name |
142 else: | 142 else: |
143 print "No remote named : " + remote_name | 143 print "No remote named : " + remote_name |
144 return | 144 return |
145 | 145 |
146 def remote_list(self): | 146 def remote_list(self): |
147 for key, value in self._config.iteritems(): | 147 for key, value in self._config.iteritems(): |
148 if key[0:6] == 'remote': | 148 if key[0:6] == 'remote': |
149 print key + "\t" + value | 149 print key + "\t" + value |
150 | 150 |
151 def remote_name_to_url(self, remote_name): | 151 def remote_name_to_url(self, remote_name): |
152 return self._config['remote.' + remote_name + '.url'] | 152 return self._config['remote.' + remote_name + '.url'] |
153 | 153 |
154 def update_references(self): | 154 def update_references(self): |
155 # TODO : if bookmarks exist, add them as git branches | 155 # TODO : if bookmarks exist, add them as git branches |
156 c = self.map_git_get(hex(self.repo.changelog.tip())) | 156 c = self.map_git_get(hex(self.repo.changelog.tip())) |
157 self.git.set_ref('refs/heads/master', c) | 157 self.git.set_ref('refs/heads/master', c) |
158 | 158 |
159 def export_git_objects(self): | 159 def export_git_objects(self): |
160 print "exporting git objects" | 160 print "exporting git objects" |
161 for rev in self.repo.changelog: | 161 for rev in self.repo.changelog: |
162 self.export_hg_commit(rev) | 162 self.export_hg_commit(rev) |
163 | 163 |
164 # convert this commit into git objects | 164 # convert this commit into git objects |
165 # go through the manifest, convert all blobs/trees we don't have | 165 # go through the manifest, convert all blobs/trees we don't have |
166 # write the commit object (with metadata info) | 166 # write the commit object (with metadata info) |
167 def export_hg_commit(self, rev): | 167 def export_hg_commit(self, rev): |
168 # return if we've already processed this | 168 # return if we've already processed this |
169 node = self.repo.changelog.lookup(rev) | 169 node = self.repo.changelog.lookup(rev) |
170 phgsha = hex(node) | 170 phgsha = hex(node) |
171 pgit_sha = self.map_git_get(phgsha) | 171 pgit_sha = self.map_git_get(phgsha) |
172 if pgit_sha: | 172 if pgit_sha: |
173 return pgit_sha | 173 return pgit_sha |
174 | 174 |
175 print "converting revision " + str(rev) | 175 print "converting revision " + str(rev) |
176 | 176 |
177 # make sure parents are converted first | 177 # make sure parents are converted first |
178 parents = self.repo.parents(rev) | 178 parents = self.repo.parents(rev) |
179 for parent in parents: | 179 for parent in parents: |
180 p_rev = parent.rev() | 180 p_rev = parent.rev() |
181 hgsha = hex(parent.node()) | 181 hgsha = hex(parent.node()) |
182 git_sha = self.map_git_get(hgsha) | 182 git_sha = self.map_git_get(hgsha) |
183 if not p_rev == -1: | 183 if not p_rev == -1: |
184 if not git_sha: | 184 if not git_sha: |
185 self.export_hg_commit(p_rev) | 185 self.export_hg_commit(p_rev) |
186 | 186 |
187 ctx = self.repo.changectx(rev) | 187 ctx = self.repo.changectx(rev) |
188 tree_sha = self.write_git_tree(ctx) | 188 tree_sha = self.write_git_tree(ctx) |
189 | 189 |
190 # TODO : something with tags? | 190 # TODO : something with tags? |
191 # TODO : explicit file renaming, copying? | 191 # TODO : explicit file renaming, copying? |
192 | 192 |
193 commit = {} | 193 commit = {} |
194 commit['tree'] = tree_sha | 194 commit['tree'] = tree_sha |
195 (time, timezone) = ctx.date() | 195 (time, timezone) = ctx.date() |
196 commit['author'] = ctx.user() + ' ' + str(int(time)) + ' ' + seconds_to_offset(timezone) | 196 commit['author'] = ctx.user() + ' ' + str(int(time)) + ' ' + seconds_to_offset(timezone) |
197 message = ctx.description() | 197 message = ctx.description() |
198 commit['message'] = ctx.description() | 198 commit['message'] = ctx.description() |
199 commit['message'] += "\n\n--HG--\n" | 199 commit['message'] += "\n\n--HG--\n" |
200 commit['message'] += "branch : " + ctx.branch() + "\n" | 200 commit['message'] += "branch : " + ctx.branch() + "\n" |
201 | 201 |
202 commit['parents'] = [] | 202 commit['parents'] = [] |
203 for parent in parents: | 203 for parent in parents: |
204 hgsha = hex(parent.node()) | 204 hgsha = hex(parent.node()) |
205 git_sha = self.map_git_get(hgsha) | 205 git_sha = self.map_git_get(hgsha) |
206 if git_sha: | 206 if git_sha: |
207 commit['parents'].append(git_sha) | 207 commit['parents'].append(git_sha) |
208 | 208 |
209 commit_sha = self.git.write_commit_hash(commit) # writing new blobs to git | 209 commit_sha = self.git.write_commit_hash(commit) # writing new blobs to git |
210 self.map_set(commit_sha, phgsha) | 210 self.map_set(commit_sha, phgsha) |
211 return commit_sha | 211 return commit_sha |
212 | 212 |
213 def write_git_tree(self, ctx): | 213 def write_git_tree(self, ctx): |
214 trees = {} | 214 trees = {} |
215 man = ctx.manifest() | 215 man = ctx.manifest() |
216 for filenm in man.keys(): | 216 for filenm in man.keys(): |
217 # write blob if not in our git database | 217 # write blob if not in our git database |
218 fctx = ctx.filectx(filenm) | 218 fctx = ctx.filectx(filenm) |
219 is_exec = 'x' in fctx.flags() | 219 is_exec = 'x' in fctx.flags() |
220 is_link = 'l' in fctx.flags() | 220 is_link = 'l' in fctx.flags() |
221 file_id = hex(fctx.filenode()) | 221 file_id = hex(fctx.filenode()) |
246 if parentpath not in trees: | 246 if parentpath not in trees: |
247 trees[parentpath] = [] | 247 trees[parentpath] = [] |
248 if treeentry not in trees[parentpath]: | 248 if treeentry not in trees[parentpath]: |
249 trees[parentpath].append( treeentry ) | 249 trees[parentpath].append( treeentry ) |
250 else: | 250 else: |
251 fileentry = ['blob', parts[0], blob_sha, is_exec, is_link] | 251 fileentry = ['blob', parts[0], blob_sha, is_exec, is_link] |
252 if '/' not in trees: | 252 if '/' not in trees: |
253 trees['/'] = [] | 253 trees['/'] = [] |
254 trees['/'].append(fileentry) | 254 trees['/'].append(fileentry) |
255 | 255 |
256 # sort by tree depth, so we write the deepest trees first | 256 # sort by tree depth, so we write the deepest trees first |
257 dirs = trees.keys() | 257 dirs = trees.keys() |
258 dirs.sort(lambda a, b: len(b.split('/'))-len(a.split('/'))) | 258 dirs.sort(lambda a, b: len(b.split('/'))-len(a.split('/'))) |
259 dirs.remove('/') | 259 dirs.remove('/') |
260 dirs.append('/') | 260 dirs.append('/') |
261 | 261 |
262 # write all the trees | 262 # write all the trees |
263 tree_sha = None | 263 tree_sha = None |
264 tree_shas = {} | 264 tree_shas = {} |
265 for dirnm in dirs: | 265 for dirnm in dirs: |
266 tree_data = [] | 266 tree_data = [] |
271 entry[2] = sha | 271 entry[2] = sha |
272 tree_data.append(entry) | 272 tree_data.append(entry) |
273 tree_sha = self.git.write_tree_array(tree_data) # writing new trees to git | 273 tree_sha = self.git.write_tree_array(tree_data) # writing new trees to git |
274 tree_shas[dirnm] = tree_sha | 274 tree_shas[dirnm] = tree_sha |
275 return tree_sha # should be the last root tree sha | 275 return tree_sha # should be the last root tree sha |
276 | 276 |
277 def remote_head(self, remote_name): | 277 def remote_head(self, remote_name): |
278 for head, sha in self.git.remote_refs(remote_name).iteritems(): | 278 for head, sha in self.git.remote_refs(remote_name).iteritems(): |
279 if head == 'HEAD': | 279 if head == 'HEAD': |
280 return self.map_hg_get(sha) | 280 return self.map_hg_get(sha) |
281 return None | 281 return None |
296 except: | 296 except: |
297 raise | 297 raise |
298 | 298 |
299 # TODO : for now, we'll just push all heads that match remote heads | 299 # TODO : for now, we'll just push all heads that match remote heads |
300 # * we should have specified push, tracking branches and --all | 300 # * we should have specified push, tracking branches and --all |
301 # takes a dict of refs:shas from the server and returns what should be | 301 # takes a dict of refs:shas from the server and returns what should be |
302 # pushed up | 302 # pushed up |
303 def get_changed_refs(self, refs): | 303 def get_changed_refs(self, refs): |
304 keys = refs.keys() | 304 keys = refs.keys() |
305 | 305 |
306 changed = [] | 306 changed = [] |
307 if not keys: | 307 if not keys: |
308 return None | 308 return None |
309 | 309 |
310 # TODO : this is a huge hack | 310 # TODO : this is a huge hack |
311 if keys[0] == 'capabilities^{}': # nothing on the server yet - first push | 311 if keys[0] == 'capabilities^{}': # nothing on the server yet - first push |
312 changed.append(("0"*40, self.git.ref('master'), 'refs/heads/master')) | 312 changed.append(("0"*40, self.git.ref('master'), 'refs/heads/master')) |
313 | 313 |
314 for ref_name in keys: | 314 for ref_name in keys: |
315 parts = ref_name.split('/') | 315 parts = ref_name.split('/') |
316 if parts[0] == 'refs': # strip off 'refs/heads' | 316 if parts[0] == 'refs': # strip off 'refs/heads' |
317 if parts[1] == 'heads': | 317 if parts[1] == 'heads': |
318 head = "/".join([v for v in parts[2:]]) | 318 head = "/".join([v for v in parts[2:]]) |
319 local_ref = self.git.ref(ref_name) | 319 local_ref = self.git.ref(ref_name) |
320 if local_ref: | 320 if local_ref: |
321 if not local_ref == refs[ref_name]: | 321 if not local_ref == refs[ref_name]: |
322 changed.append((refs[ref_name], local_ref, ref_name)) | 322 changed.append((refs[ref_name], local_ref, ref_name)) |
323 return changed | 323 return changed |
324 | 324 |
325 # takes a list of shas the server wants and shas the server has | 325 # takes a list of shas the server wants and shas the server has |
326 # and generates a list of commit shas we need to push up | 326 # and generates a list of commit shas we need to push up |
327 def generate_pack_contents(self, want, have): | 327 def generate_pack_contents(self, want, have): |
328 graph_walker = SimpleFetchGraphWalker(want, self.git.get_parents) | 328 graph_walker = SimpleFetchGraphWalker(want, self.git.get_parents) |
329 next = graph_walker.next() | 329 next = graph_walker.next() |
332 if next in have: | 332 if next in have: |
333 graph_walker.ack(next) | 333 graph_walker.ack(next) |
334 else: | 334 else: |
335 shas.append(next) | 335 shas.append(next) |
336 next = graph_walker.next() | 336 next = graph_walker.next() |
337 | 337 |
338 # so now i have the shas, need to turn them into a list of | 338 # so now i have the shas, need to turn them into a list of |
339 # tuples (sha, path) for ALL the objects i'm sending | 339 # tuples (sha, path) for ALL the objects i'm sending |
340 # TODO : don't send blobs or trees they already have | 340 # TODO : don't send blobs or trees they already have |
341 def get_objects(tree, path): | 341 def get_objects(tree, path): |
342 changes = list() | 342 changes = list() |
343 changes.append((tree, path)) | 343 changes.append((tree, path)) |
348 if isinstance (obj, Blob): | 348 if isinstance (obj, Blob): |
349 changes.append((obj, path + name)) | 349 changes.append((obj, path + name)) |
350 elif isinstance(obj, Tree): | 350 elif isinstance(obj, Tree): |
351 changes.extend (get_objects (obj, path + name + '/')) | 351 changes.extend (get_objects (obj, path + name + '/')) |
352 return changes | 352 return changes |
353 | 353 |
354 objects = [] | 354 objects = [] |
355 for commit_sha in shas: | 355 for commit_sha in shas: |
356 commit = self.git.commit(commit_sha) | 356 commit = self.git.commit(commit_sha) |
357 objects.append((commit, 'commit')) | 357 objects.append((commit, 'commit')) |
358 tree = self.git.get_object(commit.tree) | 358 tree = self.git.get_object(commit.tree) |
359 objects.extend( get_objects(tree, '/') ) | 359 objects.extend( get_objects(tree, '/') ) |
360 | 360 |
361 return objects | 361 return objects |
362 | 362 |
363 def fetch_pack(self, remote_name): | 363 def fetch_pack(self, remote_name): |
364 git_url = self.remote_name_to_url(remote_name) | 364 git_url = self.remote_name_to_url(remote_name) |
365 client, path = self.get_transport_and_path(git_url) | 365 client, path = self.get_transport_and_path(git_url) |
366 graphwalker = SimpleFetchGraphWalker(self.git.heads().values(), self.git.get_parents) | 366 graphwalker = SimpleFetchGraphWalker(self.git.heads().values(), self.git.get_parents) |
367 f, commit = self.git.object_store.add_pack() | 367 f, commit = self.git.object_store.add_pack() |
425 | 425 |
426 def import_git_commit(self, commit): | 426 def import_git_commit(self, commit): |
427 print "importing: " + commit.id | 427 print "importing: " + commit.id |
428 # TODO : look for HG metadata in the message and use it | 428 # TODO : look for HG metadata in the message and use it |
429 # TODO : add extra Git data (committer info) as extras to changeset | 429 # TODO : add extra Git data (committer info) as extras to changeset |
430 | 430 |
431 # TODO : (?) have to handle merge contexts at some point (two parent files, etc) | 431 # TODO : (?) have to handle merge contexts at some point (two parent files, etc) |
432 # TODO : throw IOError for removed files | 432 # TODO : Do something less coarse-grained than try/except on the |
433 # get_file call for removed files | |
433 def getfilectx(repo, memctx, f): | 434 def getfilectx(repo, memctx, f): |
434 (e, sha, data) = self.git.get_file(commit, f) | 435 try: |
436 (e, sha, data) = self.git.get_file(commit, f) | |
437 except TypeError: | |
438 raise IOError() | |
435 e = '' # TODO : make this a real mode | 439 e = '' # TODO : make this a real mode |
436 return context.memfilectx(f, data, 'l' in e, 'x' in e, None) | 440 return context.memfilectx(f, data, 'l' in e, 'x' in e, None) |
437 | 441 |
438 p1 = "0" * 40 | 442 p1 = "0" * 40 |
439 p2 = "0" * 40 | 443 p2 = "0" * 40 |
482 return SubprocessGitClient(), uri | 486 return SubprocessGitClient(), uri |
483 | 487 |
484 def clear(self): | 488 def clear(self): |
485 git_dir = self.repo.join('git') | 489 git_dir = self.repo.join('git') |
486 mapfile = self.repo.join('git-mapfile') | 490 mapfile = self.repo.join('git-mapfile') |
487 if os.path.exists(git_dir): | 491 if os.path.exists(git_dir): |
488 for root, dirs, files in os.walk(git_dir, topdown=False): | 492 for root, dirs, files in os.walk(git_dir, topdown=False): |
489 for name in files: | 493 for name in files: |
490 os.remove(os.path.join(root, name)) | 494 os.remove(os.path.join(root, name)) |
491 for name in dirs: | 495 for name in dirs: |
492 os.rmdir(os.path.join(root, name)) | 496 os.rmdir(os.path.join(root, name)) |
493 os.rmdir(git_dir) | 497 os.rmdir(git_dir) |
494 if os.path.exists(mapfile): | 498 if os.path.exists(mapfile): |
495 os.remove(mapfile) | 499 os.remove(mapfile) |
496 | 500 |
497 | 501 |
498 '' | 502 '' |
499 """ | 503 """ |
500 Tarjan's algorithm and topological sorting implementation in Python | 504 Tarjan's algorithm and topological sorting implementation in Python |
501 by Paul Harrison | 505 by Paul Harrison |