mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 00:45:18 +03:00
03004ac2ef
Summary: return 0 on amend/commit with nothing changed Test Plan: Make or checkout a mercurial repository and make a commit. Run the following commands and make sure the output is the same: $ hg ci; echo $? nothing changed 0 $ hg amend; echo $? nothing changed 0 Reviewers: #sourcecontrol, durham Reviewed By: #sourcecontrol, durham Differential Revision: https://phabricator.fb.com/D2427283 Tasks: 8313450
252 lines
8.1 KiB
Python
252 lines
8.1 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 hgext import rebase
|
|
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'
|
|
|
|
amendopts = [
|
|
('', 'rebase', None, _('rebases children commits after the amend')),
|
|
('', 'fixup', None, _('rebase children commits from a previous amend')),
|
|
]
|
|
|
|
def uisetup(ui):
|
|
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')),
|
|
] + 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:
|
|
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)
|
|
|
|
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
|
|
|
|
if not opts.get('noeditmessage') and not opts.get('message'):
|
|
opts['message'] = old.description()
|
|
|
|
tempnode = []
|
|
def commitfunc(ui, repo, message, match, opts):
|
|
e = cmdutil.commiteditor
|
|
noderesult = repo.commit(message,
|
|
old.user(),
|
|
old.date(),
|
|
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()
|
|
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]:
|
|
rebase.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
|