sapling/fbhistedit.py
Durham Goode 9b0613badc fbhistedit: wrap the loaded histedit
Summary:
Instead of wrapping the system histedit, lets wrap the one that was loaded by
mercurial. This makes it easier to test fbhistedit against development versions
of histedit.

Test Plan:
Ran a histedit with --config extensions.fbhistedit=/data/... --config
extensions.histedit=/data/... and verified fbhistedit wrapped the custom
histedit. Also ran the tests.

Reviewers: davidsp, rmcelroy, sid0, lcharignon, pyd

Differential Revision: https://phabricator.fb.com/D2040653
2015-05-01 15:06:09 -07:00

160 lines
5.6 KiB
Python

# fbhistedit.py - improved amend functionality
#
# Copyright 2014 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""extends the existing histedit functionality
Adds a s/stop verb to histedit to stop after a commit was picked.
"""
from mercurial import cmdutil
from mercurial import error
from mercurial import extensions
from mercurial import hg
from mercurial import lock
from mercurial import util
from mercurial.i18n import _
cmdtable = {}
command = cmdutil.command(cmdtable)
testedwith = 'internal'
def defineactions():
histedit = extensions.find('histedit')
class stop(histedit.histeditaction):
def run(self):
parentctx, replacements = super(stop, self).run()
raise error.InterventionRequired(
_('Changes commited as %s. You may amend the commit now.\n'
'When you are finished, run hg histedit --continue to resume') %
parentctx)
class execute(histedit.histeditaction):
def __init__(self, state, command):
self.state = state
self.repo = state.repo
self.command = command
@classmethod
def fromrule(cls, state, rule):
"""Parses the given rule, returning an instance of the histeditaction.
"""
command = rule
return cls(state, command)
def run(self):
state = self.state
repo, ctxnode = state.repo, state.parentctxnode
hg.update(repo, ctxnode)
# release locks so the programm can call hg and then relock.
lock.release(state.lock, state.wlock)
try:
ctx = repo[ctxnode]
rc = util.system(self.command, environ={'HGNODE': ctx.hex()},
cwd=repo.root)
except OSError as os:
raise error.InterventionRequired(
_("Cannot execute command '%s': %s") % (cmd, os))
finally:
# relock the repository
state.wlock = repo.wlock()
state.lock = repo.lock()
repo.invalidate()
repo.invalidatedirstate()
if rc != 0:
raise error.InterventionRequired(
_("Command '%s' failed with exit status %d") %
(self.command, rc))
m, a, r, d = self.repo.status()[:4]
if m or a or r or d:
self.continuedirty()
return self.continueclean()
def continuedirty(self):
raise util.Abort(_('working copy has pending changes'),
hint=_('amend, commit, or revert them and run histedit '
'--continue, or abort with histedit --abort'))
def continueclean(self):
parentctxnode = self.state.parentctxnode
newctx = self.repo['.']
if newctx.node() != parentctxnode:
return newctx, [(parentctxnode, (newctx.node(),))]
return newctx, []
return stop, execute
# HACK:
# The following function verifyrules and bootstrap continue are copied from
# histedit.py as we have no proper way of fixing up the x/exec specialcase.
def verifyrules(orig, rules, repo, ctxs):
"""Verify that there exists exactly one edit rule per given changeset.
Will abort if there are to many or too few rules, a malformed rule,
or a rule on a changeset outside of the user-given range.
"""
histedit = extensions.find('histedit')
parsed = []
expected = set(c.hex() for c in ctxs)
seen = set()
for r in rules:
if ' ' not in r:
raise util.Abort(_('malformed line "%s"') % r)
action, rest = r.split(' ', 1)
# Our x/exec specialcasing
if action in ['x', 'exec']:
parsed.append([action, rest])
else:
ha = rest.strip().split(' ', 1)[0]
try:
ha = repo[ha].hex()
except error.RepoError:
raise util.Abort(_('unknown changeset %s listed') % ha[:12])
if ha not in expected:
raise util.Abort(
_('may not use changesets other than the ones listed'))
if ha in seen:
raise util.Abort(_('duplicated command for changeset %s') %
ha[:12])
seen.add(ha)
if action not in histedit.actiontable:
raise util.Abort(_('unknown action "%s"') % action)
parsed.append([action, ha])
missing = sorted(expected - seen) # sort to stabilize output
if missing:
raise util.Abort(_('missing rules for changeset %s') % missing[0],
hint=_('do you want to use the drop action?'))
return parsed
def extsetup(ui):
histedit = extensions.find('histedit')
histedit.editcomment = _("""# Edit history between %s and %s
#
# Commits are listed from least to most recent
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, stop = use commit, and stop after committing changes
# f, fold = use commit, but combine it with the one above
# r, roll = like fold, but discard this commit's description
# d, drop = remove commit from history
# m, mess = edit message without changing commit content
# x, exec = execute given command
#
""")
stop, execute = defineactions()
histedit.actiontable['s'] = stop
histedit.actiontable['stop'] = stop
histedit.actiontable['x'] = execute
histedit.actiontable['exec'] = execute
extensions.wrapfunction(histedit, 'verifyrules', verifyrules)