402 with self.repo.vfs(b"histedit-state", b"w") as f: |
406 with self.repo.vfs(b"histedit-state", b"w") as f: |
403 self._write(f) |
407 self._write(f) |
404 |
408 |
405 def _write(self, fp): |
409 def _write(self, fp): |
406 fp.write(b'v1\n') |
410 fp.write(b'v1\n') |
407 fp.write(b'%s\n' % node.hex(self.parentctxnode)) |
411 fp.write(b'%s\n' % hex(self.parentctxnode)) |
408 fp.write(b'%s\n' % node.hex(self.topmost)) |
412 fp.write(b'%s\n' % hex(self.topmost)) |
409 fp.write(b'%s\n' % (b'True' if self.keep else b'False')) |
413 fp.write(b'%s\n' % (b'True' if self.keep else b'False')) |
410 fp.write(b'%d\n' % len(self.actions)) |
414 fp.write(b'%d\n' % len(self.actions)) |
411 for action in self.actions: |
415 for action in self.actions: |
412 fp.write(b'%s\n' % action.tostate()) |
416 fp.write(b'%s\n' % action.tostate()) |
413 fp.write(b'%d\n' % len(self.replacements)) |
417 fp.write(b'%d\n' % len(self.replacements)) |
414 for replacement in self.replacements: |
418 for replacement in self.replacements: |
415 fp.write( |
419 fp.write( |
416 b'%s%s\n' |
420 b'%s%s\n' |
417 % ( |
421 % ( |
418 node.hex(replacement[0]), |
422 hex(replacement[0]), |
419 b''.join(node.hex(r) for r in replacement[1]), |
423 b''.join(hex(r) for r in replacement[1]), |
420 ) |
424 ) |
421 ) |
425 ) |
422 backupfile = self.backupfile |
426 backupfile = self.backupfile |
423 if not backupfile: |
427 if not backupfile: |
424 backupfile = b'' |
428 backupfile = b'' |
456 replacements = [] |
460 replacements = [] |
457 replacementlen = int(lines[index]) |
461 replacementlen = int(lines[index]) |
458 index += 1 |
462 index += 1 |
459 for i in pycompat.xrange(replacementlen): |
463 for i in pycompat.xrange(replacementlen): |
460 replacement = lines[index] |
464 replacement = lines[index] |
461 original = node.bin(replacement[:40]) |
465 original = bin(replacement[:40]) |
462 succ = [ |
466 succ = [ |
463 node.bin(replacement[i : i + 40]) |
467 bin(replacement[i : i + 40]) |
464 for i in range(40, len(replacement), 40) |
468 for i in range(40, len(replacement), 40) |
465 ] |
469 ] |
466 replacements.append((original, succ)) |
470 replacements.append((original, succ)) |
467 index += 1 |
471 index += 1 |
468 |
472 |
492 """Parses the given rule, returning an instance of the histeditaction.""" |
496 """Parses the given rule, returning an instance of the histeditaction.""" |
493 ruleid = rule.strip().split(b' ', 1)[0] |
497 ruleid = rule.strip().split(b' ', 1)[0] |
494 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc |
498 # ruleid can be anything from rev numbers, hashes, "bookmarks" etc |
495 # Check for validation of rule ids and get the rulehash |
499 # Check for validation of rule ids and get the rulehash |
496 try: |
500 try: |
497 rev = node.bin(ruleid) |
501 rev = bin(ruleid) |
498 except TypeError: |
502 except TypeError: |
499 try: |
503 try: |
500 _ctx = scmutil.revsingle(state.repo, ruleid) |
504 _ctx = scmutil.revsingle(state.repo, ruleid) |
501 rulehash = _ctx.hex() |
505 rulehash = _ctx.hex() |
502 rev = node.bin(rulehash) |
506 rev = bin(rulehash) |
503 except error.RepoLookupError: |
507 except error.RepoLookupError: |
504 raise error.ParseError(_(b"invalid changeset %s") % ruleid) |
508 raise error.ParseError(_(b"invalid changeset %s") % ruleid) |
505 return cls(state, rev) |
509 return cls(state, rev) |
506 |
510 |
507 def verify(self, prev, expected, seen): |
511 def verify(self, prev, expected, seen): |
508 """ Verifies semantic correctness of the rule""" |
512 """ Verifies semantic correctness of the rule""" |
509 repo = self.repo |
513 repo = self.repo |
510 ha = node.hex(self.node) |
514 ha = hex(self.node) |
511 self.node = scmutil.resolvehexnodeidprefix(repo, ha) |
515 self.node = scmutil.resolvehexnodeidprefix(repo, ha) |
512 if self.node is None: |
516 if self.node is None: |
513 raise error.ParseError(_(b'unknown changeset %s listed') % ha[:12]) |
517 raise error.ParseError(_(b'unknown changeset %s listed') % ha[:12]) |
514 self._verifynodeconstraints(prev, expected, seen) |
518 self._verifynodeconstraints(prev, expected, seen) |
515 |
519 |
516 def _verifynodeconstraints(self, prev, expected, seen): |
520 def _verifynodeconstraints(self, prev, expected, seen): |
517 # by default command need a node in the edited list |
521 # by default command need a node in the edited list |
518 if self.node not in expected: |
522 if self.node not in expected: |
519 raise error.ParseError( |
523 raise error.ParseError( |
520 _(b'%s "%s" changeset was not a candidate') |
524 _(b'%s "%s" changeset was not a candidate') |
521 % (self.verb, node.short(self.node)), |
525 % (self.verb, short(self.node)), |
522 hint=_(b'only use listed changesets'), |
526 hint=_(b'only use listed changesets'), |
523 ) |
527 ) |
524 # and only one command per node |
528 # and only one command per node |
525 if self.node in seen: |
529 if self.node in seen: |
526 raise error.ParseError( |
530 raise error.ParseError( |
527 _(b'duplicated command for changeset %s') |
531 _(b'duplicated command for changeset %s') % short(self.node) |
528 % node.short(self.node) |
|
529 ) |
532 ) |
530 |
533 |
531 def torule(self): |
534 def torule(self): |
532 """build a histedit rule line for an action |
535 """build a histedit rule line for an action |
533 |
536 |
576 repo.ui.popbuffer() |
579 repo.ui.popbuffer() |
577 stats = applychanges(repo.ui, repo, rulectx, {}) |
580 stats = applychanges(repo.ui, repo, rulectx, {}) |
578 repo.dirstate.setbranch(rulectx.branch()) |
581 repo.dirstate.setbranch(rulectx.branch()) |
579 if stats.unresolvedcount: |
582 if stats.unresolvedcount: |
580 raise error.InterventionRequired( |
583 raise error.InterventionRequired( |
581 _(b'Fix up the change (%s %s)') |
584 _(b'Fix up the change (%s %s)') % (self.verb, short(self.node)), |
582 % (self.verb, node.short(self.node)), |
|
583 hint=_(b'hg histedit --continue to resume'), |
585 hint=_(b'hg histedit --continue to resume'), |
584 ) |
586 ) |
585 |
587 |
586 def continuedirty(self): |
588 def continuedirty(self): |
587 """Continues the action when changes have been applied to the working |
589 """Continues the action when changes have been applied to the working |
784 @action([b'pick', b'p'], _(b'use commit'), priority=True) |
785 @action([b'pick', b'p'], _(b'use commit'), priority=True) |
785 class pick(histeditaction): |
786 class pick(histeditaction): |
786 def run(self): |
787 def run(self): |
787 rulectx = self.repo[self.node] |
788 rulectx = self.repo[self.node] |
788 if rulectx.p1().node() == self.state.parentctxnode: |
789 if rulectx.p1().node() == self.state.parentctxnode: |
789 self.repo.ui.debug(b'node %s unchanged\n' % node.short(self.node)) |
790 self.repo.ui.debug(b'node %s unchanged\n' % short(self.node)) |
790 return rulectx, [] |
791 return rulectx, [] |
791 |
792 |
792 return super(pick, self).run() |
793 return super(pick, self).run() |
793 |
794 |
794 |
795 |
795 @action([b'edit', b'e'], _(b'use commit, but allow edits before making new commit'), priority=True) |
796 @action( |
|
797 [b'edit', b'e'], |
|
798 _(b'use commit, but allow edits before making new commit'), |
|
799 priority=True, |
|
800 ) |
796 class edit(histeditaction): |
801 class edit(histeditaction): |
797 def run(self): |
802 def run(self): |
798 repo = self.repo |
803 repo = self.repo |
799 rulectx = repo[self.node] |
804 rulectx = repo[self.node] |
800 hg.update(repo, self.state.parentctxnode, quietempty=True) |
805 hg.update(repo, self.state.parentctxnode, quietempty=True) |
801 applychanges(repo.ui, repo, rulectx, {}) |
806 applychanges(repo.ui, repo, rulectx, {}) |
802 hint = _(b'to edit %s, `hg histedit --continue` after making changes') |
807 hint = _(b'to edit %s, `hg histedit --continue` after making changes') |
803 raise error.InterventionRequired( |
808 raise error.InterventionRequired( |
804 _(b'Editing (%s), commit as needed now to split the change') |
809 _(b'Editing (%s), commit as needed now to split the change') |
805 % node.short(self.node), |
810 % short(self.node), |
806 hint=hint % node.short(self.node), |
811 hint=hint % short(self.node), |
807 ) |
812 ) |
808 |
813 |
809 def commiteditor(self): |
814 def commiteditor(self): |
810 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.edit') |
815 return cmdutil.getcommiteditor(edit=True, editform=b'histedit.edit') |
811 |
816 |
822 return |
827 return |
823 else: |
828 else: |
824 c = repo[prev.node] |
829 c = repo[prev.node] |
825 if not c.mutable(): |
830 if not c.mutable(): |
826 raise error.ParseError( |
831 raise error.ParseError( |
827 _(b"cannot fold into public change %s") % node.short(c.node()) |
832 _(b"cannot fold into public change %s") % short(c.node()) |
828 ) |
833 ) |
829 |
834 |
830 def continuedirty(self): |
835 def continuedirty(self): |
831 repo = self.repo |
836 repo = self.repo |
832 rulectx = repo[self.node] |
837 rulectx = repo[self.node] |
833 |
838 |
834 commit = commitfuncfor(repo, rulectx) |
839 commit = commitfuncfor(repo, rulectx) |
835 commit( |
840 commit( |
836 text=b'fold-temp-revision %s' % node.short(self.node), |
841 text=b'fold-temp-revision %s' % short(self.node), |
837 user=rulectx.user(), |
842 user=rulectx.user(), |
838 date=rulectx.date(), |
843 date=rulectx.date(), |
839 extra=rulectx.extra(), |
844 extra=rulectx.extra(), |
840 ) |
845 ) |
841 |
846 |
857 repo.ui.warn( |
862 repo.ui.warn( |
858 _( |
863 _( |
859 b'%s: cannot fold - working copy is not a ' |
864 b'%s: cannot fold - working copy is not a ' |
860 b'descendant of previous commit %s\n' |
865 b'descendant of previous commit %s\n' |
861 ) |
866 ) |
862 % (node.short(self.node), node.short(parentctxnode)) |
867 % (short(self.node), short(parentctxnode)) |
863 ) |
868 ) |
864 return ctx, [(self.node, (ctx.node(),))] |
869 return ctx, [(self.node, (ctx.node(),))] |
865 |
870 |
866 middlecommits = newcommits.copy() |
871 middlecommits = newcommits.copy() |
867 middlecommits.discard(ctx.node()) |
872 middlecommits.discard(ctx.node()) |
971 def _verifynodeconstraints(self, prev, expected, seen): |
976 def _verifynodeconstraints(self, prev, expected, seen): |
972 # base can only be use with a node not in the edited set |
977 # base can only be use with a node not in the edited set |
973 if self.node in expected: |
978 if self.node in expected: |
974 msg = _(b'%s "%s" changeset was an edited list candidate') |
979 msg = _(b'%s "%s" changeset was an edited list candidate') |
975 raise error.ParseError( |
980 raise error.ParseError( |
976 msg % (self.verb, node.short(self.node)), |
981 msg % (self.verb, short(self.node)), |
977 hint=_(b'base must only use unlisted changesets'), |
982 hint=_(b'base must only use unlisted changesets'), |
978 ) |
983 ) |
979 |
984 |
980 |
985 |
981 @action( |
986 @action( |
2069 |
2073 |
2070 mapping, tmpnodes, created, ntm = processreplacement(state) |
2074 mapping, tmpnodes, created, ntm = processreplacement(state) |
2071 if mapping: |
2075 if mapping: |
2072 for prec, succs in pycompat.iteritems(mapping): |
2076 for prec, succs in pycompat.iteritems(mapping): |
2073 if not succs: |
2077 if not succs: |
2074 ui.debug(b'histedit: %s is dropped\n' % node.short(prec)) |
2078 ui.debug(b'histedit: %s is dropped\n' % short(prec)) |
2075 else: |
2079 else: |
2076 ui.debug( |
2080 ui.debug( |
2077 b'histedit: %s is replaced by %s\n' |
2081 b'histedit: %s is replaced by %s\n' |
2078 % (node.short(prec), node.short(succs[0])) |
2082 % (short(prec), short(succs[0])) |
2079 ) |
2083 ) |
2080 if len(succs) > 1: |
2084 if len(succs) > 1: |
2081 m = b'histedit: %s' |
2085 m = b'histedit: %s' |
2082 for n in succs[1:]: |
2086 for n in succs[1:]: |
2083 ui.debug(m % node.short(n)) |
2087 ui.debug(m % short(n)) |
2084 |
2088 |
2085 if not state.keep: |
2089 if not state.keep: |
2086 if mapping: |
2090 if mapping: |
2087 movetopmostbookmarks(repo, state.topmost, ntm) |
2091 movetopmostbookmarks(repo, state.topmost, ntm) |
2088 # TODO update mq state |
2092 # TODO update mq state |
2123 |
2127 |
2124 def _aborthistedit(ui, repo, state, nobackup=False): |
2128 def _aborthistedit(ui, repo, state, nobackup=False): |
2125 try: |
2129 try: |
2126 state.read() |
2130 state.read() |
2127 __, leafs, tmpnodes, __ = processreplacement(state) |
2131 __, leafs, tmpnodes, __ = processreplacement(state) |
2128 ui.debug(b'restore wc to old parent %s\n' % node.short(state.topmost)) |
2132 ui.debug(b'restore wc to old parent %s\n' % short(state.topmost)) |
2129 |
2133 |
2130 # Recover our old commits if necessary |
2134 # Recover our old commits if necessary |
2131 if not state.topmost in repo and state.backupfile: |
2135 if not state.topmost in repo and state.backupfile: |
2132 backupfile = repo.vfs.join(state.backupfile) |
2136 backupfile = repo.vfs.join(state.backupfile) |
2133 f = hg.openpath(ui, backupfile) |
2137 f = hg.openpath(ui, backupfile) |
2177 |
2181 |
2178 def _edithisteditplan(ui, repo, state, rules): |
2182 def _edithisteditplan(ui, repo, state, rules): |
2179 state.read() |
2183 state.read() |
2180 if not rules: |
2184 if not rules: |
2181 comment = geteditcomment( |
2185 comment = geteditcomment( |
2182 ui, node.short(state.parentctxnode), node.short(state.topmost) |
2186 ui, short(state.parentctxnode), short(state.topmost) |
2183 ) |
2187 ) |
2184 rules = ruleeditor(repo, ui, state.actions, comment) |
2188 rules = ruleeditor(repo, ui, state.actions, comment) |
2185 else: |
2189 else: |
2186 rules = _readfile(ui, rules) |
2190 rules = _readfile(ui, rules) |
2187 actions = parserules(rules, state) |
2191 actions = parserules(rules, state) |
2255 ) |
2259 ) |
2256 % c |
2260 % c |
2257 ) |
2261 ) |
2258 |
2262 |
2259 if not rules: |
2263 if not rules: |
2260 comment = geteditcomment(ui, node.short(root), node.short(topmost)) |
2264 comment = geteditcomment(ui, short(root), short(topmost)) |
2261 actions = [pick(state, r) for r in revs] |
2265 actions = [pick(state, r) for r in revs] |
2262 rules = ruleeditor(repo, ui, actions, comment) |
2266 rules = ruleeditor(repo, ui, actions, comment) |
2263 else: |
2267 else: |
2264 rules = _readfile(ui, rules) |
2268 rules = _readfile(ui, rules) |
2265 actions = parserules(rules, state) |
2269 actions = parserules(rules, state) |
2459 # put the in the beginning so they execute immediately and |
2463 # put the in the beginning so they execute immediately and |
2460 # don't show in the edit-plan in the future |
2464 # don't show in the edit-plan in the future |
2461 actions[:0] = drops |
2465 actions[:0] = drops |
2462 elif missing: |
2466 elif missing: |
2463 raise error.ParseError( |
2467 raise error.ParseError( |
2464 _(b'missing rules for changeset %s') % node.short(missing[0]), |
2468 _(b'missing rules for changeset %s') % short(missing[0]), |
2465 hint=_( |
2469 hint=_( |
2466 b'use "drop %s" to discard, see also: ' |
2470 b'use "drop %s" to discard, see also: ' |
2467 b"'hg help -e histedit.config'" |
2471 b"'hg help -e histedit.config'" |
2468 ) |
2472 ) |
2469 % node.short(missing[0]), |
2473 % short(missing[0]), |
2470 ) |
2474 ) |
2471 |
2475 |
2472 |
2476 |
2473 def adjustreplacementsfrommarkers(repo, oldreplacements): |
2477 def adjustreplacementsfrommarkers(repo, oldreplacements): |
2474 """Adjust replacements from obsolescence markers |
2478 """Adjust replacements from obsolescence markers |
2618 } |
2622 } |
2619 common_nodes = histedit_nodes & set(nodelist) |
2623 common_nodes = histedit_nodes & set(nodelist) |
2620 if common_nodes: |
2624 if common_nodes: |
2621 raise error.Abort( |
2625 raise error.Abort( |
2622 _(b"histedit in progress, can't strip %s") |
2626 _(b"histedit in progress, can't strip %s") |
2623 % b', '.join(node.short(x) for x in common_nodes) |
2627 % b', '.join(short(x) for x in common_nodes) |
2624 ) |
2628 ) |
2625 return orig(ui, repo, nodelist, *args, **kwargs) |
2629 return orig(ui, repo, nodelist, *args, **kwargs) |
2626 |
2630 |
2627 |
2631 |
2628 extensions.wrapfunction(repair, b'strip', stripwrapper) |
2632 extensions.wrapfunction(repair, b'strip', stripwrapper) |