comparison hgext/histedit.py @ 17066:baf8887d40e2

histedit: fix most check-code violations
author Augie Fackler <raf@durin42.com>
date Wed, 27 Jun 2012 18:35:33 -0500
parents 168cc52ad7c2
children a86e110430f6
comparison
equal deleted inserted replaced
17065:949e241b5573 17066:baf8887d40e2
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 """Interactive history editing. 7 """Interactive history editing.
8 8
9 Inspired by git rebase --interactive. 9 Inspired by git rebase --interactive.
10 """ 10 """
11 from inspect import getargspec
12 try: 11 try:
13 import cPickle as pickle 12 import cPickle as pickle
14 except ImportError: 13 except ImportError:
15 import pickle 14 import pickle
16 import tempfile 15 import tempfile
23 from mercurial import hg 22 from mercurial import hg
24 from mercurial import node 23 from mercurial import node
25 from mercurial import patch 24 from mercurial import patch
26 from mercurial import repair 25 from mercurial import repair
27 from mercurial import scmutil 26 from mercurial import scmutil
28 from mercurial import url
29 from mercurial import util 27 from mercurial import util
30 from mercurial.i18n import _ 28 from mercurial.i18n import _
31 29
32 30
33 editcomment = """ 31 editcomment = """
42 # m, mess = edit message without changing commit content 40 # m, mess = edit message without changing commit content
43 # 41 #
44 """ 42 """
45 43
46 def between(repo, old, new, keep): 44 def between(repo, old, new, keep):
47 revs = [old, ] 45 revs = [old]
48 current = old 46 current = old
49 while current != new: 47 while current != new:
50 ctx = repo[current] 48 ctx = repo[current]
51 if not keep and len(ctx.children()) > 1: 49 if not keep and len(ctx.children()) > 1:
52 raise util.Abort(_('cannot edit history that would orphan nodes')) 50 raise util.Abort(_('cannot edit history that would orphan nodes'))
87 ui.warn(_('%s: empty changeset') 85 ui.warn(_('%s: empty changeset')
88 % node.hex(ha)) 86 % node.hex(ha))
89 return ctx, [], [], [] 87 return ctx, [], [], []
90 finally: 88 finally:
91 os.unlink(patchfile) 89 os.unlink(patchfile)
92 except Exception, inst: 90 except Exception:
93 raise util.Abort(_('Fix up the change and run ' 91 raise util.Abort(_('Fix up the change and run '
94 'hg histedit --continue')) 92 'hg histedit --continue'))
95 n = repo.commit(text=oldctx.description(), user=oldctx.user(), date=oldctx.date(), 93 n = repo.commit(text=oldctx.description(), user=oldctx.user(),
96 extra=oldctx.extra()) 94 date=oldctx.date(), extra=oldctx.extra())
97 return repo[n], [n, ], [oldctx.node(), ], [] 95 return repo[n], [n], [oldctx.node()], []
98 96
99 97
100 def edit(ui, repo, ctx, ha, opts): 98 def edit(ui, repo, ctx, ha, opts):
101 oldctx = repo[ha] 99 oldctx = repo[ha]
102 hg.update(repo, ctx.node()) 100 hg.update(repo, ctx.node())
115 files = set() 113 files = set()
116 try: 114 try:
117 patch.patch(ui, repo, patchfile, files=files, eolmode=None) 115 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
118 finally: 116 finally:
119 os.unlink(patchfile) 117 os.unlink(patchfile)
120 except Exception, inst: 118 except Exception:
121 pass 119 pass
122 raise util.Abort(_('Make changes as needed, you may commit or record as ' 120 raise util.Abort(_('Make changes as needed, you may commit or record as '
123 'needed now.\nWhen you are finished, run hg' 121 'needed now.\nWhen you are finished, run hg'
124 ' histedit --continue to resume.')) 122 ' histedit --continue to resume.'))
125 123
145 ui.warn(_('%s: empty changeset') 143 ui.warn(_('%s: empty changeset')
146 % node.hex(ha)) 144 % node.hex(ha))
147 return ctx, [], [], [] 145 return ctx, [], [], []
148 finally: 146 finally:
149 os.unlink(patchfile) 147 os.unlink(patchfile)
150 except Exception, inst: 148 except Exception:
151 raise util.Abort(_('Fix up the change and run ' 149 raise util.Abort(_('Fix up the change and run '
152 'hg histedit --continue')) 150 'hg histedit --continue'))
153 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(), date=oldctx.date(), 151 n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
154 extra=oldctx.extra()) 152 date=oldctx.date(), extra=oldctx.extra())
155 return finishfold(ui, repo, ctx, oldctx, n, opts, []) 153 return finishfold(ui, repo, ctx, oldctx, n, opts, [])
156 154
157 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges): 155 def finishfold(ui, repo, ctx, oldctx, newnode, opts, internalchanges):
158 parent = ctx.parents()[0].node() 156 parent = ctx.parents()[0].node()
159 hg.update(repo, parent) 157 hg.update(repo, parent)
172 try: 170 try:
173 patch.patch(ui, repo, patchfile, files=files, eolmode=None) 171 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
174 finally: 172 finally:
175 os.unlink(patchfile) 173 os.unlink(patchfile)
176 newmessage = '\n***\n'.join( 174 newmessage = '\n***\n'.join(
177 [ctx.description(), ] + 175 [ctx.description()] +
178 [repo[r].description() for r in internalchanges] + 176 [repo[r].description() for r in internalchanges] +
179 [oldctx.description(), ]) 177 [oldctx.description()])
180 # If the changesets are from the same author, keep it. 178 # If the changesets are from the same author, keep it.
181 if ctx.user() == oldctx.user(): 179 if ctx.user() == oldctx.user():
182 username = ctx.user() 180 username = ctx.user()
183 else: 181 else:
184 username = ui.username() 182 username = ui.username()
185 newmessage = ui.edit(newmessage, username) 183 newmessage = ui.edit(newmessage, username)
186 n = repo.commit(text=newmessage, user=username, date=max(ctx.date(), oldctx.date()), 184 n = repo.commit(text=newmessage, user=username,
187 extra=oldctx.extra()) 185 date=max(ctx.date(), oldctx.date()), extra=oldctx.extra())
188 return repo[n], [n, ], [oldctx.node(), ctx.node() ], [newnode, ] 186 return repo[n], [n], [oldctx.node(), ctx.node()], [newnode]
189 187
190 def drop(ui, repo, ctx, ha, opts): 188 def drop(ui, repo, ctx, ha, opts):
191 return ctx, [], [repo[ha].node(), ], [] 189 return ctx, [], [repo[ha].node()], []
192 190
193 191
194 def message(ui, repo, ctx, ha, opts): 192 def message(ui, repo, ctx, ha, opts):
195 oldctx = repo[ha] 193 oldctx = repo[ha]
196 hg.update(repo, ctx.node()) 194 hg.update(repo, ctx.node())
209 files = set() 207 files = set()
210 try: 208 try:
211 patch.patch(ui, repo, patchfile, files=files, eolmode=None) 209 patch.patch(ui, repo, patchfile, files=files, eolmode=None)
212 finally: 210 finally:
213 os.unlink(patchfile) 211 os.unlink(patchfile)
214 except Exception, inst: 212 except Exception:
215 raise util.Abort(_('Fix up the change and run ' 213 raise util.Abort(_('Fix up the change and run '
216 'hg histedit --continue')) 214 'hg histedit --continue'))
217 message = oldctx.description() 215 message = oldctx.description()
218 message = ui.edit(message, ui.username()) 216 message = ui.edit(message, ui.username())
219 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(), 217 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(),
253 raise util.Abort(_('source has mq patches applied')) 251 raise util.Abort(_('source has mq patches applied'))
254 252
255 parent = list(parent) + opts.get('rev', []) 253 parent = list(parent) + opts.get('rev', [])
256 if opts.get('outgoing'): 254 if opts.get('outgoing'):
257 if len(parent) > 1: 255 if len(parent) > 1:
258 raise util.Abort(_('only one repo argument allowed with --outgoing')) 256 raise util.Abort(
257 _('only one repo argument allowed with --outgoing'))
259 elif parent: 258 elif parent:
260 parent = parent[0] 259 parent = parent[0]
261 260
262 dest = ui.expandpath(parent or 'default-push', parent or 'default') 261 dest = ui.expandpath(parent or 'default-push', parent or 'default')
263 dest, revs = hg.parseurl(dest, None)[:2] 262 dest, revs = hg.parseurl(dest, None)[:2]
277 276
278 if opts.get('continue', False): 277 if opts.get('continue', False):
279 if len(parent) != 0: 278 if len(parent) != 0:
280 raise util.Abort(_('no arguments allowed with --continue')) 279 raise util.Abort(_('no arguments allowed with --continue'))
281 (parentctxnode, created, replaced, 280 (parentctxnode, created, replaced,
282 tmpnodes, existing, rules, keep, tip, replacemap ) = readstate(repo) 281 tmpnodes, existing, rules, keep, tip, replacemap) = readstate(repo)
283 currentparent, wantnull = repo.dirstate.parents() 282 currentparent, wantnull = repo.dirstate.parents()
284 parentctx = repo[parentctxnode] 283 parentctx = repo[parentctxnode]
285 # discover any nodes the user has added in the interim 284 # discover any nodes the user has added in the interim
286 newchildren = [c for c in parentctx.children() 285 newchildren = [c for c in parentctx.children()
287 if c.node() not in existing] 286 if c.node() not in existing]
288 action, currentnode = rules.pop(0) 287 action, currentnode = rules.pop(0)
289 while newchildren: 288 while newchildren:
290 if action in ['f', 'fold', ]: 289 if action in ('f', 'fold'):
291 tmpnodes.extend([n.node() for n in newchildren]) 290 tmpnodes.extend([n.node() for n in newchildren])
292 else: 291 else:
293 created.extend([n.node() for n in newchildren]) 292 created.extend([n.node() for n in newchildren])
294 newchildren = filter(lambda x: x.node() not in existing, 293 newchildren = filter(lambda x: x.node() not in existing,
295 reduce(lambda x, y: x + y, 294 reduce(lambda x, y: x + y,
298 m, a, r, d = repo.status()[:4] 297 m, a, r, d = repo.status()[:4]
299 oldctx = repo[currentnode] 298 oldctx = repo[currentnode]
300 message = oldctx.description() 299 message = oldctx.description()
301 if action in ('e', 'edit', 'm', 'mess'): 300 if action in ('e', 'edit', 'm', 'mess'):
302 message = ui.edit(message, ui.username()) 301 message = ui.edit(message, ui.username())
303 elif action in ('f', 'fold', ): 302 elif action in ('f', 'fold'):
304 message = 'fold-temp-revision %s' % currentnode 303 message = 'fold-temp-revision %s' % currentnode
305 new = None 304 new = None
306 if m or a or r or d: 305 if m or a or r or d:
307 new = repo.commit(text=message, user=oldctx.user(), date=oldctx.date(), 306 new = repo.commit(text=message, user=oldctx.user(),
308 extra=oldctx.extra()) 307 date=oldctx.date(), extra=oldctx.extra())
309 308
310 if action in ('f', 'fold'): 309 if action in ('f', 'fold'):
311 if new: 310 if new:
312 tmpnodes.append(new) 311 tmpnodes.append(new)
313 else: 312 else:
314 new = newchildren[-1] 313 new = newchildren[-1]
315 (parentctx, created_, 314 (parentctx, created_, replaced_, tmpnodes_) = finishfold(
316 replaced_, tmpnodes_, ) = finishfold(ui, repo, 315 ui, repo, parentctx, oldctx, new, opts, newchildren)
317 parentctx, oldctx, new,
318 opts, newchildren)
319 replaced.extend(replaced_) 316 replaced.extend(replaced_)
320 created.extend(created_) 317 created.extend(created_)
321 tmpnodes.extend(tmpnodes_) 318 tmpnodes.extend(tmpnodes_)
322 elif action not in ('d', 'drop'): 319 elif action not in ('d', 'drop'):
323 if new != oldctx.node(): 320 if new != oldctx.node():
336 hg.clean(repo, tip) 333 hg.clean(repo, tip)
337 ui.debug('should strip created nodes %s\n' % 334 ui.debug('should strip created nodes %s\n' %
338 ', '.join([node.hex(n)[:12] for n in created])) 335 ', '.join([node.hex(n)[:12] for n in created]))
339 ui.debug('should strip temp nodes %s\n' % 336 ui.debug('should strip temp nodes %s\n' %
340 ', '.join([node.hex(n)[:12] for n in tmpnodes])) 337 ', '.join([node.hex(n)[:12] for n in tmpnodes]))
341 for nodes in (created, tmpnodes, ): 338 for nodes in (created, tmpnodes):
342 for n in reversed(nodes): 339 for n in reversed(nodes):
343 try: 340 try:
344 repair.strip(ui, repo, n) 341 repair.strip(ui, repo, n)
345 except error.LookupError: 342 except error.LookupError:
346 pass 343 pass
365 ctxs = [repo[r] for r in revs] 362 ctxs = [repo[r] for r in revs]
366 existing = [r.node() for r in ctxs] 363 existing = [r.node() for r in ctxs]
367 rules = opts.get('commands', '') 364 rules = opts.get('commands', '')
368 if not rules: 365 if not rules:
369 rules = '\n'.join([makedesc(c) for c in ctxs]) 366 rules = '\n'.join([makedesc(c) for c in ctxs])
370 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12], ) 367 rules += editcomment % (node.hex(parent)[:12], node.hex(tip)[:12])
371 rules = ui.edit(rules, ui.username()) 368 rules = ui.edit(rules, ui.username())
372 # Save edit rules in .hg/histedit-last-edit.txt in case 369 # Save edit rules in .hg/histedit-last-edit.txt in case
373 # the user needs to ask for help after something 370 # the user needs to ask for help after something
374 # surprising happens. 371 # surprising happens.
375 f = open(repo.join('histedit-last-edit.txt'), 'w') 372 f = open(repo.join('histedit-last-edit.txt'), 'w')
390 tmpnodes = [] 387 tmpnodes = []
391 created = [] 388 created = []
392 389
393 390
394 while rules: 391 while rules:
395 writestate(repo, parentctx.node(), created, replaced, tmpnodes, existing, 392 writestate(repo, parentctx.node(), created, replaced,
396 rules, keep, tip, replacemap) 393 tmpnodes, existing, rules, keep, tip, replacemap)
397 action, ha = rules.pop(0) 394 action, ha = rules.pop(0)
398 (parentctx, created_, 395 (parentctx, created_, replaced_, tmpnodes_) = actiontable[action](
399 replaced_, tmpnodes_, ) = actiontable[action](ui, repo, 396 ui, repo, parentctx, ha, opts)
400 parentctx, ha,
401 opts)
402 397
403 hexshort = lambda x: node.hex(x)[:12] 398 hexshort = lambda x: node.hex(x)[:12]
404 399
405 if replaced_: 400 if replaced_:
406 clen, rlen = len(created_), len(replaced_) 401 clen, rlen = len(created_), len(replaced_)
413 assert rlen == 1, ('unexpected replacement of ' 408 assert rlen == 1, ('unexpected replacement of '
414 '%d changes with %d changes' % (rlen, clen)) 409 '%d changes with %d changes' % (rlen, clen))
415 # made more changesets than we're replacing 410 # made more changesets than we're replacing
416 # TODO synthesize patch names for created patches 411 # TODO synthesize patch names for created patches
417 replacemap[replaced_[0]] = created_[-1] 412 replacemap[replaced_[0]] = created_[-1]
418 ui.debug('histedit: created many, assuming %s replaced by %s' % ( 413 ui.debug('histedit: created many, assuming %s replaced by %s' %
419 hexshort(replaced_[0]), hexshort(created_[-1]))) 414 (hexshort(replaced_[0]), hexshort(created_[-1])))
420 elif rlen > clen: 415 elif rlen > clen:
421 if not created_: 416 if not created_:
422 # This must be a drop. Try and put our metadata on 417 # This must be a drop. Try and put our metadata on
423 # the parent change. 418 # the parent change.
424 assert rlen == 1 419 assert rlen == 1
449 444
450 hg.update(repo, parentctx.node()) 445 hg.update(repo, parentctx.node())
451 446
452 if not keep: 447 if not keep:
453 if replacemap: 448 if replacemap:
454 ui.note('histedit: Should update metadata for the following ' 449 ui.note(_('histedit: Should update metadata for the following '
455 'changes:\n') 450 'changes:\n'))
456 451
457 def copybms(old, new): 452 def copybms(old, new):
458 if old in tmpnodes or old in created: 453 if old in tmpnodes or old in created:
459 # can't have any metadata we'd want to update 454 # can't have any metadata we'd want to update
460 return 455 return
461 while new in replacemap: 456 while new in replacemap:
462 new = replacemap[new] 457 new = replacemap[new]
463 ui.note('histedit: %s to %s\n' % (hexshort(old), hexshort(new))) 458 ui.note(_('histedit: %s to %s\n') % (hexshort(old),
459 hexshort(new)))
464 octx = repo[old] 460 octx = repo[old]
465 marks = octx.bookmarks() 461 marks = octx.bookmarks()
466 if marks: 462 if marks:
467 ui.note('histedit: moving bookmarks %s\n' % 463 ui.note(_('histedit: moving bookmarks %s\n') %
468 ', '.join(marks)) 464 ', '.join(marks))
469 for mark in marks: 465 for mark in marks:
470 repo._bookmarks[mark] = new 466 repo._bookmarks[mark] = new
471 bookmarks.write(repo) 467 bookmarks.write(repo)
472 468
473 # We assume that bookmarks on the tip should remain 469 # We assume that bookmarks on the tip should remain
524 520
525 Will abort if there are to many or too few rules, a malformed rule, 521 Will abort if there are to many or too few rules, a malformed rule,
526 or a rule on a changeset outside of the user-given range. 522 or a rule on a changeset outside of the user-given range.
527 """ 523 """
528 parsed = [] 524 parsed = []
529 first = True
530 if len(rules) != len(ctxs): 525 if len(rules) != len(ctxs):
531 raise util.Abort(_('must specify a rule for each changeset once')) 526 raise util.Abort(_('must specify a rule for each changeset once'))
532 for r in rules: 527 for r in rules:
533 if ' ' not in r: 528 if ' ' not in r:
534 raise util.Abort(_('malformed line "%s"') % r) 529 raise util.Abort(_('malformed line "%s"') % r)
537 ha, rest = rest.split(' ', 1) 532 ha, rest = rest.split(' ', 1)
538 else: 533 else:
539 ha = r.strip() 534 ha = r.strip()
540 try: 535 try:
541 if repo[ha] not in ctxs: 536 if repo[ha] not in ctxs:
542 raise util.Abort(_('may not use changesets other than the ones listed')) 537 raise util.Abort(
538 _('may not use changesets other than the ones listed'))
543 except error.RepoError: 539 except error.RepoError:
544 raise util.Abort(_('unknown changeset %s listed') % ha) 540 raise util.Abort(_('unknown changeset %s listed') % ha)
545 if action not in actiontable: 541 if action not in actiontable:
546 raise util.Abort(_('unknown action "%s"') % action) 542 raise util.Abort(_('unknown action "%s"') % action)
547 parsed.append([action, ha]) 543 parsed.append([action, ha])
549 545
550 546
551 cmdtable = { 547 cmdtable = {
552 "histedit": 548 "histedit":
553 (histedit, 549 (histedit,
554 [('', 'commands', '', _('Read history edits from the specified file.')), 550 [('', 'commands', '', _(
551 'Read history edits from the specified file.')),
555 ('c', 'continue', False, _('continue an edit already in progress')), 552 ('c', 'continue', False, _('continue an edit already in progress')),
556 ('k', 'keep', False, _("don't strip old nodes after edit is complete")), 553 ('k', 'keep', False, _(
554 "don't strip old nodes after edit is complete")),
557 ('', 'abort', False, _('abort an edit in progress')), 555 ('', 'abort', False, _('abort an edit in progress')),
558 ('o', 'outgoing', False, _('changesets not found in destination')), 556 ('o', 'outgoing', False, _('changesets not found in destination')),
559 ('f', 'force', False, _('force outgoing even for unrelated repositories')), 557 ('f', 'force', False, _(
558 'force outgoing even for unrelated repositories')),
560 ('r', 'rev', [], _('first revision to be edited')), 559 ('r', 'rev', [], _('first revision to be edited')),
561 ], 560 ],
562 __doc__, 561 __doc__,
563 ), 562 ),
564 } 563 }