diff --git a/hgext/uncommit.py b/hgext/uncommit.py --- a/hgext/uncommit.py +++ b/hgext/uncommit.py @@ -29,6 +29,7 @@ error, node, obsolete, + obsutil, registrar, scmutil, ) @@ -185,3 +186,87 @@ with repo.dirstate.parentchange(): repo.dirstate.setparents(newid, node.nullid) _uncommitdirstate(repo, old, match) + +def predecessormarkers(ctx): + """yields the obsolete markers marking the given changeset as a successor""" + for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()): + yield obsutil.marker(ctx.repo(), data) + +@command('^unamend', []) +def unamend(ui, repo, **opts): + """undo the amend operation on a current changeset + + This command will roll back to the previous version of a changeset, + leaving working directory in state in which it was before running + `hg amend` (e.g. files modified as part of an amend will be + marked as modified `hg status`)""" + + unfi = repo.unfiltered() + + # identify the commit from which to unamend + curctx = repo['.'] + + if not curctx.mutable(): + raise error.Abort(_('cannot unamend public changesets')) + + # identify the commit to which to unamend + markers = list(predecessormarkers(curctx)) + if len(markers) != 1: + e = _("changeset must have one predecessor, found %i predecessors") + raise error.Abort(e % len(markers)) + + prednode = markers[0].prednode() + predctx = unfi[prednode] + + if curctx.children(): + raise error.Abort(_("cannot unamend in the middle of a stack")) + + with repo.wlock(), repo.lock(): + # add an extra so that we get a new hash + extras = predctx.extra() + extras['unamend_source'] = curctx.node() + + def filectxfn(repo, ctx_, path): + try: + return predctx.filectx(path) + except KeyError: + return None + + # Make a new commit same as predctx + newctx = context.memctx(repo, + parents=(predctx.p1(), predctx.p2()), + text=predctx.description(), + files=predctx.files(), + filectxfn=filectxfn, + user=predctx.user(), + date=predctx.date(), + extra=extras) + # phase handling + commitphase = curctx.phase() + overrides = {('phases', 'new-commit'): commitphase} + with repo.ui.configoverride(overrides, 'uncommit'): + newprednode = repo.commitctx(newctx) + + newpredctx = repo[newprednode] + + changedfiles = [] + wctx = repo[None] + wm = wctx.manifest() + cm = newpredctx.manifest() + dirstate = repo.dirstate + diff = cm.diff(wm) + changedfiles.extend(diff.iterkeys()) + + tr = repo.transaction('unamend') + with dirstate.parentchange(): + dirstate.rebuild(newprednode, cm, changedfiles) + # we want added and removed files to be shown + # properly, not with ? and ! prefixes + for filename, data in diff.iteritems(): + if data[0][0] is None: + dirstate.add(filename) + if data[1][0] is None: + dirstate.remove(filename) + mapping = {curctx.node(): (newprednode,)} + scmutil.cleanupnodes(repo, mapping, 'unamend') + tr.close() diff --git a/tests/test-unamend.t b/tests/test-unamend.t new file mode 100644 --- /dev/null +++ b/tests/test-unamend.t @@ -0,0 +1,289 @@ +Test for command `hg unamend` which lives in uncommit extension +=============================================================== + + $ cat >> $HGRCPATH << EOF + > [alias] + > glog = log -G -T '{rev}:{node|short} {desc}' + > [experimental] + > evolution = createmarkers, allowunstable + > [extensions] + > rebase = + > amend = + > uncommit = + > EOF + +Repo Setup +--------- + + $ hg init repo + $ cd repo + $ for ch in {a..h}; do touch $ch; echo "foo" >> $ch; hg ci -Aqm "Added "$ch; done + + $ hg glog + @ 7:ec2426147f0e Added h + | + o 6:87d6d6676308 Added g + | + o 5:825660c69f0c Added f + | + o 4:aa98ab95a928 Added e + | + o 3:62615734edd5 Added d + | + o 2:28ad74487de9 Added c + | + o 1:29becc82797a Added b + | + o 0:18d04c59bb5d Added a + + +Trying to unamend when there was no amend done +---------------------------------------------- + + $ hg unamend + abort: changeset must have one predecessor, found 0 predecessors + [255] + +Unamend on clean wdir and tip +------------------------------ + + $ echo "bar" >> h + $ hg amend + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID c9fa1a715c1b7661c0fafb362a9f30bd75878d7d + # Parent 87d6d66763085b629e6d7ed56778c79827273022 + Added h + + diff -r 87d6d6676308 -r c9fa1a715c1b h + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/h Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,2 @@ + +foo + +bar + + $ hg glog --hidden + @ 8:c9fa1a715c1b Added h + | + | x 7:ec2426147f0e Added h + |/ + o 6:87d6d6676308 Added g + | + o 5:825660c69f0c Added f + | + o 4:aa98ab95a928 Added e + | + o 3:62615734edd5 Added d + | + o 2:28ad74487de9 Added c + | + o 1:29becc82797a Added b + | + o 0:18d04c59bb5d Added a + + $ hg unamend + $ hg glog --hidden + @ 9:8da14a1fd653 Added h + | + | x 8:c9fa1a715c1b Added h + |/ + | x 7:ec2426147f0e Added h + |/ + o 6:87d6d6676308 Added g + | + o 5:825660c69f0c Added f + | + o 4:aa98ab95a928 Added e + | + o 3:62615734edd5 Added d + | + o 2:28ad74487de9 Added c + | + o 1:29becc82797a Added b + | + o 0:18d04c59bb5d Added a + + $ hg diff + diff -r 8da14a1fd653 h + --- a/h Thu Jan 01 00:00:00 1970 +0000 + +++ b/h Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,2 @@ + foo + +bar + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 8da14a1fd653c3f07fdad5760511c9e12652a306 + # Parent 87d6d66763085b629e6d7ed56778c79827273022 + Added h + + diff -r 87d6d6676308 -r 8da14a1fd653 h + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/h Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +foo + + $ hg status + M h + + $ hg log -r . -T '{extras % "{extra}\n"}' --config alias.log=log + branch=default + unamend_source=\xc9\xfa\x1aq\\\x1bva\xc0\xfa\xfb6*\x9f0\xbdu\x87\x8d} + +Unamend on a dirty working directory +------------------------------------ + + $ hg ci -m "Added bar to h" + $ echo "bar" >> a + $ hg amend + $ echo "foobar" >> a + $ echo "bar" >> b + $ hg status + M a + M b + + $ hg unamend + + $ hg status + M a + M b + + $ hg diff + diff -r 49f03646f7a0 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,3 @@ + foo + +bar + +foobar + diff -r 49f03646f7a0 b + --- a/b Thu Jan 01 00:00:00 1970 +0000 + +++ b/b Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,2 @@ + foo + +bar + +Unamending an added file +------------------------ + + $ hg ci -m "Added things to a and b" + $ echo foo > bar + $ hg add bar + $ hg amend + + $ hg unamend + $ hg status + A bar + + $ hg revert --all + forgetting bar + +Unamending a removed file +------------------------- + + $ hg remove a + $ hg amend + + $ hg unamend + $ hg status + R a + ? bar + + $ hg revert --all + undeleting a + +Unamending an added file with dirty wdir status +----------------------------------------------- + + $ hg add bar + $ hg amend + $ echo bar >> bar + $ hg status + M bar + + $ hg unamend + $ hg status + A bar + $ hg diff + diff -r 738d71740456 bar + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/bar Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,2 @@ + +foo + +bar + + $ hg revert --all + forgetting bar + +Unamending in middle of a stack +------------------------------- + + $ hg glog + @ 19:738d71740456 Added things to a and b + | + o 12:49f03646f7a0 Added bar to h + | + o 9:8da14a1fd653 Added h + | + o 6:87d6d6676308 Added g + | + o 5:825660c69f0c Added f + | + o 4:aa98ab95a928 Added e + | + o 3:62615734edd5 Added d + | + o 2:28ad74487de9 Added c + | + o 1:29becc82797a Added b + | + o 0:18d04c59bb5d Added a + + $ hg up 5 + 2 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ echo bar >> f + $ hg amend + $ hg rebase -s 6 -d . -q + + $ hg glog + o 24:947f189a0dfc Added things to a and b + | + o 23:8c216e653148 Added bar to h + | + o 22:533b8ea407b4 Added h + | + o 21:49635b68477e Added g + | + @ 20:93f0e8ffab32 Added f + | + o 4:aa98ab95a928 Added e + | + o 3:62615734edd5 Added d + | + o 2:28ad74487de9 Added c + | + o 1:29becc82797a Added b + | + o 0:18d04c59bb5d Added a + + + $ hg unamend + abort: cannot unamend in the middle of a stack + [255] + +Trying to unamend a public changeset +------------------------------------ + + $ hg up + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg phase -r . -p + $ hg unamend + abort: cannot unamend public changesets + [255]