Mercurial > hg > hg-git
comparison git_handler.py @ 50:d274092e3b24
Hacky implementation of file removals.
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Tue, 28 Apr 2009 19:33:03 -0700 |
parents | fcfb4db848e1 |
children | 1421d04f1ad2 |
comparison
equal
deleted
inserted
replaced
49:9f3f79a209d6 | 50:d274092e3b24 |
---|---|
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 |
291 except: | 291 except: |
292 raise | 292 raise |
293 | 293 |
294 # TODO : for now, we'll just push all heads that match remote heads | 294 # TODO : for now, we'll just push all heads that match remote heads |
295 # * we should have specified push, tracking branches and --all | 295 # * we should have specified push, tracking branches and --all |
296 # takes a dict of refs:shas from the server and returns what should be | 296 # takes a dict of refs:shas from the server and returns what should be |
297 # pushed up | 297 # pushed up |
298 def get_changed_refs(self, refs): | 298 def get_changed_refs(self, refs): |
299 keys = refs.keys() | 299 keys = refs.keys() |
300 | 300 |
301 changed = [] | 301 changed = [] |
302 if not keys: | 302 if not keys: |
303 return None | 303 return None |
304 | 304 |
305 # TODO : this is a huge hack | 305 # TODO : this is a huge hack |
306 if keys[0] == 'capabilities^{}': # nothing on the server yet - first push | 306 if keys[0] == 'capabilities^{}': # nothing on the server yet - first push |
307 changed.append(("0"*40, self.git.ref('master'), 'refs/heads/master')) | 307 changed.append(("0"*40, self.git.ref('master'), 'refs/heads/master')) |
308 | 308 |
309 for ref_name in keys: | 309 for ref_name in keys: |
310 parts = ref_name.split('/') | 310 parts = ref_name.split('/') |
311 if parts[0] == 'refs': # strip off 'refs/heads' | 311 if parts[0] == 'refs': # strip off 'refs/heads' |
312 if parts[1] == 'heads': | 312 if parts[1] == 'heads': |
313 head = "/".join([v for v in parts[2:]]) | 313 head = "/".join([v for v in parts[2:]]) |
314 local_ref = self.git.ref(ref_name) | 314 local_ref = self.git.ref(ref_name) |
315 if local_ref: | 315 if local_ref: |
316 if not local_ref == refs[ref_name]: | 316 if not local_ref == refs[ref_name]: |
317 changed.append((refs[ref_name], local_ref, ref_name)) | 317 changed.append((refs[ref_name], local_ref, ref_name)) |
318 return changed | 318 return changed |
319 | 319 |
320 # takes a list of shas the server wants and shas the server has | 320 # takes a list of shas the server wants and shas the server has |
321 # and generates a list of commit shas we need to push up | 321 # and generates a list of commit shas we need to push up |
322 def generate_pack_contents(self, want, have): | 322 def generate_pack_contents(self, want, have): |
323 graph_walker = SimpleFetchGraphWalker(want, self.git.get_parents) | 323 graph_walker = SimpleFetchGraphWalker(want, self.git.get_parents) |
324 next = graph_walker.next() | 324 next = graph_walker.next() |
327 if next in have: | 327 if next in have: |
328 graph_walker.ack(next) | 328 graph_walker.ack(next) |
329 else: | 329 else: |
330 shas.append(next) | 330 shas.append(next) |
331 next = graph_walker.next() | 331 next = graph_walker.next() |
332 | 332 |
333 # so now i have the shas, need to turn them into a list of | 333 # so now i have the shas, need to turn them into a list of |
334 # tuples (sha, path) for ALL the objects i'm sending | 334 # tuples (sha, path) for ALL the objects i'm sending |
335 # TODO : don't send blobs or trees they already have | 335 # TODO : don't send blobs or trees they already have |
336 def get_objects(tree, path): | 336 def get_objects(tree, path): |
337 changes = list() | 337 changes = list() |
338 changes.append((tree, path)) | 338 changes.append((tree, path)) |
343 if isinstance (obj, Blob): | 343 if isinstance (obj, Blob): |
344 changes.append((obj, path + name)) | 344 changes.append((obj, path + name)) |
345 elif isinstance(obj, Tree): | 345 elif isinstance(obj, Tree): |
346 changes.extend (get_objects (obj, path + name + '/')) | 346 changes.extend (get_objects (obj, path + name + '/')) |
347 return changes | 347 return changes |
348 | 348 |
349 objects = [] | 349 objects = [] |
350 for commit_sha in shas: | 350 for commit_sha in shas: |
351 commit = self.git.commit(commit_sha) | 351 commit = self.git.commit(commit_sha) |
352 objects.append((commit, 'commit')) | 352 objects.append((commit, 'commit')) |
353 tree = self.git.get_object(commit.tree) | 353 tree = self.git.get_object(commit.tree) |
354 objects.extend( get_objects(tree, '/') ) | 354 objects.extend( get_objects(tree, '/') ) |
355 | 355 |
356 return objects | 356 return objects |
357 | 357 |
358 def fetch_pack(self, remote_name): | 358 def fetch_pack(self, remote_name): |
359 git_url = self.remote_name_to_url(remote_name) | 359 git_url = self.remote_name_to_url(remote_name) |
360 client, path = self.get_transport_and_path(git_url) | 360 client, path = self.get_transport_and_path(git_url) |
361 graphwalker = SimpleFetchGraphWalker(self.git.heads().values(), self.git.get_parents) | 361 graphwalker = SimpleFetchGraphWalker(self.git.heads().values(), self.git.get_parents) |
362 f, commit = self.git.object_store.add_pack() | 362 f, commit = self.git.object_store.add_pack() |
418 | 418 |
419 def import_git_commit(self, commit): | 419 def import_git_commit(self, commit): |
420 print "importing: " + commit.id | 420 print "importing: " + commit.id |
421 # TODO : look for HG metadata in the message and use it | 421 # TODO : look for HG metadata in the message and use it |
422 # TODO : add extra Git data (committer info) as extras to changeset | 422 # TODO : add extra Git data (committer info) as extras to changeset |
423 | 423 |
424 # TODO : (?) have to handle merge contexts at some point (two parent files, etc) | 424 # TODO : (?) have to handle merge contexts at some point (two parent files, etc) |
425 # TODO : throw IOError for removed files | 425 # TODO : Do something less coarse-grained than try/except on the |
426 # get_file call for removed files | |
426 def getfilectx(repo, memctx, f): | 427 def getfilectx(repo, memctx, f): |
427 (e, sha, data) = self.git.get_file(commit, f) | 428 try: |
429 (e, sha, data) = self.git.get_file(commit, f) | |
430 except TypeError: | |
431 raise IOError() | |
428 e = '' # TODO : make this a real mode | 432 e = '' # TODO : make this a real mode |
429 return context.memfilectx(f, data, 'l' in e, 'x' in e, None) | 433 return context.memfilectx(f, data, 'l' in e, 'x' in e, None) |
430 | 434 |
431 p1 = "0" * 40 | 435 p1 = "0" * 40 |
432 p2 = "0" * 40 | 436 p2 = "0" * 40 |
481 return SubprocessGitClient(), uri | 485 return SubprocessGitClient(), uri |
482 | 486 |
483 def clear(self): | 487 def clear(self): |
484 git_dir = self.repo.join('git') | 488 git_dir = self.repo.join('git') |
485 mapfile = self.repo.join('git-mapfile') | 489 mapfile = self.repo.join('git-mapfile') |
486 if os.path.exists(git_dir): | 490 if os.path.exists(git_dir): |
487 for root, dirs, files in os.walk(git_dir, topdown=False): | 491 for root, dirs, files in os.walk(git_dir, topdown=False): |
488 for name in files: | 492 for name in files: |
489 os.remove(os.path.join(root, name)) | 493 os.remove(os.path.join(root, name)) |
490 for name in dirs: | 494 for name in dirs: |
491 os.rmdir(os.path.join(root, name)) | 495 os.rmdir(os.path.join(root, name)) |
492 os.rmdir(git_dir) | 496 os.rmdir(git_dir) |
493 if os.path.exists(mapfile): | 497 if os.path.exists(mapfile): |
494 os.remove(mapfile) | 498 os.remove(mapfile) |
495 | 499 |
496 | 500 |
497 '' | 501 '' |
498 """ | 502 """ |
499 Tarjan's algorithm and topological sorting implementation in Python | 503 Tarjan's algorithm and topological sorting implementation in Python |
500 by Paul Harrison | 504 by Paul Harrison |