mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 22:37:37 +03:00
fbamend: backport split command
Summary: Backport the split command and its dependencies (namely, `bookmarksupdater`) from mutable-history changeset cb0d62ed5e2a using GPL2 license. There are some adjustments to make the ported code work well with existing code, namely: - The `--norebase` flag was renamed as `--no-rebase` and moved to the backported command so we no longer wrap the split command. - Rebase now runs inside a same transaction. The glob change in tests is because `(tip)` shows up. Test Plan: arc unit Reviewers: #mercurial, ikostia Reviewed By: ikostia Subscribers: ikostia, mjpieters, medson Differential Revision: https://phabricator.intern.facebook.com/D5254544 Signature: t1:5254544:1497519470:fe8a7308b35b578fd4f1257cebba15062e752c2f
This commit is contained in:
parent
08e9bc0bbf
commit
020ff84b79
@ -58,6 +58,7 @@ from . import (
|
||||
movement,
|
||||
restack,
|
||||
revsets,
|
||||
split,
|
||||
unamend,
|
||||
)
|
||||
|
||||
@ -66,8 +67,9 @@ revsetpredicate = revsets.revsetpredicate
|
||||
cmdtable = {}
|
||||
command = registrar.command(cmdtable)
|
||||
|
||||
cmdtable.update(unamend.cmdtable)
|
||||
cmdtable.update(movement.cmdtable)
|
||||
cmdtable.update(split.cmdtable)
|
||||
cmdtable.update(unamend.cmdtable)
|
||||
|
||||
testedwith = 'ships-with-fb-hgext'
|
||||
|
||||
@ -124,25 +126,15 @@ def uisetup(ui):
|
||||
|
||||
evolvemod = extensions.find('evolve')
|
||||
|
||||
# Remove `hg previous`, `hg next` from evolve.
|
||||
# Remove conflicted commands from evolve.
|
||||
table = evolvemod.cmdtable
|
||||
for name in ['prev', 'next']:
|
||||
for name in ['prev', 'next', 'split']:
|
||||
todelete = [k for k in table if name in k]
|
||||
for k in todelete:
|
||||
oldentry = table[k]
|
||||
table['debugevolve%s' % name] = oldentry
|
||||
del table[k]
|
||||
|
||||
# Wrap `hg split`.
|
||||
splitentry = extensions.wrapcommand(
|
||||
evolvemod.cmdtable,
|
||||
'split',
|
||||
wrapsplit,
|
||||
)
|
||||
splitentry[1].append(
|
||||
('', 'norebase', False, _("don't rebase children after split"))
|
||||
)
|
||||
|
||||
# Wrap `hg fold`.
|
||||
foldentry = extensions.wrapcommand(
|
||||
evolvemod.cmdtable,
|
||||
@ -395,39 +387,6 @@ def fixupamend(ui, repo):
|
||||
finally:
|
||||
lockmod.release(wlock, lock, tr)
|
||||
|
||||
def wrapsplit(orig, ui, repo, *args, **opts):
|
||||
"""Automatically rebase unstable descendants after split."""
|
||||
# Find the rev number of the changeset to split. This needs to happen
|
||||
# before splitting in case the input revset is relative to the working
|
||||
# copy parent, since `hg split` may update to a new changeset.
|
||||
revs = (list(args) + opts.get('rev', [])) or ['.']
|
||||
rev = scmutil.revsingle(repo, revs[0]).rev()
|
||||
torebase = repo.revs('descendants(%d) - (%d)', rev, rev)
|
||||
|
||||
# Perform split.
|
||||
ret = orig(ui, repo, *args, **opts)
|
||||
|
||||
# Return early if split failed.
|
||||
if ret:
|
||||
return ret
|
||||
|
||||
# Fix up stack.
|
||||
if not opts['norebase'] and torebase:
|
||||
with repo.wlock():
|
||||
with repo.lock():
|
||||
with repo.transaction('splitrebase'):
|
||||
top = repo.revs('allsuccessors(%d)', rev).last()
|
||||
common.restackonce(ui, repo, top)
|
||||
# The rebasestate file is incorrectly left behind, so cleanup.
|
||||
# See the earlier comment on util.unlinkpath for more details.
|
||||
util.unlinkpath(repo.vfs.join("rebasestate"),
|
||||
ignoremissing=True)
|
||||
|
||||
# Fix up bookmarks, if any.
|
||||
_fixbookmarks(repo, [rev])
|
||||
|
||||
return ret
|
||||
|
||||
def wrapfold(orig, ui, repo, *args, **opts):
|
||||
"""Automatically rebase unstable descendants after fold."""
|
||||
# Find the rev numbers of the changesets that will be folded. This needs
|
||||
|
@ -124,3 +124,18 @@ def latest(repo, rev):
|
||||
"""
|
||||
latest = repo.revs('allsuccessors(%d)', rev).last()
|
||||
return latest if latest is not None else rev
|
||||
|
||||
def bookmarksupdater(repo, oldid, tr):
|
||||
"""Return a callable update(newid) updating the current bookmark
|
||||
and bookmarks bound to oldid to newid.
|
||||
"""
|
||||
def updatebookmarks(newid):
|
||||
dirty = False
|
||||
oldbookmarks = repo.nodebookmarks(oldid)
|
||||
if oldbookmarks:
|
||||
for b in oldbookmarks:
|
||||
repo._bookmarks[b] = newid
|
||||
dirty = True
|
||||
if dirty:
|
||||
repo._bookmarks.recordchange(tr)
|
||||
return updatebookmarks
|
||||
|
124
hgext3rd/fbamend/split.py
Normal file
124
hgext3rd/fbamend/split.py
Normal file
@ -0,0 +1,124 @@
|
||||
# split.py - split a changeset into smaller parts
|
||||
#
|
||||
# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
|
||||
# Logilab SA <contact@logilab.fr>
|
||||
# Pierre-Yves David <pierre-yves.david@ens-lyon.org>
|
||||
# Patrick Mezard <patrick@mezard.eu>
|
||||
# Copyright 2017 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.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from mercurial import (
|
||||
bookmarks,
|
||||
cmdutil,
|
||||
commands,
|
||||
error,
|
||||
hg,
|
||||
lock as lockmod,
|
||||
obsolete,
|
||||
registrar,
|
||||
scmutil,
|
||||
)
|
||||
from mercurial.i18n import _
|
||||
|
||||
from . import common
|
||||
|
||||
cmdtable = {}
|
||||
command = registrar.command(cmdtable)
|
||||
|
||||
@command('^split',
|
||||
[('r', 'rev', [], _("revision to split")),
|
||||
('', 'no-rebase', False, _("don't rebase descendants after split")),
|
||||
] + commands.commitopts + commands.commitopts2,
|
||||
_('hg split [OPTION]... [-r] [REV]'))
|
||||
def split(ui, repo, *revs, **opts):
|
||||
"""split a changeset into smaller changesets
|
||||
|
||||
By default, split the current revision by prompting for all its hunks to be
|
||||
redistributed into new changesets.
|
||||
|
||||
Use --rev to split a given changeset instead.
|
||||
"""
|
||||
tr = wlock = lock = None
|
||||
newcommits = []
|
||||
|
||||
revarg = (list(revs) + opts.get('rev')) or ['.']
|
||||
if len(revarg) != 1:
|
||||
msg = _("more than one revset is given")
|
||||
hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both")
|
||||
raise error.Abort(msg, hint=hnt)
|
||||
|
||||
rev = scmutil.revsingle(repo, revarg[0])
|
||||
if opts.get('no_rebase'):
|
||||
torebase = ()
|
||||
else:
|
||||
torebase = repo.revs('descendants(%d) - (%d)', rev, rev)
|
||||
try:
|
||||
wlock = repo.wlock()
|
||||
lock = repo.lock()
|
||||
cmdutil.bailifchanged(repo)
|
||||
tr = repo.transaction('split')
|
||||
ctx = repo[rev]
|
||||
r = ctx.rev()
|
||||
disallowunstable = not obsolete.isenabled(repo,
|
||||
obsolete.allowunstableopt)
|
||||
if disallowunstable:
|
||||
# XXX We should check head revs
|
||||
if repo.revs("(%d::) - %d", rev, rev):
|
||||
raise error.Abort(_("cannot split commit: %s not a head") % ctx)
|
||||
|
||||
if len(ctx.parents()) > 1:
|
||||
raise error.Abort(_("cannot split merge commits"))
|
||||
prev = ctx.p1()
|
||||
bmupdate = common.bookmarksupdater(repo, ctx.node(), tr)
|
||||
bookactive = repo._activebookmark
|
||||
if bookactive is not None:
|
||||
repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
|
||||
bookmarks.deactivate(repo)
|
||||
hg.update(repo, prev)
|
||||
|
||||
commands.revert(ui, repo, rev=r, all=True)
|
||||
|
||||
def haschanges():
|
||||
modified, added, removed, deleted = repo.status()[:4]
|
||||
return modified or added or removed or deleted
|
||||
msg = ("HG: This is the original pre-split commit message. "
|
||||
"Edit it as appropriate.\n\n")
|
||||
msg += ctx.description()
|
||||
opts['message'] = msg
|
||||
opts['edit'] = True
|
||||
while haschanges():
|
||||
pats = ()
|
||||
cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
|
||||
cmdutil.recordfilter, *pats, **opts)
|
||||
# TODO: Does no seem like the best way to do this
|
||||
# We should make dorecord return the newly created commit
|
||||
newcommits.append(repo['.'])
|
||||
if haschanges():
|
||||
if ui.prompt('Done splitting? [yN]', default='n') == 'y':
|
||||
commands.commit(ui, repo, **opts)
|
||||
newcommits.append(repo['.'])
|
||||
break
|
||||
else:
|
||||
ui.status(_("no more change to split\n"))
|
||||
|
||||
if newcommits:
|
||||
tip = repo[newcommits[-1]]
|
||||
bmupdate(tip.node())
|
||||
if bookactive is not None:
|
||||
bookmarks.activate(repo, bookactive)
|
||||
obsolete.createmarkers(repo, [(repo[r], newcommits)])
|
||||
|
||||
if torebase:
|
||||
top = repo.revs('allsuccessors(%d)', rev).last()
|
||||
common.restackonce(ui, repo, top, inhibithack=True)
|
||||
tr.close()
|
||||
finally:
|
||||
tr.release()
|
||||
lockmod.release(tr, lock, wlock)
|
||||
|
||||
# clean up possibly incorrect rebasestate
|
||||
repo.vfs.tryunlink('rebasestate')
|
@ -1,19 +1,13 @@
|
||||
Set up test environment.
|
||||
$ . $TESTDIR/require-ext.sh evolve
|
||||
$ extpath=`dirname $TESTDIR`
|
||||
$ cp $extpath/hgext3rd/allowunstable.py $TESTTMP
|
||||
$ cat >> $HGRCPATH << EOF
|
||||
> [extensions]
|
||||
> allowunstable=$TESTTMP/allowunstable.py
|
||||
> directaccess=$TESTDIR/../hgext3rd/directaccess.py
|
||||
> evolve=
|
||||
> fbamend=$TESTDIR/../hgext3rd/fbamend
|
||||
> inhibit=$TESTDIR/../hgext3rd/inhibit.py
|
||||
> rebase=
|
||||
> strip=
|
||||
> [experimental]
|
||||
> evolution = createmarkers
|
||||
> evolutioncommands = prev next fold split
|
||||
> evolution = createmarkers, allowunstable
|
||||
> [ui]
|
||||
> interactive = true
|
||||
> EOF
|
||||
@ -117,7 +111,7 @@ Split in the middle of a stack.
|
||||
created new head
|
||||
Done splitting? [yN] y
|
||||
rebasing 4:* "add d1 and d2" (glob)
|
||||
rebasing 5:* "add d1 and d2" (glob)
|
||||
rebasing 5:* "add d1 and d2"* (glob)
|
||||
|
||||
$ showgraph
|
||||
o 9 add d1 and d2
|
||||
@ -165,7 +159,7 @@ Split with multiple children and using hash.
|
||||
rebasing 7:* "add c1 and c2" (glob)
|
||||
rebasing 8:* "add d1 and d2" (glob)
|
||||
rebasing 9:* "add d1 and d2" (glob)
|
||||
rebasing 10:* "add d1 and d2" (glob)
|
||||
rebasing 10:* "add d1 and d2"* (glob)
|
||||
|
||||
$ showgraph
|
||||
o 18 add d1 and d2
|
||||
@ -213,7 +207,7 @@ Split using revset.
|
||||
rebasing 14:* "add c1 and c2" (glob)
|
||||
rebasing 15:* "add c1 and c2" (glob)
|
||||
rebasing 16:* "add d1 and d2" (glob)
|
||||
rebasing 17:* "add d1 and d2" (glob)
|
||||
rebasing 17:* "add d1 and d2"* (glob)
|
||||
|
||||
$ showgraph
|
||||
o 23 add d1 and d2
|
||||
@ -242,7 +236,7 @@ Test that command aborts when given multiple commits.
|
||||
(use either `hg split <rs>` or `hg split --rev <rs>`, not both)
|
||||
[255]
|
||||
|
||||
Test --norebase flag.
|
||||
Test --no-rebase flag.
|
||||
$ mkcommit e
|
||||
created new head
|
||||
$ hg rebase -s 20 -d .
|
||||
@ -272,7 +266,7 @@ Test --norebase flag.
|
||||
o 10 add d1 and d2
|
||||
|
|
||||
o 0 add a1 and a2
|
||||
$ hg split --norebase << EOF
|
||||
$ hg split --no-rebase << EOF
|
||||
> y
|
||||
> y
|
||||
> n
|
||||
|
Loading…
Reference in New Issue
Block a user