Mercurial > hg-stable
diff hgext/uncommit.py @ 35193:867990238dc6
unamend: move fb extension unamend to core
unamend extension adds an unamend command which undoes the effect of the amend
command. This patch moves the unamend command from that extension to uncommit
extension and this one does not completely undoes the effect of amend command as
it creates a new commit, rather than reviving the old one back.
This also adds tests for the same.
.. feature::
A new unamend command in uncommit extension which undoes the effect of the
amend command by creating a new changeset which was there before amend and
moving the changes that were amended to the working directory.
Differential Revision: https://phab.mercurial-scm.org/D821
author | Pulkit Goyal <7895pulkit@gmail.com> |
---|---|
date | Sun, 24 Sep 2017 00:56:52 +0530 |
parents | 3ebae3ec4664 |
children | 9dadcb99cc17 |
line wrap: on
line diff
--- a/hgext/uncommit.py Tue Nov 28 13:27:43 2017 -0500 +++ b/hgext/uncommit.py Sun Sep 24 00:56:52 2017 +0530 @@ -29,6 +29,7 @@ error, node, obsolete, + obsutil, pycompat, registrar, scmutil, @@ -194,3 +195,124 @@ 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) + +def _unamenddirstate(repo, predctx, curctx): + """""" + + s = repo.status(predctx, curctx) + ds = repo.dirstate + copies = dict(ds.copies()) + for f in s.modified: + if ds[f] == 'r': + # modified + removed -> removed + continue + ds.normallookup(f) + + for f in s.added: + if ds[f] == 'r': + # added + removed -> unknown + ds.drop(f) + elif ds[f] != 'a': + ds.add(f) + + for f in s.removed: + if ds[f] == 'a': + # removed + added -> normal + ds.normallookup(f) + elif ds[f] != 'r': + ds.remove(f) + + # Merge old parent and old working dir copies + oldcopies = {} + for f in (s.modified + s.added): + src = curctx[f].renamed() + if src: + oldcopies[f] = src[0] + oldcopies.update(copies) + copies = dict((dst, oldcopies.get(src, src)) + for dst, src in oldcopies.iteritems()) + # Adjust the dirstate copies + for dst, src in copies.iteritems(): + if (src not in predctx or dst in predctx or ds[dst] != 'a'): + src = None + ds.copy(src, dst) + +@command('^unamend', []) +def unamend(ui, repo, **opts): + """ + undo the most recent 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['.'] + + with repo.wlock(), repo.lock(), repo.transaction('unamend'): + 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 a changeset with children")) + + # add an extra so that we get a new hash + # note: allowing unamend to undo an unamend is an intentional feature + 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()) + + with dirstate.parentchange(): + dirstate.setparents(newprednode, node.nullid) + _unamenddirstate(repo, newpredctx, curctx) + + mapping = {curctx.node(): (newprednode,)} + scmutil.cleanupnodes(repo, mapping, 'unamend')