238 cmdtable = {} |
238 cmdtable = {} |
239 command = registrar.command(cmdtable) |
239 command = registrar.command(cmdtable) |
240 |
240 |
241 configtable = {} |
241 configtable = {} |
242 configitem = registrar.configitem(configtable) |
242 configitem = registrar.configitem(configtable) |
243 configitem('experimental', 'histedit.autoverb', |
243 configitem( |
244 default=False, |
244 'experimental', 'histedit.autoverb', default=False, |
245 ) |
245 ) |
246 configitem('histedit', 'defaultrev', |
246 configitem( |
247 default=None, |
247 'histedit', 'defaultrev', default=None, |
248 ) |
248 ) |
249 configitem('histedit', 'dropmissing', |
249 configitem( |
250 default=False, |
250 'histedit', 'dropmissing', default=False, |
251 ) |
251 ) |
252 configitem('histedit', 'linelen', |
252 configitem( |
253 default=80, |
253 'histedit', 'linelen', default=80, |
254 ) |
254 ) |
255 configitem('histedit', 'singletransaction', |
255 configitem( |
256 default=False, |
256 'histedit', 'singletransaction', default=False, |
257 ) |
257 ) |
258 configitem('ui', 'interface.histedit', |
258 configitem( |
259 default=None, |
259 'ui', 'interface.histedit', default=None, |
260 ) |
260 ) |
261 configitem('histedit', 'summary-template', |
261 configitem('histedit', 'summary-template', default='{rev} {desc|firstline}') |
262 default='{rev} {desc|firstline}') |
|
263 |
262 |
264 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
263 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
265 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
264 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
266 # be specifying the version(s) of Mercurial they are tested with, or |
265 # be specifying the version(s) of Mercurial they are tested with, or |
267 # leave the attribute unspecified. |
266 # leave the attribute unspecified. |
282 - sorted long commands |
282 - sorted long commands |
283 - additional hints |
283 - additional hints |
284 |
284 |
285 Commands are only included once. |
285 Commands are only included once. |
286 """ |
286 """ |
287 intro = _("""Edit history between %s and %s |
287 intro = _( |
|
288 """Edit history between %s and %s |
288 |
289 |
289 Commits are listed from least to most recent |
290 Commits are listed from least to most recent |
290 |
291 |
291 You can reorder changesets by reordering the lines |
292 You can reorder changesets by reordering the lines |
292 |
293 |
293 Commands: |
294 Commands: |
294 """) |
295 """ |
|
296 ) |
295 actions = [] |
297 actions = [] |
|
298 |
296 def addverb(v): |
299 def addverb(v): |
297 a = actiontable[v] |
300 a = actiontable[v] |
298 lines = a.message.split("\n") |
301 lines = a.message.split("\n") |
299 if len(a.verbs): |
302 if len(a.verbs): |
300 v = ', '.join(sorted(a.verbs, key=lambda v: len(v))) |
303 v = ', '.join(sorted(a.verbs, key=lambda v: len(v))) |
301 actions.append(" %s = %s" % (v, lines[0])) |
304 actions.append(" %s = %s" % (v, lines[0])) |
302 actions.extend([' %s' for l in lines[1:]]) |
305 actions.extend([' %s' for l in lines[1:]]) |
303 |
306 |
304 for v in ( |
307 for v in ( |
305 sorted(primaryactions) + |
308 sorted(primaryactions) |
306 sorted(secondaryactions) + |
309 + sorted(secondaryactions) |
307 sorted(tertiaryactions) |
310 + sorted(tertiaryactions) |
308 ): |
311 ): |
309 addverb(v) |
312 addverb(v) |
310 actions.append('') |
313 actions.append('') |
311 |
314 |
312 hints = [] |
315 hints = [] |
313 if ui.configbool('histedit', 'dropmissing'): |
316 if ui.configbool('histedit', 'dropmissing'): |
314 hints.append("Deleting a changeset from the list " |
317 hints.append( |
315 "will DISCARD it from the edited history!") |
318 "Deleting a changeset from the list " |
|
319 "will DISCARD it from the edited history!" |
|
320 ) |
316 |
321 |
317 lines = (intro % (first, last)).split('\n') + actions + hints |
322 lines = (intro % (first, last)).split('\n') + actions + hints |
318 |
323 |
319 return ''.join(['# %s\n' % l if l else '#\n' for l in lines]) |
324 return ''.join(['# %s\n' % l if l else '#\n' for l in lines]) |
|
325 |
320 |
326 |
321 class histeditstate(object): |
327 class histeditstate(object): |
322 def __init__(self, repo): |
328 def __init__(self, repo): |
323 self.repo = repo |
329 self.repo = repo |
324 self.actions = None |
330 self.actions = None |
355 data = pickle.loads(fp) |
361 data = pickle.loads(fp) |
356 parentctxnode, rules, keep, topmost, replacements = data |
362 parentctxnode, rules, keep, topmost, replacements = data |
357 backupfile = None |
363 backupfile = None |
358 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules]) |
364 rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules]) |
359 |
365 |
360 return {'parentctxnode': parentctxnode, "rules": rules, "keep": keep, |
366 return { |
361 "topmost": topmost, "replacements": replacements, |
367 'parentctxnode': parentctxnode, |
362 "backupfile": backupfile} |
368 "rules": rules, |
|
369 "keep": keep, |
|
370 "topmost": topmost, |
|
371 "replacements": replacements, |
|
372 "backupfile": backupfile, |
|
373 } |
363 |
374 |
364 def write(self, tr=None): |
375 def write(self, tr=None): |
365 if tr: |
376 if tr: |
366 tr.addfilegenerator('histedit-state', ('histedit-state',), |
377 tr.addfilegenerator( |
367 self._write, location='plain') |
378 'histedit-state', |
|
379 ('histedit-state',), |
|
380 self._write, |
|
381 location='plain', |
|
382 ) |
368 else: |
383 else: |
369 with self.repo.vfs("histedit-state", "w") as f: |
384 with self.repo.vfs("histedit-state", "w") as f: |
370 self._write(f) |
385 self._write(f) |
371 |
386 |
372 def _write(self, fp): |
387 def _write(self, fp): |
475 self._verifynodeconstraints(prev, expected, seen) |
497 self._verifynodeconstraints(prev, expected, seen) |
476 |
498 |
477 def _verifynodeconstraints(self, prev, expected, seen): |
499 def _verifynodeconstraints(self, prev, expected, seen): |
478 # by default command need a node in the edited list |
500 # by default command need a node in the edited list |
479 if self.node not in expected: |
501 if self.node not in expected: |
480 raise error.ParseError(_('%s "%s" changeset was not a candidate') |
502 raise error.ParseError( |
481 % (self.verb, node.short(self.node)), |
503 _('%s "%s" changeset was not a candidate') |
482 hint=_('only use listed changesets')) |
504 % (self.verb, node.short(self.node)), |
|
505 hint=_('only use listed changesets'), |
|
506 ) |
483 # and only one command per node |
507 # and only one command per node |
484 if self.node in seen: |
508 if self.node in seen: |
485 raise error.ParseError(_('duplicated command for changeset %s') % |
509 raise error.ParseError( |
486 node.short(self.node)) |
510 _('duplicated command for changeset %s') % node.short(self.node) |
|
511 ) |
487 |
512 |
488 def torule(self): |
513 def torule(self): |
489 """build a histedit rule line for an action |
514 """build a histedit rule line for an action |
490 |
515 |
491 by default lines are in the form: |
516 by default lines are in the form: |
492 <hash> <rev> <summary> |
517 <hash> <rev> <summary> |
493 """ |
518 """ |
494 ctx = self.repo[self.node] |
519 ctx = self.repo[self.node] |
495 ui = self.repo.ui |
520 ui = self.repo.ui |
496 summary = cmdutil.rendertemplate( |
521 summary = ( |
497 ctx, ui.config('histedit', 'summary-template')) or '' |
522 cmdutil.rendertemplate( |
|
523 ctx, ui.config('histedit', 'summary-template') |
|
524 ) |
|
525 or '' |
|
526 ) |
498 summary = summary.splitlines()[0] |
527 summary = summary.splitlines()[0] |
499 line = '%s %s %s' % (self.verb, ctx, summary) |
528 line = '%s %s %s' % (self.verb, ctx, summary) |
500 # trim to 75 columns by default so it's not stupidly wide in my editor |
529 # trim to 75 columns by default so it's not stupidly wide in my editor |
501 # (the 5 more are left for verb) |
530 # (the 5 more are left for verb) |
502 maxlen = self.repo.ui.configint('histedit', 'linelen') |
531 maxlen = self.repo.ui.configint('histedit', 'linelen') |
503 maxlen = max(maxlen, 22) # avoid truncating hash |
532 maxlen = max(maxlen, 22) # avoid truncating hash |
504 return stringutil.ellipsis(line, maxlen) |
533 return stringutil.ellipsis(line, maxlen) |
505 |
534 |
506 def tostate(self): |
535 def tostate(self): |
507 """Print an action in format used by histedit state files |
536 """Print an action in format used by histedit state files |
508 (the first line is a verb, the remainder is the second) |
537 (the first line is a verb, the remainder is the second) |
574 |
612 |
575 Note that fold has its own separated logic because its handling is a bit |
613 Note that fold has its own separated logic because its handling is a bit |
576 different and not easily factored out of the fold method. |
614 different and not easily factored out of the fold method. |
577 """ |
615 """ |
578 phasemin = src.phase() |
616 phasemin = src.phase() |
|
617 |
579 def commitfunc(**kwargs): |
618 def commitfunc(**kwargs): |
580 overrides = {('phases', 'new-commit'): phasemin} |
619 overrides = {('phases', 'new-commit'): phasemin} |
581 with repo.ui.configoverride(overrides, 'histedit'): |
620 with repo.ui.configoverride(overrides, 'histedit'): |
582 extra = kwargs.get(r'extra', {}).copy() |
621 extra = kwargs.get(r'extra', {}).copy() |
583 extra['histedit_source'] = src.hex() |
622 extra['histedit_source'] = src.hex() |
584 kwargs[r'extra'] = extra |
623 kwargs[r'extra'] = extra |
585 return repo.commit(**kwargs) |
624 return repo.commit(**kwargs) |
|
625 |
586 return commitfunc |
626 return commitfunc |
|
627 |
587 |
628 |
588 def applychanges(ui, repo, ctx, opts): |
629 def applychanges(ui, repo, ctx, opts): |
589 """Merge changeset from ctx (only) in the current working directory""" |
630 """Merge changeset from ctx (only) in the current working directory""" |
590 wcpar = repo.dirstate.p1() |
631 wcpar = repo.dirstate.p1() |
591 if ctx.p1().node() == wcpar: |
632 if ctx.p1().node() == wcpar: |
596 stats = mergemod.updateresult(0, 0, 0, 0) |
637 stats = mergemod.updateresult(0, 0, 0, 0) |
597 ui.popbuffer() |
638 ui.popbuffer() |
598 else: |
639 else: |
599 try: |
640 try: |
600 # ui.forcemerge is an internal variable, do not document |
641 # ui.forcemerge is an internal variable, do not document |
601 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), |
642 repo.ui.setconfig( |
602 'histedit') |
643 'ui', 'forcemerge', opts.get('tool', ''), 'histedit' |
|
644 ) |
603 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit']) |
645 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit']) |
604 finally: |
646 finally: |
605 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit') |
647 repo.ui.setconfig('ui', 'forcemerge', '', 'histedit') |
606 return stats |
648 return stats |
|
649 |
607 |
650 |
608 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False): |
651 def collapse(repo, firstctx, lastctx, commitopts, skipprompt=False): |
609 """collapse the set of revisions from first to last as new one. |
652 """collapse the set of revisions from first to last as new one. |
610 |
653 |
611 Expected commit options are: |
654 Expected commit options are: |
697 cls.verbs = verbs |
755 cls.verbs = verbs |
698 cls.message = message |
756 cls.message = message |
699 for verb in verbs: |
757 for verb in verbs: |
700 actiontable[verb] = cls |
758 actiontable[verb] = cls |
701 return cls |
759 return cls |
|
760 |
702 return wrap |
761 return wrap |
703 |
762 |
704 @action(['pick', 'p'], |
763 |
705 _('use commit'), |
764 @action(['pick', 'p'], _('use commit'), priority=True) |
706 priority=True) |
|
707 class pick(histeditaction): |
765 class pick(histeditaction): |
708 def run(self): |
766 def run(self): |
709 rulectx = self.repo[self.node] |
767 rulectx = self.repo[self.node] |
710 if rulectx.p1().node() == self.state.parentctxnode: |
768 if rulectx.p1().node() == self.state.parentctxnode: |
711 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node)) |
769 self.repo.ui.debug('node %s unchanged\n' % node.short(self.node)) |
712 return rulectx, [] |
770 return rulectx, [] |
713 |
771 |
714 return super(pick, self).run() |
772 return super(pick, self).run() |
715 |
773 |
716 @action(['edit', 'e'], |
774 |
717 _('use commit, but stop for amending'), |
775 @action(['edit', 'e'], _('use commit, but stop for amending'), priority=True) |
718 priority=True) |
|
719 class edit(histeditaction): |
776 class edit(histeditaction): |
720 def run(self): |
777 def run(self): |
721 repo = self.repo |
778 repo = self.repo |
722 rulectx = repo[self.node] |
779 rulectx = repo[self.node] |
723 hg.update(repo, self.state.parentctxnode, quietempty=True) |
780 hg.update(repo, self.state.parentctxnode, quietempty=True) |
724 applychanges(repo.ui, repo, rulectx, {}) |
781 applychanges(repo.ui, repo, rulectx, {}) |
725 raise error.InterventionRequired( |
782 raise error.InterventionRequired( |
726 _('Editing (%s), you may commit or record as needed now.') |
783 _('Editing (%s), you may commit or record as needed now.') |
727 % node.short(self.node), |
784 % node.short(self.node), |
728 hint=_('hg histedit --continue to resume')) |
785 hint=_('hg histedit --continue to resume'), |
|
786 ) |
729 |
787 |
730 def commiteditor(self): |
788 def commiteditor(self): |
731 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit') |
789 return cmdutil.getcommiteditor(edit=True, editform='histedit.edit') |
732 |
790 |
733 @action(['fold', 'f'], |
791 |
734 _('use commit, but combine it with the one above')) |
792 @action(['fold', 'f'], _('use commit, but combine it with the one above')) |
735 class fold(histeditaction): |
793 class fold(histeditaction): |
736 def verify(self, prev, expected, seen): |
794 def verify(self, prev, expected, seen): |
737 """ Verifies semantic correctness of the fold rule""" |
795 """ Verifies semantic correctness of the fold rule""" |
738 super(fold, self).verify(prev, expected, seen) |
796 super(fold, self).verify(prev, expected, seen) |
739 repo = self.repo |
797 repo = self.repo |
743 return |
801 return |
744 else: |
802 else: |
745 c = repo[prev.node] |
803 c = repo[prev.node] |
746 if not c.mutable(): |
804 if not c.mutable(): |
747 raise error.ParseError( |
805 raise error.ParseError( |
748 _("cannot fold into public change %s") % node.short(c.node())) |
806 _("cannot fold into public change %s") % node.short(c.node()) |
749 |
807 ) |
750 |
808 |
751 def continuedirty(self): |
809 def continuedirty(self): |
752 repo = self.repo |
810 repo = self.repo |
753 rulectx = repo[self.node] |
811 rulectx = repo[self.node] |
754 |
812 |
755 commit = commitfuncfor(repo, rulectx) |
813 commit = commitfuncfor(repo, rulectx) |
756 commit(text='fold-temp-revision %s' % node.short(self.node), |
814 commit( |
757 user=rulectx.user(), date=rulectx.date(), |
815 text='fold-temp-revision %s' % node.short(self.node), |
758 extra=rulectx.extra()) |
816 user=rulectx.user(), |
|
817 date=rulectx.date(), |
|
818 extra=rulectx.extra(), |
|
819 ) |
759 |
820 |
760 def continueclean(self): |
821 def continueclean(self): |
761 repo = self.repo |
822 repo = self.repo |
762 ctx = repo['.'] |
823 ctx = repo['.'] |
763 rulectx = repo[self.node] |
824 rulectx = repo[self.node] |
764 parentctxnode = self.state.parentctxnode |
825 parentctxnode = self.state.parentctxnode |
765 if ctx.node() == parentctxnode: |
826 if ctx.node() == parentctxnode: |
766 repo.ui.warn(_('%s: empty changeset\n') % |
827 repo.ui.warn(_('%s: empty changeset\n') % node.short(self.node)) |
767 node.short(self.node)) |
|
768 return ctx, [(self.node, (parentctxnode,))] |
828 return ctx, [(self.node, (parentctxnode,))] |
769 |
829 |
770 parentctx = repo[parentctxnode] |
830 parentctx = repo[parentctxnode] |
771 newcommits = set(c.node() for c in repo.set('(%d::. - %d)', |
831 newcommits = set( |
772 parentctx.rev(), |
832 c.node() |
773 parentctx.rev())) |
833 for c in repo.set('(%d::. - %d)', parentctx.rev(), parentctx.rev()) |
|
834 ) |
774 if not newcommits: |
835 if not newcommits: |
775 repo.ui.warn(_('%s: cannot fold - working copy is not a ' |
836 repo.ui.warn( |
776 'descendant of previous commit %s\n') % |
837 _( |
777 (node.short(self.node), node.short(parentctxnode))) |
838 '%s: cannot fold - working copy is not a ' |
|
839 'descendant of previous commit %s\n' |
|
840 ) |
|
841 % (node.short(self.node), node.short(parentctxnode)) |
|
842 ) |
778 return ctx, [(self.node, (ctx.node(),))] |
843 return ctx, [(self.node, (ctx.node(),))] |
779 |
844 |
780 middlecommits = newcommits.copy() |
845 middlecommits = newcommits.copy() |
781 middlecommits.discard(ctx.node()) |
846 middlecommits.discard(ctx.node()) |
782 |
847 |
783 return self.finishfold(repo.ui, repo, parentctx, rulectx, ctx.node(), |
848 return self.finishfold( |
784 middlecommits) |
849 repo.ui, repo, parentctx, rulectx, ctx.node(), middlecommits |
|
850 ) |
785 |
851 |
786 def skipprompt(self): |
852 def skipprompt(self): |
787 """Returns true if the rule should skip the message editor. |
853 """Returns true if the rule should skip the message editor. |
788 |
854 |
789 For example, 'fold' wants to show an editor, but 'rollup' |
855 For example, 'fold' wants to show an editor, but 'rollup' |
839 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex()) |
909 extra['histedit_source'] = '%s,%s' % (ctx.hex(), oldctx.hex()) |
840 commitopts['extra'] = extra |
910 commitopts['extra'] = extra |
841 phasemin = max(ctx.phase(), oldctx.phase()) |
911 phasemin = max(ctx.phase(), oldctx.phase()) |
842 overrides = {('phases', 'new-commit'): phasemin} |
912 overrides = {('phases', 'new-commit'): phasemin} |
843 with repo.ui.configoverride(overrides, 'histedit'): |
913 with repo.ui.configoverride(overrides, 'histedit'): |
844 n = collapse(repo, ctx, repo[newnode], commitopts, |
914 n = collapse( |
845 skipprompt=self.skipprompt()) |
915 repo, |
|
916 ctx, |
|
917 repo[newnode], |
|
918 commitopts, |
|
919 skipprompt=self.skipprompt(), |
|
920 ) |
846 if n is None: |
921 if n is None: |
847 return ctx, [] |
922 return ctx, [] |
848 hg.updaterepo(repo, n, overwrite=False) |
923 hg.updaterepo(repo, n, overwrite=False) |
849 replacements = [(oldctx.node(), (newnode,)), |
924 replacements = [ |
850 (ctx.node(), (n,)), |
925 (oldctx.node(), (newnode,)), |
851 (newnode, (n,)), |
926 (ctx.node(), (n,)), |
852 ] |
927 (newnode, (n,)), |
|
928 ] |
853 for ich in internalchanges: |
929 for ich in internalchanges: |
854 replacements.append((ich, (n,))) |
930 replacements.append((ich, (n,))) |
855 return repo[n], replacements |
931 return repo[n], replacements |
856 |
932 |
857 @action(['base', 'b'], |
933 |
858 _('checkout changeset and apply further changesets from there')) |
934 @action( |
|
935 ['base', 'b'], |
|
936 _('checkout changeset and apply further changesets from there'), |
|
937 ) |
859 class base(histeditaction): |
938 class base(histeditaction): |
860 |
|
861 def run(self): |
939 def run(self): |
862 if self.repo['.'].node() != self.node: |
940 if self.repo['.'].node() != self.node: |
863 mergemod.update(self.repo, self.node, branchmerge=False, force=True) |
941 mergemod.update(self.repo, self.node, branchmerge=False, force=True) |
864 return self.continueclean() |
942 return self.continueclean() |
865 |
943 |
874 # base can only be use with a node not in the edited set |
952 # base can only be use with a node not in the edited set |
875 if self.node in expected: |
953 if self.node in expected: |
876 msg = _('%s "%s" changeset was an edited list candidate') |
954 msg = _('%s "%s" changeset was an edited list candidate') |
877 raise error.ParseError( |
955 raise error.ParseError( |
878 msg % (self.verb, node.short(self.node)), |
956 msg % (self.verb, node.short(self.node)), |
879 hint=_('base must only use unlisted changesets')) |
957 hint=_('base must only use unlisted changesets'), |
880 |
958 ) |
881 @action(['_multifold'], |
959 |
882 _( |
960 |
883 """fold subclass used for when multiple folds happen in a row |
961 @action( |
|
962 ['_multifold'], |
|
963 _( |
|
964 """fold subclass used for when multiple folds happen in a row |
884 |
965 |
885 We only want to fire the editor for the folded message once when |
966 We only want to fire the editor for the folded message once when |
886 (say) four changes are folded down into a single change. This is |
967 (say) four changes are folded down into a single change. This is |
887 similar to rollup, but we should preserve both messages so that |
968 similar to rollup, but we should preserve both messages so that |
888 when the last fold operation runs we can show the user all the |
969 when the last fold operation runs we can show the user all the |
889 commit messages in their editor. |
970 commit messages in their editor. |
890 """), |
971 """ |
891 internal=True) |
972 ), |
|
973 internal=True, |
|
974 ) |
892 class _multifold(fold): |
975 class _multifold(fold): |
893 def skipprompt(self): |
976 def skipprompt(self): |
894 return True |
977 return True |
895 |
978 |
896 @action(["roll", "r"], |
979 |
897 _("like fold, but discard this commit's description and date")) |
980 @action( |
|
981 ["roll", "r"], |
|
982 _("like fold, but discard this commit's description and date"), |
|
983 ) |
898 class rollup(fold): |
984 class rollup(fold): |
899 def mergedescs(self): |
985 def mergedescs(self): |
900 return False |
986 return False |
901 |
987 |
902 def skipprompt(self): |
988 def skipprompt(self): |
903 return True |
989 return True |
904 |
990 |
905 def firstdate(self): |
991 def firstdate(self): |
906 return True |
992 return True |
907 |
993 |
908 @action(["drop", "d"], |
994 |
909 _('remove commit from history')) |
995 @action(["drop", "d"], _('remove commit from history')) |
910 class drop(histeditaction): |
996 class drop(histeditaction): |
911 def run(self): |
997 def run(self): |
912 parentctx = self.repo[self.state.parentctxnode] |
998 parentctx = self.repo[self.state.parentctxnode] |
913 return parentctx, [(self.node, tuple())] |
999 return parentctx, [(self.node, tuple())] |
914 |
1000 |
915 @action(["mess", "m"], |
1001 |
916 _('edit commit message without changing commit content'), |
1002 @action( |
917 priority=True) |
1003 ["mess", "m"], |
|
1004 _('edit commit message without changing commit content'), |
|
1005 priority=True, |
|
1006 ) |
918 class message(histeditaction): |
1007 class message(histeditaction): |
919 def commiteditor(self): |
1008 def commiteditor(self): |
920 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess') |
1009 return cmdutil.getcommiteditor(edit=True, editform='histedit.mess') |
|
1010 |
921 |
1011 |
922 def findoutgoing(ui, repo, remote=None, force=False, opts=None): |
1012 def findoutgoing(ui, repo, remote=None, force=False, opts=None): |
923 """utility function to find the first outgoing changeset |
1013 """utility function to find the first outgoing changeset |
924 |
1014 |
925 Used by initialization code""" |
1015 Used by initialization code""" |
955 ACTION_LABELS = { |
1046 ACTION_LABELS = { |
956 'fold': '^fold', |
1047 'fold': '^fold', |
957 'roll': '^roll', |
1048 'roll': '^roll', |
958 } |
1049 } |
959 |
1050 |
960 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5 |
1051 COLOR_HELP, COLOR_SELECTED, COLOR_OK, COLOR_WARN, COLOR_CURRENT = 1, 2, 3, 4, 5 |
961 COLOR_DIFF_ADD_LINE, COLOR_DIFF_DEL_LINE, COLOR_DIFF_OFFSET = 6, 7, 8 |
1052 COLOR_DIFF_ADD_LINE, COLOR_DIFF_DEL_LINE, COLOR_DIFF_OFFSET = 6, 7, 8 |
962 |
1053 |
963 E_QUIT, E_HISTEDIT = 1, 2 |
1054 E_QUIT, E_HISTEDIT = 1, 2 |
964 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7 |
1055 E_PAGEDOWN, E_PAGEUP, E_LINEUP, E_LINEDOWN, E_RESIZE = 3, 4, 5, 6, 7 |
965 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3 |
1056 MODE_INIT, MODE_PATCH, MODE_RULES, MODE_HELP = 0, 1, 2, 3 |
966 |
1057 |
967 KEYTABLE = { |
1058 KEYTABLE = { |
968 'global': { |
1059 'global': { |
969 'h': 'next-action', |
1060 'h': 'next-action', |
970 'KEY_RIGHT': 'next-action', |
1061 'KEY_RIGHT': 'next-action', |
971 'l': 'prev-action', |
1062 'l': 'prev-action', |
972 'KEY_LEFT': 'prev-action', |
1063 'KEY_LEFT': 'prev-action', |
973 'q': 'quit', |
1064 'q': 'quit', |
974 'c': 'histedit', |
1065 'c': 'histedit', |
975 'C': 'histedit', |
1066 'C': 'histedit', |
976 'v': 'showpatch', |
1067 'v': 'showpatch', |
977 '?': 'help', |
1068 '?': 'help', |
978 }, |
1069 }, |
979 MODE_RULES: { |
1070 MODE_RULES: { |
980 'd': 'action-drop', |
1071 'd': 'action-drop', |
981 'e': 'action-edit', |
1072 'e': 'action-edit', |
982 'f': 'action-fold', |
1073 'f': 'action-fold', |
983 'm': 'action-mess', |
1074 'm': 'action-mess', |
984 'p': 'action-pick', |
1075 'p': 'action-pick', |
985 'r': 'action-roll', |
1076 'r': 'action-roll', |
986 ' ': 'select', |
1077 ' ': 'select', |
987 'j': 'down', |
1078 'j': 'down', |
988 'k': 'up', |
1079 'k': 'up', |
989 'KEY_DOWN': 'down', |
1080 'KEY_DOWN': 'down', |
990 'KEY_UP': 'up', |
1081 'KEY_UP': 'up', |
991 'J': 'move-down', |
1082 'J': 'move-down', |
992 'K': 'move-up', |
1083 'K': 'move-up', |
993 'KEY_NPAGE': 'move-down', |
1084 'KEY_NPAGE': 'move-down', |
994 'KEY_PPAGE': 'move-up', |
1085 'KEY_PPAGE': 'move-up', |
995 '0': 'goto', # Used for 0..9 |
1086 '0': 'goto', # Used for 0..9 |
996 }, |
1087 }, |
997 MODE_PATCH: { |
1088 MODE_PATCH: { |
998 ' ': 'page-down', |
1089 ' ': 'page-down', |
999 'KEY_NPAGE': 'page-down', |
1090 'KEY_NPAGE': 'page-down', |
1000 'KEY_PPAGE': 'page-up', |
1091 'KEY_PPAGE': 'page-up', |
1001 'j': 'line-down', |
1092 'j': 'line-down', |
1002 'k': 'line-up', |
1093 'k': 'line-up', |
1003 'KEY_DOWN': 'line-down', |
1094 'KEY_DOWN': 'line-down', |
1004 'KEY_UP': 'line-up', |
1095 'KEY_UP': 'line-up', |
1005 'J': 'down', |
1096 'J': 'down', |
1006 'K': 'up', |
1097 'K': 'up', |
1007 }, |
1098 }, |
1008 MODE_HELP: { |
1099 MODE_HELP: {}, |
1009 }, |
|
1010 } |
1100 } |
|
1101 |
1011 |
1102 |
1012 def screen_size(): |
1103 def screen_size(): |
1013 return struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, ' ')) |
1104 return struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, ' ')) |
|
1105 |
1014 |
1106 |
1015 class histeditrule(object): |
1107 class histeditrule(object): |
1016 def __init__(self, ctx, pos, action='pick'): |
1108 def __init__(self, ctx, pos, action='pick'): |
1017 self.ctx = ctx |
1109 self.ctx = ctx |
1018 self.action = action |
1110 self.action = action |
1037 r = self.ctx.rev() |
1129 r = self.ctx.rev() |
1038 desc = self.ctx.description().splitlines()[0].strip() |
1130 desc = self.ctx.description().splitlines()[0].strip() |
1039 if self.action == 'roll': |
1131 if self.action == 'roll': |
1040 desc = '' |
1132 desc = '' |
1041 return "#{0:<2} {1:<6} {2}:{3} {4}".format( |
1133 return "#{0:<2} {1:<6} {2}:{3} {4}".format( |
1042 self.origpos, action, r, h, desc) |
1134 self.origpos, action, r, h, desc |
|
1135 ) |
1043 |
1136 |
1044 def checkconflicts(self, other): |
1137 def checkconflicts(self, other): |
1045 if other.pos > self.pos and other.origpos <= self.origpos: |
1138 if other.pos > self.pos and other.origpos <= self.origpos: |
1046 if set(other.ctx.files()) & set(self.ctx.files()) != set(): |
1139 if set(other.ctx.files()) & set(self.ctx.files()) != set(): |
1047 self.conflicts.append(other) |
1140 self.conflicts.append(other) |
1048 return self.conflicts |
1141 return self.conflicts |
1049 |
1142 |
1050 if other in self.conflicts: |
1143 if other in self.conflicts: |
1051 self.conflicts.remove(other) |
1144 self.conflicts.remove(other) |
1052 return self.conflicts |
1145 return self.conflicts |
|
1146 |
1053 |
1147 |
1054 # ============ EVENTS =============== |
1148 # ============ EVENTS =============== |
1055 def movecursor(state, oldpos, newpos): |
1149 def movecursor(state, oldpos, newpos): |
1056 '''Change the rule/changeset that the cursor is pointing to, regardless of |
1150 '''Change the rule/changeset that the cursor is pointing to, regardless of |
1057 current mode (you can switch between patches from the view patch window).''' |
1151 current mode (you can switch between patches from the view patch window).''' |
1069 modestate['line_offset'] = newpos - state['page_height'] + 1 |
1163 modestate['line_offset'] = newpos - state['page_height'] + 1 |
1070 |
1164 |
1071 # Reset the patch view region to the top of the new patch. |
1165 # Reset the patch view region to the top of the new patch. |
1072 state['modes'][MODE_PATCH]['line_offset'] = 0 |
1166 state['modes'][MODE_PATCH]['line_offset'] = 0 |
1073 |
1167 |
|
1168 |
1074 def changemode(state, mode): |
1169 def changemode(state, mode): |
1075 curmode, _ = state['mode'] |
1170 curmode, _ = state['mode'] |
1076 state['mode'] = (mode, curmode) |
1171 state['mode'] = (mode, curmode) |
1077 if mode == MODE_PATCH: |
1172 if mode == MODE_PATCH: |
1078 state['modes'][MODE_PATCH]['patchcontents'] = patchcontents(state) |
1173 state['modes'][MODE_PATCH]['patchcontents'] = patchcontents(state) |
1079 |
1174 |
|
1175 |
1080 def makeselection(state, pos): |
1176 def makeselection(state, pos): |
1081 state['selected'] = pos |
1177 state['selected'] = pos |
|
1178 |
1082 |
1179 |
1083 def swap(state, oldpos, newpos): |
1180 def swap(state, oldpos, newpos): |
1084 """Swap two positions and calculate necessary conflicts in |
1181 """Swap two positions and calculate necessary conflicts in |
1085 O(|newpos-oldpos|) time""" |
1182 O(|newpos-oldpos|) time""" |
1086 |
1183 |
1222 if color: |
1325 if color: |
1223 win.addstr(y, x, line, color) |
1326 win.addstr(y, x, line, color) |
1224 else: |
1327 else: |
1225 win.addstr(y, x, line) |
1328 win.addstr(y, x, line) |
1226 |
1329 |
|
1330 |
1227 def _trunc_head(line, n): |
1331 def _trunc_head(line, n): |
1228 if len(line) <= n: |
1332 if len(line) <= n: |
1229 return line |
1333 return line |
1230 return '> ' + line[-(n - 2):] |
1334 return '> ' + line[-(n - 2) :] |
|
1335 |
|
1336 |
1231 def _trunc_tail(line, n): |
1337 def _trunc_tail(line, n): |
1232 if len(line) <= n: |
1338 if len(line) <= n: |
1233 return line |
1339 return line |
1234 return line[:n - 2] + ' >' |
1340 return line[: n - 2] + ' >' |
|
1341 |
1235 |
1342 |
1236 def patchcontents(state): |
1343 def patchcontents(state): |
1237 repo = state['repo'] |
1344 repo = state['repo'] |
1238 rule = state['rules'][state['pos']] |
1345 rule = state['rules'][state['pos']] |
1239 displayer = logcmdutil.changesetdisplayer(repo.ui, repo, { |
1346 displayer = logcmdutil.changesetdisplayer( |
1240 "patch": True, "template": "status" |
1347 repo.ui, repo, {"patch": True, "template": "status"}, buffered=True |
1241 }, buffered=True) |
1348 ) |
1242 overrides = {('ui', 'verbose'): True} |
1349 overrides = {('ui', 'verbose'): True} |
1243 with repo.ui.configoverride(overrides, source='histedit'): |
1350 with repo.ui.configoverride(overrides, source='histedit'): |
1244 displayer.show(rule.ctx) |
1351 displayer.show(rule.ctx) |
1245 displayer.close() |
1352 displayer.close() |
1246 return displayer.hunk[rule.ctx.rev()].splitlines() |
1353 return displayer.hunk[rule.ctx.rev()].splitlines() |
|
1354 |
1247 |
1355 |
1248 def _chisteditmain(repo, rules, stdscr): |
1356 def _chisteditmain(repo, rules, stdscr): |
1249 try: |
1357 try: |
1250 curses.use_default_colors() |
1358 curses.use_default_colors() |
1251 except curses.error: |
1359 except curses.error: |
1505 revs = opts.get('rev', [])[:] |
1616 revs = opts.get('rev', [])[:] |
1506 cmdutil.checkunfinished(repo) |
1617 cmdutil.checkunfinished(repo) |
1507 cmdutil.bailifchanged(repo) |
1618 cmdutil.bailifchanged(repo) |
1508 |
1619 |
1509 if os.path.exists(os.path.join(repo.path, 'histedit-state')): |
1620 if os.path.exists(os.path.join(repo.path, 'histedit-state')): |
1510 raise error.Abort(_('history edit already in progress, try ' |
1621 raise error.Abort( |
1511 '--continue or --abort')) |
1622 _( |
|
1623 'history edit already in progress, try ' |
|
1624 '--continue or --abort' |
|
1625 ) |
|
1626 ) |
1512 revs.extend(freeargs) |
1627 revs.extend(freeargs) |
1513 if not revs: |
1628 if not revs: |
1514 defaultrev = destutil.desthistedit(ui, repo) |
1629 defaultrev = destutil.desthistedit(ui, repo) |
1515 if defaultrev is not None: |
1630 if defaultrev is not None: |
1516 revs.append(defaultrev) |
1631 revs.append(defaultrev) |
1517 if len(revs) != 1: |
1632 if len(revs) != 1: |
1518 raise error.Abort( |
1633 raise error.Abort( |
1519 _('histedit requires exactly one ancestor revision')) |
1634 _('histedit requires exactly one ancestor revision') |
|
1635 ) |
1520 |
1636 |
1521 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs))) |
1637 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs))) |
1522 if len(rr) != 1: |
1638 if len(rr) != 1: |
1523 raise error.Abort(_('The specified revisions must have ' |
1639 raise error.Abort( |
1524 'exactly one common root')) |
1640 _( |
|
1641 'The specified revisions must have ' |
|
1642 'exactly one common root' |
|
1643 ) |
|
1644 ) |
1525 root = rr[0].node() |
1645 root = rr[0].node() |
1526 |
1646 |
1527 topmost = repo.dirstate.p1() |
1647 topmost = repo.dirstate.p1() |
1528 revs = between(repo, root, topmost, keep) |
1648 revs = between(repo, root, topmost, keep) |
1529 if not revs: |
1649 if not revs: |
1530 raise error.Abort(_('%s is not an ancestor of working directory') % |
1650 raise error.Abort( |
1531 node.short(root)) |
1651 _('%s is not an ancestor of working directory') |
|
1652 % node.short(root) |
|
1653 ) |
1532 |
1654 |
1533 ctxs = [] |
1655 ctxs = [] |
1534 for i, r in enumerate(revs): |
1656 for i, r in enumerate(revs): |
1535 ctxs.append(histeditrule(repo[r], i)) |
1657 ctxs.append(histeditrule(repo[r], i)) |
1536 # Curses requires setting the locale or it will default to the C |
1658 # Curses requires setting the locale or it will default to the C |
1554 return _texthistedit(ui, repo, *freeargs, **opts) |
1676 return _texthistedit(ui, repo, *freeargs, **opts) |
1555 except KeyboardInterrupt: |
1677 except KeyboardInterrupt: |
1556 pass |
1678 pass |
1557 return -1 |
1679 return -1 |
1558 |
1680 |
1559 @command('histedit', |
1681 |
1560 [('', 'commands', '', |
1682 @command( |
1561 _('read history edits from the specified file'), _('FILE')), |
1683 'histedit', |
1562 ('c', 'continue', False, _('continue an edit already in progress')), |
1684 [ |
1563 ('', 'edit-plan', False, _('edit remaining actions list')), |
1685 ( |
1564 ('k', 'keep', False, |
1686 '', |
1565 _("don't strip old nodes after edit is complete")), |
1687 'commands', |
1566 ('', 'abort', False, _('abort an edit in progress')), |
1688 '', |
1567 ('o', 'outgoing', False, _('changesets not found in destination')), |
1689 _('read history edits from the specified file'), |
1568 ('f', 'force', False, |
1690 _('FILE'), |
1569 _('force outgoing even for unrelated repositories')), |
1691 ), |
1570 ('r', 'rev', [], _('first revision to be edited'), _('REV'))] + |
1692 ('c', 'continue', False, _('continue an edit already in progress')), |
1571 cmdutil.formatteropts, |
1693 ('', 'edit-plan', False, _('edit remaining actions list')), |
1572 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"), |
1694 ('k', 'keep', False, _("don't strip old nodes after edit is complete")), |
1573 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT) |
1695 ('', 'abort', False, _('abort an edit in progress')), |
|
1696 ('o', 'outgoing', False, _('changesets not found in destination')), |
|
1697 ( |
|
1698 'f', |
|
1699 'force', |
|
1700 False, |
|
1701 _('force outgoing even for unrelated repositories'), |
|
1702 ), |
|
1703 ('r', 'rev', [], _('first revision to be edited'), _('REV')), |
|
1704 ] |
|
1705 + cmdutil.formatteropts, |
|
1706 _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"), |
|
1707 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, |
|
1708 ) |
1574 def histedit(ui, repo, *freeargs, **opts): |
1709 def histedit(ui, repo, *freeargs, **opts): |
1575 """interactively edit changeset history |
1710 """interactively edit changeset history |
1576 |
1711 |
1577 This command lets you edit a linear series of changesets (up to |
1712 This command lets you edit a linear series of changesets (up to |
1578 and including the working directory, which should be clean). |
1713 and including the working directory, which should be clean). |
1671 conflicts). |
1806 conflicts). |
1672 """ |
1807 """ |
1673 # kludge: _chistedit only works for starting an edit, not aborting |
1808 # kludge: _chistedit only works for starting an edit, not aborting |
1674 # or continuing, so fall back to regular _texthistedit for those |
1809 # or continuing, so fall back to regular _texthistedit for those |
1675 # operations. |
1810 # operations. |
1676 if ui.interface('histedit') == 'curses' and _getgoal( |
1811 if ( |
1677 pycompat.byteskwargs(opts)) == goalnew: |
1812 ui.interface('histedit') == 'curses' |
|
1813 and _getgoal(pycompat.byteskwargs(opts)) == goalnew |
|
1814 ): |
1678 return _chistedit(ui, repo, *freeargs, **opts) |
1815 return _chistedit(ui, repo, *freeargs, **opts) |
1679 return _texthistedit(ui, repo, *freeargs, **opts) |
1816 return _texthistedit(ui, repo, *freeargs, **opts) |
|
1817 |
1680 |
1818 |
1681 def _texthistedit(ui, repo, *freeargs, **opts): |
1819 def _texthistedit(ui, repo, *freeargs, **opts): |
1682 state = histeditstate(repo) |
1820 state = histeditstate(repo) |
1683 with repo.wlock() as wlock, repo.lock() as lock: |
1821 with repo.wlock() as wlock, repo.lock() as lock: |
1684 state.wlock = wlock |
1822 state.wlock = wlock |
1685 state.lock = lock |
1823 state.lock = lock |
1686 _histedit(ui, repo, state, *freeargs, **opts) |
1824 _histedit(ui, repo, state, *freeargs, **opts) |
1687 |
1825 |
|
1826 |
1688 goalcontinue = 'continue' |
1827 goalcontinue = 'continue' |
1689 goalabort = 'abort' |
1828 goalabort = 'abort' |
1690 goaleditplan = 'edit-plan' |
1829 goaleditplan = 'edit-plan' |
1691 goalnew = 'new' |
1830 goalnew = 'new' |
|
1831 |
1692 |
1832 |
1693 def _getgoal(opts): |
1833 def _getgoal(opts): |
1694 if opts.get(b'continue'): |
1834 if opts.get(b'continue'): |
1695 return goalcontinue |
1835 return goalcontinue |
1696 if opts.get(b'abort'): |
1836 if opts.get(b'abort'): |
1697 return goalabort |
1837 return goalabort |
1698 if opts.get(b'edit_plan'): |
1838 if opts.get(b'edit_plan'): |
1699 return goaleditplan |
1839 return goaleditplan |
1700 return goalnew |
1840 return goalnew |
1701 |
1841 |
|
1842 |
1702 def _readfile(ui, path): |
1843 def _readfile(ui, path): |
1703 if path == '-': |
1844 if path == '-': |
1704 with ui.timeblockedsection('histedit'): |
1845 with ui.timeblockedsection('histedit'): |
1705 return ui.fin.read() |
1846 return ui.fin.read() |
1706 else: |
1847 else: |
1707 with open(path, 'rb') as f: |
1848 with open(path, 'rb') as f: |
1708 return f.read() |
1849 return f.read() |
|
1850 |
1709 |
1851 |
1710 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs): |
1852 def _validateargs(ui, repo, state, freeargs, opts, goal, rules, revs): |
1711 # TODO only abort if we try to histedit mq patches, not just |
1853 # TODO only abort if we try to histedit mq patches, not just |
1712 # blanket if mq patches are applied somewhere |
1854 # blanket if mq patches are applied somewhere |
1713 mq = getattr(repo, 'mq', None) |
1855 mq = getattr(repo, 'mq', None) |
1727 elif goal == 'abort': |
1869 elif goal == 'abort': |
1728 if any((outg, revs, freeargs, rules, editplan)): |
1870 if any((outg, revs, freeargs, rules, editplan)): |
1729 raise error.Abort(_('no arguments allowed with --abort')) |
1871 raise error.Abort(_('no arguments allowed with --abort')) |
1730 elif goal == 'edit-plan': |
1872 elif goal == 'edit-plan': |
1731 if any((outg, revs, freeargs)): |
1873 if any((outg, revs, freeargs)): |
1732 raise error.Abort(_('only --commands argument allowed with ' |
1874 raise error.Abort( |
1733 '--edit-plan')) |
1875 _('only --commands argument allowed with ' '--edit-plan') |
|
1876 ) |
1734 else: |
1877 else: |
1735 if state.inprogress(): |
1878 if state.inprogress(): |
1736 raise error.Abort(_('history edit already in progress, try ' |
1879 raise error.Abort( |
1737 '--continue or --abort')) |
1880 _( |
|
1881 'history edit already in progress, try ' |
|
1882 '--continue or --abort' |
|
1883 ) |
|
1884 ) |
1738 if outg: |
1885 if outg: |
1739 if revs: |
1886 if revs: |
1740 raise error.Abort(_('no revisions allowed with --outgoing')) |
1887 raise error.Abort(_('no revisions allowed with --outgoing')) |
1741 if len(freeargs) > 1: |
1888 if len(freeargs) > 1: |
1742 raise error.Abort( |
1889 raise error.Abort( |
1743 _('only one repo argument allowed with --outgoing')) |
1890 _('only one repo argument allowed with --outgoing') |
|
1891 ) |
1744 else: |
1892 else: |
1745 revs.extend(freeargs) |
1893 revs.extend(freeargs) |
1746 if len(revs) == 0: |
1894 if len(revs) == 0: |
1747 defaultrev = destutil.desthistedit(ui, repo) |
1895 defaultrev = destutil.desthistedit(ui, repo) |
1748 if defaultrev is not None: |
1896 if defaultrev is not None: |
1749 revs.append(defaultrev) |
1897 revs.append(defaultrev) |
1750 |
1898 |
1751 if len(revs) != 1: |
1899 if len(revs) != 1: |
1752 raise error.Abort( |
1900 raise error.Abort( |
1753 _('histedit requires exactly one ancestor revision')) |
1901 _('histedit requires exactly one ancestor revision') |
|
1902 ) |
|
1903 |
1754 |
1904 |
1755 def _histedit(ui, repo, state, *freeargs, **opts): |
1905 def _histedit(ui, repo, state, *freeargs, **opts): |
1756 opts = pycompat.byteskwargs(opts) |
1906 opts = pycompat.byteskwargs(opts) |
1757 fm = ui.formatter('histedit', opts) |
1907 fm = ui.formatter('histedit', opts) |
1758 fm.startitem() |
1908 fm.startitem() |
1794 |
1948 |
1795 _continuehistedit(ui, repo, state) |
1949 _continuehistedit(ui, repo, state) |
1796 _finishhistedit(ui, repo, state, fm) |
1950 _finishhistedit(ui, repo, state, fm) |
1797 fm.end() |
1951 fm.end() |
1798 |
1952 |
|
1953 |
1799 def _continuehistedit(ui, repo, state): |
1954 def _continuehistedit(ui, repo, state): |
1800 """This function runs after either: |
1955 """This function runs after either: |
1801 - bootstrapcontinue (if the goal is 'continue') |
1956 - bootstrapcontinue (if the goal is 'continue') |
1802 - _newhistedit (if the goal is 'new') |
1957 - _newhistedit (if the goal is 'new') |
1803 """ |
1958 """ |
1804 # preprocess rules so that we can hide inner folds from the user |
1959 # preprocess rules so that we can hide inner folds from the user |
1805 # and only show one editor |
1960 # and only show one editor |
1806 actions = state.actions[:] |
1961 actions = state.actions[:] |
1807 for idx, (action, nextact) in enumerate( |
1962 for idx, (action, nextact) in enumerate(zip(actions, actions[1:] + [None])): |
1808 zip(actions, actions[1:] + [None])): |
|
1809 if action.verb == 'fold' and nextact and nextact.verb == 'fold': |
1963 if action.verb == 'fold' and nextact and nextact.verb == 'fold': |
1810 state.actions[idx].__class__ = _multifold |
1964 state.actions[idx].__class__ = _multifold |
1811 |
1965 |
1812 # Force an initial state file write, so the user can run --abort/continue |
1966 # Force an initial state file write, so the user can run --abort/continue |
1813 # even if there's an exception before the first transaction serialize. |
1967 # even if there's an exception before the first transaction serialize. |
1820 if ui.configbool("histedit", "singletransaction"): |
1974 if ui.configbool("histedit", "singletransaction"): |
1821 # Don't use a 'with' for the transaction, since actions may close |
1975 # Don't use a 'with' for the transaction, since actions may close |
1822 # and reopen a transaction. For example, if the action executes an |
1976 # and reopen a transaction. For example, if the action executes an |
1823 # external process it may choose to commit the transaction first. |
1977 # external process it may choose to commit the transaction first. |
1824 tr = repo.transaction('histedit') |
1978 tr = repo.transaction('histedit') |
1825 progress = ui.makeprogress(_("editing"), unit=_('changes'), |
1979 progress = ui.makeprogress( |
1826 total=len(state.actions)) |
1980 _("editing"), unit=_('changes'), total=len(state.actions) |
|
1981 ) |
1827 with progress, util.acceptintervention(tr): |
1982 with progress, util.acceptintervention(tr): |
1828 while state.actions: |
1983 while state.actions: |
1829 state.write(tr=tr) |
1984 state.write(tr=tr) |
1830 actobj = state.actions[0] |
1985 actobj = state.actions[0] |
1831 progress.increment(item=actobj.torule()) |
1986 progress.increment(item=actobj.torule()) |
1832 ui.debug('histedit: processing %s %s\n' % (actobj.verb, |
1987 ui.debug( |
1833 actobj.torule())) |
1988 'histedit: processing %s %s\n' % (actobj.verb, actobj.torule()) |
|
1989 ) |
1834 parentctx, replacement_ = actobj.run() |
1990 parentctx, replacement_ = actobj.run() |
1835 state.parentctxnode = parentctx.node() |
1991 state.parentctxnode = parentctx.node() |
1836 state.replacements.extend(replacement_) |
1992 state.replacements.extend(replacement_) |
1837 state.actions.pop(0) |
1993 state.actions.pop(0) |
1838 |
1994 |
1839 state.write() |
1995 state.write() |
|
1996 |
1840 |
1997 |
1841 def _finishhistedit(ui, repo, state, fm): |
1998 def _finishhistedit(ui, repo, state, fm): |
1842 """This action runs when histedit is finishing its session""" |
1999 """This action runs when histedit is finishing its session""" |
1843 hg.updaterepo(repo, state.parentctxnode, overwrite=False) |
2000 hg.updaterepo(repo, state.parentctxnode, overwrite=False) |
1844 |
2001 |
1866 if n in repo: |
2025 if n in repo: |
1867 mapping[n] = () |
2026 mapping[n] = () |
1868 |
2027 |
1869 # remove entries about unknown nodes |
2028 # remove entries about unknown nodes |
1870 nodemap = repo.unfiltered().changelog.nodemap |
2029 nodemap = repo.unfiltered().changelog.nodemap |
1871 mapping = {k: v for k, v in mapping.items() |
2030 mapping = { |
1872 if k in nodemap and all(n in nodemap for n in v)} |
2031 k: v |
|
2032 for k, v in mapping.items() |
|
2033 if k in nodemap and all(n in nodemap for n in v) |
|
2034 } |
1873 scmutil.cleanupnodes(repo, mapping, 'histedit') |
2035 scmutil.cleanupnodes(repo, mapping, 'histedit') |
1874 hf = fm.hexfunc |
2036 hf = fm.hexfunc |
1875 fl = fm.formatlist |
2037 fl = fm.formatlist |
1876 fd = fm.formatdict |
2038 fd = fm.formatdict |
1877 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node') |
2039 nodechanges = fd( |
1878 for oldn, newn in mapping.iteritems()}, |
2040 { |
1879 key="oldnode", value="newnodes") |
2041 hf(oldn): fl([hf(n) for n in newn], name='node') |
|
2042 for oldn, newn in mapping.iteritems() |
|
2043 }, |
|
2044 key="oldnode", |
|
2045 value="newnodes", |
|
2046 ) |
1880 fm.data(nodechanges=nodechanges) |
2047 fm.data(nodechanges=nodechanges) |
1881 |
2048 |
1882 state.clear() |
2049 state.clear() |
1883 if os.path.exists(repo.sjoin('undo')): |
2050 if os.path.exists(repo.sjoin('undo')): |
1884 os.unlink(repo.sjoin('undo')) |
2051 os.unlink(repo.sjoin('undo')) |
1885 if repo.vfs.exists('histedit-last-edit.txt'): |
2052 if repo.vfs.exists('histedit-last-edit.txt'): |
1886 repo.vfs.unlink('histedit-last-edit.txt') |
2053 repo.vfs.unlink('histedit-last-edit.txt') |
1887 |
2054 |
|
2055 |
1888 def _aborthistedit(ui, repo, state, nobackup=False): |
2056 def _aborthistedit(ui, repo, state, nobackup=False): |
1889 try: |
2057 try: |
1890 state.read() |
2058 state.read() |
1891 __, leafs, tmpnodes, __ = processreplacement(state) |
2059 __, leafs, tmpnodes, __ = processreplacement(state) |
1892 ui.debug('restore wc to old parent %s\n' |
2060 ui.debug('restore wc to old parent %s\n' % node.short(state.topmost)) |
1893 % node.short(state.topmost)) |
|
1894 |
2061 |
1895 # Recover our old commits if necessary |
2062 # Recover our old commits if necessary |
1896 if not state.topmost in repo and state.backupfile: |
2063 if not state.topmost in repo and state.backupfile: |
1897 backupfile = repo.vfs.join(state.backupfile) |
2064 backupfile = repo.vfs.join(state.backupfile) |
1898 f = hg.openpath(ui, backupfile) |
2065 f = hg.openpath(ui, backupfile) |
1899 gen = exchange.readbundle(ui, f, backupfile) |
2066 gen = exchange.readbundle(ui, f, backupfile) |
1900 with repo.transaction('histedit.abort') as tr: |
2067 with repo.transaction('histedit.abort') as tr: |
1901 bundle2.applybundle(repo, gen, tr, source='histedit', |
2068 bundle2.applybundle( |
1902 url='bundle:' + backupfile) |
2069 repo, gen, tr, source='histedit', url='bundle:' + backupfile |
|
2070 ) |
1903 |
2071 |
1904 os.remove(backupfile) |
2072 os.remove(backupfile) |
1905 |
2073 |
1906 # check whether we should update away |
2074 # check whether we should update away |
1907 if repo.unfiltered().revs('parents() and (%n or %ln::)', |
2075 if repo.unfiltered().revs( |
1908 state.parentctxnode, leafs | tmpnodes): |
2076 'parents() and (%n or %ln::)', |
|
2077 state.parentctxnode, |
|
2078 leafs | tmpnodes, |
|
2079 ): |
1909 hg.clean(repo, state.topmost, show_stats=True, quietempty=True) |
2080 hg.clean(repo, state.topmost, show_stats=True, quietempty=True) |
1910 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup) |
2081 cleanupnode(ui, repo, tmpnodes, nobackup=nobackup) |
1911 cleanupnode(ui, repo, leafs, nobackup=nobackup) |
2082 cleanupnode(ui, repo, leafs, nobackup=nobackup) |
1912 except Exception: |
2083 except Exception: |
1913 if state.inprogress(): |
2084 if state.inprogress(): |
1914 ui.warn(_('warning: encountered an exception during histedit ' |
2085 ui.warn( |
1915 '--abort; the repository may not have been completely ' |
2086 _( |
1916 'cleaned up\n')) |
2087 'warning: encountered an exception during histedit ' |
|
2088 '--abort; the repository may not have been completely ' |
|
2089 'cleaned up\n' |
|
2090 ) |
|
2091 ) |
1917 raise |
2092 raise |
1918 finally: |
2093 finally: |
1919 state.clear() |
2094 state.clear() |
|
2095 |
1920 |
2096 |
1921 def hgaborthistedit(ui, repo): |
2097 def hgaborthistedit(ui, repo): |
1922 state = histeditstate(repo) |
2098 state = histeditstate(repo) |
1923 nobackup = not ui.configbool('rewrite', 'backup-bundle') |
2099 nobackup = not ui.configbool('rewrite', 'backup-bundle') |
1924 with repo.wlock() as wlock, repo.lock() as lock: |
2100 with repo.wlock() as wlock, repo.lock() as lock: |
1925 state.wlock = wlock |
2101 state.wlock = wlock |
1926 state.lock = lock |
2102 state.lock = lock |
1927 _aborthistedit(ui, repo, state, nobackup=nobackup) |
2103 _aborthistedit(ui, repo, state, nobackup=nobackup) |
1928 |
2104 |
|
2105 |
1929 def _edithisteditplan(ui, repo, state, rules): |
2106 def _edithisteditplan(ui, repo, state, rules): |
1930 state.read() |
2107 state.read() |
1931 if not rules: |
2108 if not rules: |
1932 comment = geteditcomment(ui, |
2109 comment = geteditcomment( |
1933 node.short(state.parentctxnode), |
2110 ui, node.short(state.parentctxnode), node.short(state.topmost) |
1934 node.short(state.topmost)) |
2111 ) |
1935 rules = ruleeditor(repo, ui, state.actions, comment) |
2112 rules = ruleeditor(repo, ui, state.actions, comment) |
1936 else: |
2113 else: |
1937 rules = _readfile(ui, rules) |
2114 rules = _readfile(ui, rules) |
1938 actions = parserules(rules, state) |
2115 actions = parserules(rules, state) |
1939 ctxs = [repo[act.node] |
2116 ctxs = [repo[act.node] for act in state.actions if act.node] |
1940 for act in state.actions if act.node] |
|
1941 warnverifyactions(ui, repo, actions, state, ctxs) |
2117 warnverifyactions(ui, repo, actions, state, ctxs) |
1942 state.actions = actions |
2118 state.actions = actions |
1943 state.write() |
2119 state.write() |
|
2120 |
1944 |
2121 |
1945 def _newhistedit(ui, repo, state, revs, freeargs, opts): |
2122 def _newhistedit(ui, repo, state, revs, freeargs, opts): |
1946 outg = opts.get('outgoing') |
2123 outg = opts.get('outgoing') |
1947 rules = opts.get('commands', '') |
2124 rules = opts.get('commands', '') |
1948 force = opts.get('force') |
2125 force = opts.get('force') |
1981 # collision after we've started histedit and backing out gets ugly |
2163 # collision after we've started histedit and backing out gets ugly |
1982 # for everyone, especially the user. |
2164 # for everyone, especially the user. |
1983 for c in [ctxs[0].p1()] + ctxs: |
2165 for c in [ctxs[0].p1()] + ctxs: |
1984 try: |
2166 try: |
1985 mergemod.calculateupdates( |
2167 mergemod.calculateupdates( |
1986 repo, wctx, c, ancs, |
2168 repo, |
|
2169 wctx, |
|
2170 c, |
|
2171 ancs, |
1987 # These parameters were determined by print-debugging |
2172 # These parameters were determined by print-debugging |
1988 # what happens later on inside histedit. |
2173 # what happens later on inside histedit. |
1989 branchmerge=False, force=False, acceptremote=False, |
2174 branchmerge=False, |
1990 followcopies=False) |
2175 force=False, |
|
2176 acceptremote=False, |
|
2177 followcopies=False, |
|
2178 ) |
1991 except error.Abort: |
2179 except error.Abort: |
1992 raise error.Abort( |
2180 raise error.Abort( |
1993 _("untracked files in working directory conflict with files in %s") % ( |
2181 _( |
1994 c)) |
2182 "untracked files in working directory conflict with files in %s" |
|
2183 ) |
|
2184 % c |
|
2185 ) |
1995 |
2186 |
1996 if not rules: |
2187 if not rules: |
1997 comment = geteditcomment(ui, node.short(root), node.short(topmost)) |
2188 comment = geteditcomment(ui, node.short(root), node.short(topmost)) |
1998 actions = [pick(state, r) for r in revs] |
2189 actions = [pick(state, r) for r in revs] |
1999 rules = ruleeditor(repo, ui, actions, comment) |
2190 rules = ruleeditor(repo, ui, actions, comment) |
2007 state.parentctxnode = parentctxnode |
2198 state.parentctxnode = parentctxnode |
2008 state.actions = actions |
2199 state.actions = actions |
2009 state.topmost = topmost |
2200 state.topmost = topmost |
2010 state.replacements = [] |
2201 state.replacements = [] |
2011 |
2202 |
2012 ui.log("histedit", "%d actions to histedit\n", len(actions), |
2203 ui.log( |
2013 histedit_num_actions=len(actions)) |
2204 "histedit", |
|
2205 "%d actions to histedit\n", |
|
2206 len(actions), |
|
2207 histedit_num_actions=len(actions), |
|
2208 ) |
2014 |
2209 |
2015 # Create a backup so we can always abort completely. |
2210 # Create a backup so we can always abort completely. |
2016 backupfile = None |
2211 backupfile = None |
2017 if not obsolete.isenabled(repo, obsolete.createmarkersopt): |
2212 if not obsolete.isenabled(repo, obsolete.createmarkersopt): |
2018 backupfile = repair.backupbundle(repo, [parentctxnode], |
2213 backupfile = repair.backupbundle( |
2019 [topmost], root, 'histedit') |
2214 repo, [parentctxnode], [topmost], root, 'histedit' |
|
2215 ) |
2020 state.backupfile = backupfile |
2216 state.backupfile = backupfile |
|
2217 |
2021 |
2218 |
2022 def _getsummary(ctx): |
2219 def _getsummary(ctx): |
2023 # a common pattern is to extract the summary but default to the empty |
2220 # a common pattern is to extract the summary but default to the empty |
2024 # string |
2221 # string |
2025 summary = ctx.description() or '' |
2222 summary = ctx.description() or '' |
2026 if summary: |
2223 if summary: |
2027 summary = summary.splitlines()[0] |
2224 summary = summary.splitlines()[0] |
2028 return summary |
2225 return summary |
2029 |
2226 |
|
2227 |
2030 def bootstrapcontinue(ui, state, opts): |
2228 def bootstrapcontinue(ui, state, opts): |
2031 repo = state.repo |
2229 repo = state.repo |
2032 |
2230 |
2033 ms = mergemod.mergestate.read(repo) |
2231 ms = mergemod.mergestate.read(repo) |
2034 mergeutil.checkunresolved(ms) |
2232 mergeutil.checkunresolved(ms) |
2046 state.parentctxnode = parentctx.node() |
2244 state.parentctxnode = parentctx.node() |
2047 state.replacements.extend(replacements) |
2245 state.replacements.extend(replacements) |
2048 |
2246 |
2049 return state |
2247 return state |
2050 |
2248 |
|
2249 |
2051 def between(repo, old, new, keep): |
2250 def between(repo, old, new, keep): |
2052 """select and validate the set of revision to edit |
2251 """select and validate the set of revision to edit |
2053 |
2252 |
2054 When keep is false, the specified set can't have children.""" |
2253 When keep is false, the specified set can't have children.""" |
2055 revs = repo.revs('%n::%n', old, new) |
2254 revs = repo.revs('%n::%n', old, new) |
2056 if revs and not keep: |
2255 if revs and not keep: |
2057 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and |
2256 if not obsolete.isenabled( |
2058 repo.revs('(%ld::) - (%ld)', revs, revs)): |
2257 repo, obsolete.allowunstableopt |
2059 raise error.Abort(_('can only histedit a changeset together ' |
2258 ) and repo.revs('(%ld::) - (%ld)', revs, revs): |
2060 'with all its descendants')) |
2259 raise error.Abort( |
|
2260 _( |
|
2261 'can only histedit a changeset together ' |
|
2262 'with all its descendants' |
|
2263 ) |
|
2264 ) |
2061 if repo.revs('(%ld) and merge()', revs): |
2265 if repo.revs('(%ld) and merge()', revs): |
2062 raise error.Abort(_('cannot edit history that contains merges')) |
2266 raise error.Abort(_('cannot edit history that contains merges')) |
2063 root = repo[revs.first()] # list is already sorted by repo.revs() |
2267 root = repo[revs.first()] # list is already sorted by repo.revs() |
2064 if not root.mutable(): |
2268 if not root.mutable(): |
2065 raise error.Abort(_('cannot edit public changeset: %s') % root, |
2269 raise error.Abort( |
2066 hint=_("see 'hg help phases' for details")) |
2270 _('cannot edit public changeset: %s') % root, |
|
2271 hint=_("see 'hg help phases' for details"), |
|
2272 ) |
2067 return pycompat.maplist(repo.changelog.node, revs) |
2273 return pycompat.maplist(repo.changelog.node, revs) |
|
2274 |
2068 |
2275 |
2069 def ruleeditor(repo, ui, actions, editcomment=""): |
2276 def ruleeditor(repo, ui, actions, editcomment=""): |
2070 """open an editor to edit rules |
2277 """open an editor to edit rules |
2071 |
2278 |
2072 rules are in the format [ [act, ctx], ...] like in state.rules |
2279 rules are in the format [ [act, ctx], ...] like in state.rules |
2106 actions += l |
2313 actions += l |
2107 |
2314 |
2108 rules = '\n'.join([act.torule() for act in actions]) |
2315 rules = '\n'.join([act.torule() for act in actions]) |
2109 rules += '\n\n' |
2316 rules += '\n\n' |
2110 rules += editcomment |
2317 rules += editcomment |
2111 rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'}, |
2318 rules = ui.edit( |
2112 repopath=repo.path, action='histedit') |
2319 rules, |
|
2320 ui.username(), |
|
2321 {'prefix': 'histedit'}, |
|
2322 repopath=repo.path, |
|
2323 action='histedit', |
|
2324 ) |
2113 |
2325 |
2114 # Save edit rules in .hg/histedit-last-edit.txt in case |
2326 # Save edit rules in .hg/histedit-last-edit.txt in case |
2115 # the user needs to ask for help after something |
2327 # the user needs to ask for help after something |
2116 # surprising happens. |
2328 # surprising happens. |
2117 with repo.vfs('histedit-last-edit.txt', 'wb') as f: |
2329 with repo.vfs('histedit-last-edit.txt', 'wb') as f: |
2118 f.write(rules) |
2330 f.write(rules) |
2119 |
2331 |
2120 return rules |
2332 return rules |
2121 |
2333 |
|
2334 |
2122 def parserules(rules, state): |
2335 def parserules(rules, state): |
2123 """Read the histedit rules string and return list of action objects """ |
2336 """Read the histedit rules string and return list of action objects """ |
2124 rules = [l for l in (r.strip() for r in rules.splitlines()) |
2337 rules = [ |
2125 if l and not l.startswith('#')] |
2338 l |
|
2339 for l in (r.strip() for r in rules.splitlines()) |
|
2340 if l and not l.startswith('#') |
|
2341 ] |
2126 actions = [] |
2342 actions = [] |
2127 for r in rules: |
2343 for r in rules: |
2128 if ' ' not in r: |
2344 if ' ' not in r: |
2129 raise error.ParseError(_('malformed line "%s"') % r) |
2345 raise error.ParseError(_('malformed line "%s"') % r) |
2130 verb, rest = r.split(' ', 1) |
2346 verb, rest = r.split(' ', 1) |
2155 expected = set(c.node() for c in ctxs) |
2377 expected = set(c.node() for c in ctxs) |
2156 seen = set() |
2378 seen = set() |
2157 prev = None |
2379 prev = None |
2158 |
2380 |
2159 if actions and actions[0].verb in ['roll', 'fold']: |
2381 if actions and actions[0].verb in ['roll', 'fold']: |
2160 raise error.ParseError(_('first changeset cannot use verb "%s"') % |
2382 raise error.ParseError( |
2161 actions[0].verb) |
2383 _('first changeset cannot use verb "%s"') % actions[0].verb |
|
2384 ) |
2162 |
2385 |
2163 for action in actions: |
2386 for action in actions: |
2164 action.verify(prev, expected, seen) |
2387 action.verify(prev, expected, seen) |
2165 prev = action |
2388 prev = action |
2166 if action.node is not None: |
2389 if action.node is not None: |
2167 seen.add(action.node) |
2390 seen.add(action.node) |
2168 missing = sorted(expected - seen) # sort to stabilize output |
2391 missing = sorted(expected - seen) # sort to stabilize output |
2169 |
2392 |
2170 if state.repo.ui.configbool('histedit', 'dropmissing'): |
2393 if state.repo.ui.configbool('histedit', 'dropmissing'): |
2171 if len(actions) == 0: |
2394 if len(actions) == 0: |
2172 raise error.ParseError(_('no rules provided'), |
2395 raise error.ParseError( |
2173 hint=_('use strip extension to remove commits')) |
2396 _('no rules provided'), |
|
2397 hint=_('use strip extension to remove commits'), |
|
2398 ) |
2174 |
2399 |
2175 drops = [drop(state, n) for n in missing] |
2400 drops = [drop(state, n) for n in missing] |
2176 # put the in the beginning so they execute immediately and |
2401 # put the in the beginning so they execute immediately and |
2177 # don't show in the edit-plan in the future |
2402 # don't show in the edit-plan in the future |
2178 actions[:0] = drops |
2403 actions[:0] = drops |
2179 elif missing: |
2404 elif missing: |
2180 raise error.ParseError(_('missing rules for changeset %s') % |
2405 raise error.ParseError( |
2181 node.short(missing[0]), |
2406 _('missing rules for changeset %s') % node.short(missing[0]), |
2182 hint=_('use "drop %s" to discard, see also: ' |
2407 hint=_( |
2183 "'hg help -e histedit.config'") |
2408 'use "drop %s" to discard, see also: ' |
2184 % node.short(missing[0])) |
2409 "'hg help -e histedit.config'" |
|
2410 ) |
|
2411 % node.short(missing[0]), |
|
2412 ) |
|
2413 |
2185 |
2414 |
2186 def adjustreplacementsfrommarkers(repo, oldreplacements): |
2415 def adjustreplacementsfrommarkers(repo, oldreplacements): |
2187 """Adjust replacements from obsolescence markers |
2416 """Adjust replacements from obsolescence markers |
2188 |
2417 |
2189 Replacements structure is originally generated based on |
2418 Replacements structure is originally generated based on |
2312 roots = [c.node() for c in repo.set("roots(%ln)", nodes)] |
2546 roots = [c.node() for c in repo.set("roots(%ln)", nodes)] |
2313 if roots: |
2547 if roots: |
2314 backup = not nobackup |
2548 backup = not nobackup |
2315 repair.strip(ui, repo, roots, backup=backup) |
2549 repair.strip(ui, repo, roots, backup=backup) |
2316 |
2550 |
|
2551 |
2317 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs): |
2552 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs): |
2318 if isinstance(nodelist, str): |
2553 if isinstance(nodelist, str): |
2319 nodelist = [nodelist] |
2554 nodelist = [nodelist] |
2320 state = histeditstate(repo) |
2555 state = histeditstate(repo) |
2321 if state.inprogress(): |
2556 if state.inprogress(): |
2322 state.read() |
2557 state.read() |
2323 histedit_nodes = {action.node for action |
2558 histedit_nodes = { |
2324 in state.actions if action.node} |
2559 action.node for action in state.actions if action.node |
|
2560 } |
2325 common_nodes = histedit_nodes & set(nodelist) |
2561 common_nodes = histedit_nodes & set(nodelist) |
2326 if common_nodes: |
2562 if common_nodes: |
2327 raise error.Abort(_("histedit in progress, can't strip %s") |
2563 raise error.Abort( |
2328 % ', '.join(node.short(x) for x in common_nodes)) |
2564 _("histedit in progress, can't strip %s") |
|
2565 % ', '.join(node.short(x) for x in common_nodes) |
|
2566 ) |
2329 return orig(ui, repo, nodelist, *args, **kwargs) |
2567 return orig(ui, repo, nodelist, *args, **kwargs) |
2330 |
2568 |
|
2569 |
2331 extensions.wrapfunction(repair, 'strip', stripwrapper) |
2570 extensions.wrapfunction(repair, 'strip', stripwrapper) |
|
2571 |
2332 |
2572 |
2333 def summaryhook(ui, repo): |
2573 def summaryhook(ui, repo): |
2334 state = histeditstate(repo) |
2574 state = histeditstate(repo) |
2335 if not state.inprogress(): |
2575 if not state.inprogress(): |
2336 return |
2576 return |
2337 state.read() |
2577 state.read() |
2338 if state.actions: |
2578 if state.actions: |
2339 # i18n: column positioning for "hg summary" |
2579 # i18n: column positioning for "hg summary" |
2340 ui.write(_('hist: %s (histedit --continue)\n') % |
2580 ui.write( |
2341 (ui.label(_('%d remaining'), 'histedit.remaining') % |
2581 _('hist: %s (histedit --continue)\n') |
2342 len(state.actions))) |
2582 % ( |
|
2583 ui.label(_('%d remaining'), 'histedit.remaining') |
|
2584 % len(state.actions) |
|
2585 ) |
|
2586 ) |
|
2587 |
2343 |
2588 |
2344 def extsetup(ui): |
2589 def extsetup(ui): |
2345 cmdutil.summaryhooks.add('histedit', summaryhook) |
2590 cmdutil.summaryhooks.add('histedit', summaryhook) |
2346 statemod.addunfinished('histedit', fname='histedit-state', allowcommit=True, |
2591 statemod.addunfinished( |
2347 continueflag=True, abortfunc=hgaborthistedit) |
2592 'histedit', |
|
2593 fname='histedit-state', |
|
2594 allowcommit=True, |
|
2595 continueflag=True, |
|
2596 abortfunc=hgaborthistedit, |
|
2597 ) |