Mercurial > hg
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 } |