223 try: |
223 try: |
224 fp = self.repo.vfs('histedit-state', 'r') |
224 fp = self.repo.vfs('histedit-state', 'r') |
225 except IOError as err: |
225 except IOError as err: |
226 if err.errno != errno.ENOENT: |
226 if err.errno != errno.ENOENT: |
227 raise |
227 raise |
228 raise util.Abort(_('no histedit in progress')) |
228 raise error.Abort(_('no histedit in progress')) |
229 |
229 |
230 try: |
230 try: |
231 data = pickle.load(fp) |
231 data = pickle.load(fp) |
232 parentctxnode, rules, keep, topmost, replacements = data |
232 parentctxnode, rules, keep, topmost, replacements = data |
233 backupfile = None |
233 backupfile = None |
329 repo = state.repo |
329 repo = state.repo |
330 rulehash = rule.strip().split(' ', 1)[0] |
330 rulehash = rule.strip().split(' ', 1)[0] |
331 try: |
331 try: |
332 node = repo[rulehash].node() |
332 node = repo[rulehash].node() |
333 except error.RepoError: |
333 except error.RepoError: |
334 raise util.Abort(_('unknown changeset %s listed') % rulehash[:12]) |
334 raise error.Abort(_('unknown changeset %s listed') % rulehash[:12]) |
335 return cls(state, node) |
335 return cls(state, node) |
336 |
336 |
337 def run(self): |
337 def run(self): |
338 """Runs the action. The default behavior is simply apply the action's |
338 """Runs the action. The default behavior is simply apply the action's |
339 rulectx onto the current parentctx.""" |
339 rulectx onto the current parentctx.""" |
662 if revs: |
662 if revs: |
663 revs = [repo.lookup(rev) for rev in revs] |
663 revs = [repo.lookup(rev) for rev in revs] |
664 |
664 |
665 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force) |
665 outgoing = discovery.findcommonoutgoing(repo, other, revs, force=force) |
666 if not outgoing.missing: |
666 if not outgoing.missing: |
667 raise util.Abort(_('no outgoing ancestors')) |
667 raise error.Abort(_('no outgoing ancestors')) |
668 roots = list(repo.revs("roots(%ln)", outgoing.missing)) |
668 roots = list(repo.revs("roots(%ln)", outgoing.missing)) |
669 if 1 < len(roots): |
669 if 1 < len(roots): |
670 msg = _('there are ambiguous outgoing revisions') |
670 msg = _('there are ambiguous outgoing revisions') |
671 hint = _('see "hg help histedit" for more detail') |
671 hint = _('see "hg help histedit" for more detail') |
672 raise util.Abort(msg, hint=hint) |
672 raise error.Abort(msg, hint=hint) |
673 return repo.lookup(roots[0]) |
673 return repo.lookup(roots[0]) |
674 |
674 |
675 actiontable = {'p': pick, |
675 actiontable = {'p': pick, |
676 'pick': pick, |
676 'pick': pick, |
677 'e': edit, |
677 'e': edit, |
734 def _histedit(ui, repo, state, *freeargs, **opts): |
734 def _histedit(ui, repo, state, *freeargs, **opts): |
735 # TODO only abort if we try and histedit mq patches, not just |
735 # TODO only abort if we try and histedit mq patches, not just |
736 # blanket if mq patches are applied somewhere |
736 # blanket if mq patches are applied somewhere |
737 mq = getattr(repo, 'mq', None) |
737 mq = getattr(repo, 'mq', None) |
738 if mq and mq.applied: |
738 if mq and mq.applied: |
739 raise util.Abort(_('source has mq patches applied')) |
739 raise error.Abort(_('source has mq patches applied')) |
740 |
740 |
741 # basic argument incompatibility processing |
741 # basic argument incompatibility processing |
742 outg = opts.get('outgoing') |
742 outg = opts.get('outgoing') |
743 cont = opts.get('continue') |
743 cont = opts.get('continue') |
744 editplan = opts.get('edit_plan') |
744 editplan = opts.get('edit_plan') |
746 force = opts.get('force') |
746 force = opts.get('force') |
747 rules = opts.get('commands', '') |
747 rules = opts.get('commands', '') |
748 revs = opts.get('rev', []) |
748 revs = opts.get('rev', []) |
749 goal = 'new' # This invocation goal, in new, continue, abort |
749 goal = 'new' # This invocation goal, in new, continue, abort |
750 if force and not outg: |
750 if force and not outg: |
751 raise util.Abort(_('--force only allowed with --outgoing')) |
751 raise error.Abort(_('--force only allowed with --outgoing')) |
752 if cont: |
752 if cont: |
753 if any((outg, abort, revs, freeargs, rules, editplan)): |
753 if any((outg, abort, revs, freeargs, rules, editplan)): |
754 raise util.Abort(_('no arguments allowed with --continue')) |
754 raise error.Abort(_('no arguments allowed with --continue')) |
755 goal = 'continue' |
755 goal = 'continue' |
756 elif abort: |
756 elif abort: |
757 if any((outg, revs, freeargs, rules, editplan)): |
757 if any((outg, revs, freeargs, rules, editplan)): |
758 raise util.Abort(_('no arguments allowed with --abort')) |
758 raise error.Abort(_('no arguments allowed with --abort')) |
759 goal = 'abort' |
759 goal = 'abort' |
760 elif editplan: |
760 elif editplan: |
761 if any((outg, revs, freeargs)): |
761 if any((outg, revs, freeargs)): |
762 raise util.Abort(_('only --commands argument allowed with ' |
762 raise error.Abort(_('only --commands argument allowed with ' |
763 '--edit-plan')) |
763 '--edit-plan')) |
764 goal = 'edit-plan' |
764 goal = 'edit-plan' |
765 else: |
765 else: |
766 if os.path.exists(os.path.join(repo.path, 'histedit-state')): |
766 if os.path.exists(os.path.join(repo.path, 'histedit-state')): |
767 raise util.Abort(_('history edit already in progress, try ' |
767 raise error.Abort(_('history edit already in progress, try ' |
768 '--continue or --abort')) |
768 '--continue or --abort')) |
769 if outg: |
769 if outg: |
770 if revs: |
770 if revs: |
771 raise util.Abort(_('no revisions allowed with --outgoing')) |
771 raise error.Abort(_('no revisions allowed with --outgoing')) |
772 if len(freeargs) > 1: |
772 if len(freeargs) > 1: |
773 raise util.Abort( |
773 raise error.Abort( |
774 _('only one repo argument allowed with --outgoing')) |
774 _('only one repo argument allowed with --outgoing')) |
775 else: |
775 else: |
776 revs.extend(freeargs) |
776 revs.extend(freeargs) |
777 if len(revs) == 0: |
777 if len(revs) == 0: |
778 # experimental config: histedit.defaultrev |
778 # experimental config: histedit.defaultrev |
779 histeditdefault = ui.config('histedit', 'defaultrev') |
779 histeditdefault = ui.config('histedit', 'defaultrev') |
780 if histeditdefault: |
780 if histeditdefault: |
781 revs.append(histeditdefault) |
781 revs.append(histeditdefault) |
782 if len(revs) != 1: |
782 if len(revs) != 1: |
783 raise util.Abort( |
783 raise error.Abort( |
784 _('histedit requires exactly one ancestor revision')) |
784 _('histedit requires exactly one ancestor revision')) |
785 |
785 |
786 |
786 |
787 replacements = [] |
787 replacements = [] |
788 state.keep = opts.get('keep', False) |
788 state.keep = opts.get('keep', False) |
854 remote = None |
854 remote = None |
855 root = findoutgoing(ui, repo, remote, force, opts) |
855 root = findoutgoing(ui, repo, remote, force, opts) |
856 else: |
856 else: |
857 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs))) |
857 rr = list(repo.set('roots(%ld)', scmutil.revrange(repo, revs))) |
858 if len(rr) != 1: |
858 if len(rr) != 1: |
859 raise util.Abort(_('The specified revisions must have ' |
859 raise error.Abort(_('The specified revisions must have ' |
860 'exactly one common root')) |
860 'exactly one common root')) |
861 root = rr[0].node() |
861 root = rr[0].node() |
862 |
862 |
863 revs = between(repo, root, topmost, state.keep) |
863 revs = between(repo, root, topmost, state.keep) |
864 if not revs: |
864 if not revs: |
865 raise util.Abort(_('%s is not an ancestor of working directory') % |
865 raise error.Abort(_('%s is not an ancestor of working directory') % |
866 node.short(root)) |
866 node.short(root)) |
867 |
867 |
868 ctxs = [repo[r] for r in revs] |
868 ctxs = [repo[r] for r in revs] |
869 if not rules: |
869 if not rules: |
870 comment = editcomment % (node.short(root), node.short(topmost)) |
870 comment = editcomment % (node.short(root), node.short(topmost)) |
958 s = repo.status() |
958 s = repo.status() |
959 if s.modified or s.added or s.removed or s.deleted: |
959 if s.modified or s.added or s.removed or s.deleted: |
960 actobj.continuedirty() |
960 actobj.continuedirty() |
961 s = repo.status() |
961 s = repo.status() |
962 if s.modified or s.added or s.removed or s.deleted: |
962 if s.modified or s.added or s.removed or s.deleted: |
963 raise util.Abort(_("working copy still dirty")) |
963 raise error.Abort(_("working copy still dirty")) |
964 |
964 |
965 parentctx, replacements = actobj.continueclean() |
965 parentctx, replacements = actobj.continueclean() |
966 |
966 |
967 state.parentctxnode = parentctx.node() |
967 state.parentctxnode = parentctx.node() |
968 state.replacements.extend(replacements) |
968 state.replacements.extend(replacements) |
975 When keep is false, the specified set can't have children.""" |
975 When keep is false, the specified set can't have children.""" |
976 ctxs = list(repo.set('%n::%n', old, new)) |
976 ctxs = list(repo.set('%n::%n', old, new)) |
977 if ctxs and not keep: |
977 if ctxs and not keep: |
978 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and |
978 if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and |
979 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)): |
979 repo.revs('(%ld::) - (%ld)', ctxs, ctxs)): |
980 raise util.Abort(_('cannot edit history that would orphan nodes')) |
980 raise error.Abort(_('cannot edit history that would orphan nodes')) |
981 if repo.revs('(%ld) and merge()', ctxs): |
981 if repo.revs('(%ld) and merge()', ctxs): |
982 raise util.Abort(_('cannot edit history that contains merges')) |
982 raise error.Abort(_('cannot edit history that contains merges')) |
983 root = ctxs[0] # list is already sorted by repo.set |
983 root = ctxs[0] # list is already sorted by repo.set |
984 if not root.mutable(): |
984 if not root.mutable(): |
985 raise util.Abort(_('cannot edit public changeset: %s') % root, |
985 raise error.Abort(_('cannot edit public changeset: %s') % root, |
986 hint=_('see "hg help phases" for details')) |
986 hint=_('see "hg help phases" for details')) |
987 return [c.node() for c in ctxs] |
987 return [c.node() for c in ctxs] |
988 |
988 |
989 def makedesc(repo, action, rev): |
989 def makedesc(repo, action, rev): |
990 """build a initial action line for a ctx |
990 """build a initial action line for a ctx |
1031 parsed = [] |
1031 parsed = [] |
1032 expected = set(c.hex() for c in ctxs) |
1032 expected = set(c.hex() for c in ctxs) |
1033 seen = set() |
1033 seen = set() |
1034 for r in rules: |
1034 for r in rules: |
1035 if ' ' not in r: |
1035 if ' ' not in r: |
1036 raise util.Abort(_('malformed line "%s"') % r) |
1036 raise error.Abort(_('malformed line "%s"') % r) |
1037 action, rest = r.split(' ', 1) |
1037 action, rest = r.split(' ', 1) |
1038 ha = rest.strip().split(' ', 1)[0] |
1038 ha = rest.strip().split(' ', 1)[0] |
1039 try: |
1039 try: |
1040 ha = repo[ha].hex() |
1040 ha = repo[ha].hex() |
1041 except error.RepoError: |
1041 except error.RepoError: |
1042 raise util.Abort(_('unknown changeset %s listed') % ha[:12]) |
1042 raise error.Abort(_('unknown changeset %s listed') % ha[:12]) |
1043 if ha not in expected: |
1043 if ha not in expected: |
1044 raise util.Abort( |
1044 raise error.Abort( |
1045 _('may not use changesets other than the ones listed')) |
1045 _('may not use changesets other than the ones listed')) |
1046 if ha in seen: |
1046 if ha in seen: |
1047 raise util.Abort(_('duplicated command for changeset %s') % |
1047 raise error.Abort(_('duplicated command for changeset %s') % |
1048 ha[:12]) |
1048 ha[:12]) |
1049 seen.add(ha) |
1049 seen.add(ha) |
1050 if action not in actiontable or action.startswith('_'): |
1050 if action not in actiontable or action.startswith('_'): |
1051 raise util.Abort(_('unknown action "%s"') % action) |
1051 raise error.Abort(_('unknown action "%s"') % action) |
1052 parsed.append([action, ha]) |
1052 parsed.append([action, ha]) |
1053 missing = sorted(expected - seen) # sort to stabilize output |
1053 missing = sorted(expected - seen) # sort to stabilize output |
1054 if missing: |
1054 if missing: |
1055 raise util.Abort(_('missing rules for changeset %s') % |
1055 raise error.Abort(_('missing rules for changeset %s') % |
1056 missing[0][:12], |
1056 missing[0][:12], |
1057 hint=_('do you want to use the drop action?')) |
1057 hint=_('do you want to use the drop action?')) |
1058 return parsed |
1058 return parsed |
1059 |
1059 |
1060 def newnodestoabort(state): |
1060 def newnodestoabort(state): |
1206 histedit_nodes = set([repo[rulehash].node() for (action, rulehash) |
1206 histedit_nodes = set([repo[rulehash].node() for (action, rulehash) |
1207 in state.rules if rulehash in repo]) |
1207 in state.rules if rulehash in repo]) |
1208 strip_nodes = set([repo[n].node() for n in nodelist]) |
1208 strip_nodes = set([repo[n].node() for n in nodelist]) |
1209 common_nodes = histedit_nodes & strip_nodes |
1209 common_nodes = histedit_nodes & strip_nodes |
1210 if common_nodes: |
1210 if common_nodes: |
1211 raise util.Abort(_("histedit in progress, can't strip %s") |
1211 raise error.Abort(_("histedit in progress, can't strip %s") |
1212 % ', '.join(node.short(x) for x in common_nodes)) |
1212 % ', '.join(node.short(x) for x in common_nodes)) |
1213 return orig(ui, repo, nodelist, *args, **kwargs) |
1213 return orig(ui, repo, nodelist, *args, **kwargs) |
1214 |
1214 |
1215 extensions.wrapfunction(repair, 'strip', stripwrapper) |
1215 extensions.wrapfunction(repair, 'strip', stripwrapper) |
1216 |
1216 |