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