mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 16:31:02 +03:00
f08e17d3ed
Summary: Using `testedwith = 'internal'` is not a good habit [1]. Having it auto-updated in batch would also introduce a lot of churn. This diff makes them "ships-with-fb-hgext". If we do want to fill the ideal "testedwith" information, we could put it in a centric place, like a "fbtestedwith" extension rewriting those "ships-with-fb-hgext" on the fly. Maybe having in-repo tags for tested Mercurial releases is also a good idea. [1]: www.mercurial-scm.org/repo/hg/rev/2af1014c2534 Test Plan: `arc lint` Reviewers: #sourcecontrol, rmcelroy Reviewed By: rmcelroy Subscribers: rmcelroy, mjpieters Differential Revision: https://phabricator.intern.facebook.com/D4244689 Signature: t1:4244689:1480440027:3dc18d017b48beba1176fbfd120351889259eb4b
237 lines
8.2 KiB
Python
237 lines
8.2 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 changeset was picked.
|
|
"""
|
|
|
|
import os
|
|
from pipes import quote
|
|
|
|
from mercurial import cmdutil
|
|
from mercurial import error
|
|
from mercurial import extensions
|
|
from mercurial import hg
|
|
from mercurial import lock
|
|
from mercurial import node
|
|
from mercurial import scmutil
|
|
from mercurial.i18n import _
|
|
|
|
cmdtable = {}
|
|
command = cmdutil.command(cmdtable)
|
|
|
|
testedwith = 'ships-with-fb-hgext'
|
|
|
|
def defineactions():
|
|
histedit = extensions.find('histedit')
|
|
@histedit.action(['stop', 's'],
|
|
_('pick changeset, and stop after committing changes'))
|
|
class stop(histedit.histeditaction):
|
|
def run(self):
|
|
parentctx, replacements = super(stop, self).run()
|
|
self.state.read()
|
|
self.state.replacements.extend(replacements)
|
|
self.state.write()
|
|
raise error.InterventionRequired(
|
|
_('Changes commited as %s. You may amend the changeset now.\n'
|
|
'When you are done, run hg histedit --continue to resume') %
|
|
parentctx)
|
|
|
|
def continueclean(self):
|
|
self.state.replacements = [(n, r) for (n, r) \
|
|
in self.state.replacements \
|
|
if n != self.node]
|
|
return super(stop, self).continueclean()
|
|
|
|
@histedit.action(['exec', 'x'],
|
|
_('execute given command'))
|
|
class execute(histedit.histeditaction):
|
|
def __init__(self, state, command):
|
|
self.state = state
|
|
self.repo = state.repo
|
|
self.command = command
|
|
self.cwd = state.repo.root
|
|
self.node = None
|
|
|
|
@classmethod
|
|
def fromrule(cls, state, rule):
|
|
"""Parses the given rule, returns an instance of the histeditaction.
|
|
"""
|
|
command = rule
|
|
return cls(state, command)
|
|
|
|
def torule(self, *args, **kwargs):
|
|
return "%s %s" % (self.verb, self.command)
|
|
|
|
def tostate(self):
|
|
"""Print an action in format used by histedit state files
|
|
(the first line is a verb, the remainder is the second)
|
|
"""
|
|
return "%s\n%s" % (self.verb, self.command)
|
|
|
|
def verify(self, *args, **kwds):
|
|
pass
|
|
|
|
def constraints(self):
|
|
return set()
|
|
|
|
def nodetoverify(self):
|
|
return None
|
|
|
|
def run(self):
|
|
state = self.state
|
|
repo, ctxnode = state.repo, state.parentctxnode
|
|
hg.update(repo, ctxnode)
|
|
|
|
# release locks so the program can call hg and then relock.
|
|
lock.release(state.lock, state.wlock)
|
|
|
|
try:
|
|
ctx = repo[ctxnode]
|
|
shell = os.environ.get('SHELL', None)
|
|
cmd = self.command
|
|
if shell and self.repo.ui.config('fbhistedit',
|
|
'exec_in_user_shell'):
|
|
cmd = "%s -c -i %s" % (shell, quote(cmd))
|
|
rc = repo.ui.system(cmd, environ={'HGNODE': ctx.hex()},
|
|
cwd=self.cwd)
|
|
except OSError as ose:
|
|
raise error.InterventionRequired(
|
|
_("Cannot execute command '%s': %s") % (self.command, ose))
|
|
finally:
|
|
# relock the repository
|
|
state.wlock = repo.wlock()
|
|
state.lock = repo.lock()
|
|
repo.invalidateall()
|
|
|
|
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 error.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, []
|
|
|
|
@histedit.action(['execr', 'xr'],
|
|
_('execute given command relative to current directory'))
|
|
class executerelative(execute):
|
|
def __init__(self, state, command):
|
|
super(executerelative, self).__init__(state, command)
|
|
self.cwd = os.getcwd()
|
|
|
|
return stop, execute, executerelative
|
|
|
|
def extsetup(ui):
|
|
stop, execute, executerel = defineactions()
|
|
|
|
if ui.config('experimental', 'histeditng'):
|
|
rebase = extensions.find('rebase')
|
|
extensions.wrapcommand(rebase.cmdtable, 'rebase', _rebase,
|
|
synopsis=' [-i]')
|
|
|
|
aliases, entry = cmdutil.findcmd('rebase', rebase.cmdtable)
|
|
newentry = list(entry)
|
|
options = newentry[1]
|
|
# dirty hack because we need to change an existing switch
|
|
for idx, opt in enumerate(options):
|
|
if opt[0] == 'i':
|
|
del options[idx]
|
|
options.append(('i', 'interactive', False, 'interactive rebase'))
|
|
rebase.cmdtable['rebase'] = tuple(newentry)
|
|
|
|
|
|
def _rebase(orig, ui, repo, **opts):
|
|
histedit = extensions.find('histedit')
|
|
|
|
contf = opts.get('continue')
|
|
abortf = opts.get('abort')
|
|
|
|
if (contf or abortf) and \
|
|
not repo.vfs.exists('rebasestate') and\
|
|
repo.vfs.exists('histedit.state'):
|
|
msg = _("no rebase in progress")
|
|
hint = _('If you want to continue or abort an interactive rebase please'
|
|
' use "histedit --continue/--abort" instead.')
|
|
raise error.Abort(msg, hint=hint)
|
|
|
|
if not opts.get('interactive'):
|
|
return orig(ui, repo, **opts)
|
|
|
|
|
|
# the argument parsing has as lot of copy-paste from rebase.py
|
|
# Validate input and define rebasing points
|
|
destf = opts.get('dest', None)
|
|
srcf = opts.get('source', None)
|
|
basef = opts.get('base', None)
|
|
revf = opts.get('rev', [])
|
|
keepf = opts.get('keep', False)
|
|
|
|
src = None
|
|
|
|
if contf or abortf:
|
|
raise error.Abort('no interactive rebase in progress')
|
|
if destf:
|
|
dest = scmutil.revsingle(repo, destf)
|
|
else:
|
|
raise error.Abort("you must specify a destination (-d) for the rebase")
|
|
|
|
if srcf and basef:
|
|
raise error.Abort(_('cannot specify both a source and a base'))
|
|
if revf:
|
|
raise error.Abort('--rev not supported with interactive rebase')
|
|
elif srcf:
|
|
src = scmutil.revsingle(repo, srcf)
|
|
else:
|
|
base = scmutil.revrange(repo, [basef or '.'])
|
|
if not base:
|
|
ui.status(_('empty "base" revision set - '
|
|
"can't compute rebase set\n"))
|
|
return 1
|
|
commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
|
|
if commonanc is not None:
|
|
src = repo.revs('min((%d::(%ld) - %d)::)',
|
|
commonanc, base, commonanc).first()
|
|
else:
|
|
src = None
|
|
|
|
if src is None:
|
|
raise error.Abort('no revisions to rebase')
|
|
src = repo[src].node()
|
|
|
|
topmost, empty = repo.dirstate.parents()
|
|
revs = histedit.between(repo, src, topmost, keepf)
|
|
ctxs = [repo[r] for r in revs]
|
|
state = histedit.histeditstate(repo)
|
|
rules = [histedit.base(state, repo[dest])] + \
|
|
[histedit.pick(state, ctx) for ctx in ctxs]
|
|
editcomment = """#
|
|
# Interactive rebase is just a wrapper over histedit (adding the 'base' line as
|
|
# the first rule). To continue or abort it you should use:
|
|
# "hg histedit --continue" and "--abort"
|
|
#
|
|
"""
|
|
editcomment += histedit.geteditcomment(ui, node.short(src),
|
|
node.short(topmost))
|
|
histedit.ruleeditor(repo, ui, rules, editcomment=editcomment)
|
|
|
|
return histedit.histedit(ui, repo, node.hex(src), keep=keepf,
|
|
commands=repo.join('histedit-last-edit.txt'))
|