sapling/fbamend.py
Christian Delahousse 6c94249d6f fbamend: fix --logfile for hg amend
Summary:
Fbamend broke logfile. fbamend tends to write state to opts['message'] even
if -m or --message was not used at the cmdline. If you inputted a logfile
cmd arg, cmdutil.logmessage would see both opts['message'] and opts['logfile']
as populated and throw an abort exception. This is a workaround.

Test Plan: See test.

Reviewers: rmcelroy

Differential Revision: https://phabricator.fb.com/D2586779

Tasks: 7434543
2015-10-27 14:37:20 -07:00

287 lines
9.3 KiB
Python

# fbamend.py - improved amend functionality
#
# Copyright 2013 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 commit amend functionality
Adds an hg amend command that amends the current parent commit with the
changes in the working copy. Similiar to the existing hg commit --amend
except it doesn't prompt for the commit message unless --edit is provided.
Allows amending commits that have children and can automatically rebase
the children onto the new version of the commit
This extension is incompatible with changeset evolution. The command will
automatically disable itself if changeset evolution is enabled.
"""
from mercurial import util, cmdutil, phases, commands, bookmarks, repair
from mercurial import merge, extensions
from mercurial.node import hex
from mercurial import obsolete
from mercurial import lock as lockmod
from mercurial.i18n import _
import errno, os, re
cmdtable = {}
command = cmdutil.command(cmdtable)
testedwith = 'internal'
rebasemod = None
amendopts = [
('', 'rebase', None, _('rebases children commits after the amend')),
('', 'fixup', None, _('rebase children commits from a previous amend')),
]
def uisetup(ui):
global rebasemod
try:
rebasemod = extensions.find('rebase')
except KeyError:
ui.warn("no rebase extension detected - disabling fbamend")
return
entry = extensions.wrapcommand(commands.table, 'commit', commit)
for opt in amendopts:
opt = (opt[0], opt[1], opt[2], "(with --amend) " + opt[3])
entry[1].append(opt)
# manual call of the decorator
command('^amend', [
('A', 'addremove', None,
_('mark new/missing files as added/removed before committing')),
('e', 'edit', None, _('prompt to edit the commit message')),
('i', 'interactive', None, _('use interactive mode')),
] + amendopts + commands.walkopts + commands.commitopts,
_('hg amend [OPTION]...'))(amend)
def commit(orig, ui, repo, *pats, **opts):
if opts.get("amend"):
# commit --amend default behavior is to prompt for edit
opts['noeditmessage'] = True
return amend(ui, repo, *pats, **opts)
else:
badflags = [flag for flag in
['rebase', 'fixup'] if opts.get(flag, None)]
if badflags:
raise util.Abort(_('--%s must be called with --amend') %
badflags[0])
return orig(ui, repo, *pats, **opts)
def amend(ui, repo, *pats, **opts):
'''amend the current commit with more changes
'''
if obsolete.isenabled(repo, 'allnewcommands'):
msg = ('fbamend and evolve extension are incompatible, '
'fbamend deactivated.\n'
'You can either disable it globally:\n'
'- type `hg config --edit`\n'
'- drop the `fbamend=` line from the `[extensions]` section\n'
'or disable it for a specific repo:\n'
'- type `hg config --local --edit`\n'
'- add a `fbamend=!%s` line in the `[extensions]` section\n')
msg %= ui.config('extensions', 'fbamend')
ui.write_err(msg)
rebase = opts.get('rebase')
if rebase and _histediting(repo):
# if a histedit is in flight, it's dangerous to remove old commits
hint = _('during histedit, use amend without --rebase')
raise util.Abort('histedit in progress', hint=hint)
badflags = [flag for flag in
['rebase', 'fixup'] if opts.get(flag, None)]
if opts.get('interactive') and badflags:
raise util.Abort(_('--interactive and --%s are mutually exclusive') %
badflags[0])
fixup = opts.get('fixup')
if fixup:
fixupamend(ui, repo)
return
old = repo['.']
if old.phase() == phases.public:
raise util.Abort(_('cannot amend public changesets'))
if len(repo[None].parents()) > 1:
raise util.Abort(_('cannot amend while merging'))
haschildren = len(old.children()) > 0
opts['message'] = cmdutil.logmessage(ui, opts)
# Avoid further processing of any logfile. If such a file existed, its
# contents have been copied into opts['message'] by logmessage
opts['logfile'] = ''
if not opts.get('noeditmessage') and not opts.get('message'):
opts['message'] = old.description()
tempnode = []
commitdate = old.date() if not opts.get('date') else opts.get('date')
def commitfunc(ui, repo, message, match, opts):
e = cmdutil.commiteditor
noderesult = repo.commit(message,
old.user(),
commitdate,
match,
editor=e,
extra={})
# the temporary commit is the very first commit
if not tempnode:
tempnode.append(noderesult)
return noderesult
active = bmactive(repo)
oldbookmarks = old.bookmarks()
if haschildren:
def fakestrip(orig, ui, repo, *args, **kwargs):
if tempnode:
if tempnode[0]:
# don't strip everything, just the temp node
# this is very hacky
orig(ui, repo, tempnode[0], backup='none')
tempnode.pop()
else:
orig(ui, repo, *args, **kwargs)
extensions.wrapfunction(repair, 'strip', fakestrip)
wlock = None
lock = None
try:
wlock = repo.wlock()
lock = repo.lock()
if opts.get('interactive'):
# Strip the interactive flag to avoid infinite recursive loop
opts.pop('interactive')
cmdutil.dorecord(ui, repo, amend, None, False,
cmdutil.recordfilter, *pats, **opts)
return
else:
node = cmdutil.amend(ui, repo, commitfunc, old, {}, pats, opts)
if node == old.node():
ui.status(_("nothing changed\n"))
return 0
if haschildren and not rebase:
msg = _("warning: the commit's children were left behind\n")
if _histediting(repo):
ui.warn(msg)
ui.status(_('(this is okay since a histedit is in progress)\n'))
else:
_usereducation(ui)
ui.warn(msg)
ui.status("(use 'hg amend --fixup' to rebase them)\n")
newbookmarks = repo._bookmarks
# move old bookmarks to new node
for bm in oldbookmarks:
newbookmarks[bm] = node
if not _histediting(repo):
preamendname = _preamendname(repo, node)
if haschildren:
newbookmarks[preamendname] = old.node()
elif not active:
# update bookmark if it isn't based on the active bookmark name
oldname = _preamendname(repo, old.node())
if oldname in repo._bookmarks:
newbookmarks[preamendname] = repo._bookmarks[oldname]
del newbookmarks[oldname]
newbookmarks.write()
if rebase and haschildren:
fixupamend(ui, repo)
finally:
lockmod.release(wlock, lock)
def fixupamend(ui, repo):
"""rebases any children found on the preamend commit and strips the
preamend commit
"""
wlock = None
lock = None
try:
wlock = repo.wlock()
lock = repo.lock()
current = repo['.']
preamendname = _preamendname(repo, current.node())
if not preamendname in repo._bookmarks:
raise util.Abort(_('no bookmark %s' % preamendname),
hint=_('check if your bookmark is active'))
ui.status("rebasing the children of %s\n" % (preamendname))
old = repo[preamendname]
oldbookmarks = old.bookmarks()
active = bmactive(repo)
opts = {
'rev' : [str(c.rev()) for c in old.descendants()],
'dest' : active
}
if opts['rev'] and opts['rev'][0]:
rebasemod.rebase(ui, repo, **opts)
for bookmark in oldbookmarks:
repo._bookmarks.pop(bookmark)
repo._bookmarks.write()
if obsolete.isenabled(repo, obsolete.createmarkersopt):
# clean up the original node if inhibit kept it alive
if not old.obsolete():
obsolete.createmarkers(repo, [(old,())])
else:
repair.strip(ui, repo, old.node(), topic='preamend-backup')
merge.update(repo, current.node(), False, True, False)
if active:
bmactivate(repo, active)
finally:
lockmod.release(wlock, lock)
def _preamendname(repo, node):
suffix = '.preamend'
name = bmactive(repo)
if not name:
name = hex(node)[:12]
return name + suffix
def _histediting(repo):
return repo.vfs.exists('histedit-state')
def _usereducation(ui):
"""
You can print out a message to the user here
"""
education = ui.config('fbamend', 'education')
if education:
ui.warn(education + "\n")
### bookmarks api compatibility layer ###
def bmactivate(repo, mark):
try:
return bookmarks.activate(repo, mark)
except AttributeError:
return bookmarks.setcurrent(repo, mark)
def bmactive(repo):
try:
return repo._activebookmark
except AttributeError:
return repo._bookmarkcurrent