mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
9b0613badc
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
160 lines
5.6 KiB
Python
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)
|