diff --git a/hgext3rd/uncommit.py b/hgext3rd/uncommit.py deleted file mode 100644 --- a/hgext3rd/uncommit.py +++ /dev/null @@ -1,176 +0,0 @@ -# uncommit - undo the actions of a commit -# -# Copyright 2011 Peter Arrenbrecht -# Logilab SA -# Pierre-Yves David -# Patrick Mezard -# Copyright 2016 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. - -"""uncommit some or all of a local changeset - -This command undoes the effect of a local commit, returning the affected -files to their uncommitted state. This means that files modified or -deleted in the changeset will be left unchanged, and so will remain modified in -the working directory. -""" - -from mercurial import ( - phases, - obsolete, - commands, - error, - scmutil, - copies, - context, - node, - registrar, -) -from mercurial.i18n import _ - -cmdtable = {} -command = registrar.command(cmdtable) - -testedwith = 'ships-with-fb-hgext' - -def _updatebookmarks(repo, oldid, newid, tr): - oldbookmarks = repo.nodebookmarks(oldid) - if oldbookmarks: - changes = [] - for b in oldbookmarks: - changes.append((b, newid)) - repo._bookmarks.applychanges(repo, tr, changes) - -def _commitfiltered(repo, ctx, match): - """Recommit ctx with changed files not in match. Return the new - node identifier, or None if nothing changed. - """ - base = ctx.p1() - # ctx - initialfiles = set(ctx.files()) - exclude = set(f for f in initialfiles if match(f)) - - # No files matched commit, so nothing excluded - if not exclude: - return None - - files = (initialfiles - exclude) - if not files and not repo.ui.configbool('ui', 'allowemptycommit'): - return ctx.parents()[0].node() - - # Filter copies - copied = copies.pathcopies(base, ctx) - copied = dict((dst, src) for dst, src in copied.iteritems() - if dst in files) - def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()): - if path not in contentctx: - return None - fctx = contentctx[path] - mctx = context.memfilectx(repo, fctx.path(), fctx.data(), - fctx.islink(), - fctx.isexec(), - copied=copied.get(path)) - return mctx - - new = context.memctx(repo, - parents=[base.node(), node.nullid], - text=ctx.description(), - files=files, - filectxfn=filectxfn, - user=ctx.user(), - date=ctx.date(), - extra=ctx.extra()) - newid = repo.commitctx(new) - return newid - -def _uncommitdirstate(repo, oldctx, match): - """Fix the dirstate after switching the working directory from - oldctx to a copy of oldctx not containing changed files matched by - match. - """ - ctx = repo['.'] - ds = repo.dirstate - copies = dict(ds.copies()) - m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3] - for f in m: - if ds[f] == 'r': - # modified + removed -> removed - continue - ds.normallookup(f) - - for f in a: - if ds[f] == 'r': - # added + removed -> unknown - ds.drop(f) - elif ds[f] != 'a': - ds.add(f) - - for f in r: - 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 (m + a): - src = oldctx[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 ctx or dst in ctx or ds[dst] != 'a'): - src = None - ds.copy(src, dst) - -@command('uncommit', - commands.walkopts, - _('[OPTION]... [FILE]...')) -def uncommit(ui, repo, *pats, **opts): - """uncommit some or all of a local changeset - - This command undoes the effect of a local commit, returning the affected - files to their uncommitted state. This means that files modified or - deleted in the changeset will be left unchanged, and so will remain - modified in the working directory. - """ - - with repo.wlock(), repo.lock(): - wctx = repo[None] - - if len(wctx.parents()) <= 0 or not wctx.parents()[0]: - raise error.Abort(_("cannot uncommit null changeset")) - if len(wctx.parents()) > 1: - raise error.Abort(_("cannot uncommit while merging")) - old = repo['.'] - oldphase = old.phase() - if oldphase == phases.public: - raise error.Abort(_("cannot rewrite immutable changeset")) - if len(old.parents()) > 1: - raise error.Abort(_("cannot uncommit merge changeset")) - - with repo.transaction('uncommit') as tr: - match = scmutil.match(old, pats, opts) - newid = _commitfiltered(repo, old, match) - if newid is None: - raise error.Abort(_('nothing to uncommit')) - - if newid != old.p1().node(): - # Move local changes on filtered changeset - obsolete.createmarkers(repo, [(old, (repo[newid],))]) - phases.retractboundary(repo, tr, oldphase, [newid]) - else: - # Fully removed the old commit - obsolete.createmarkers(repo, [(old, ())]) - - with repo.dirstate.parentchange(): - repo.dirstate.setparents(newid, node.nullid) - _uncommitdirstate(repo, old, match) - - _updatebookmarks(repo, old.node(), newid, tr) diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t deleted file mode 100644 --- a/tests/test-uncommit.t +++ /dev/null @@ -1,225 +0,0 @@ -Test uncommit - set up the config - - $ cat >> $HGRCPATH < [experimental] - > evolution=createmarkers - > [extensions] - > uncommit = $TESTDIR/../hgext3rd/uncommit.py - > drawdag=$RUNTESTDIR/drawdag.py - > EOF - -Build up a repo - - $ hg init repo - $ cd repo - -Uncommit with no commits should fail - - $ hg uncommit - abort: cannot uncommit null changeset - [255] - -Create some commits - - $ touch files - $ hg add files - $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done - $ ls - file-a - file-ab - file-abc - file-abcd - file-abcde - files - $ hg log -G -T '{rev}:{node} {desc}' --hidden - @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde - | - o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd - | - o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc - | - o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab - | - o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a - -Simple uncommit off the top - - $ hg uncommit - $ hg status - M files - A file-abcde - $ hg heads -T '{rev}:{node} {desc}' - 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd (no-eol) - -Recommit - - $ hg commit -m 'new change abcde' - $ hg status - $ hg heads -T '{rev}:{node} {desc}' - 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol) - -Uncommit of non-existent and unchanged files has no effect - $ hg uncommit nothinghere - abort: nothing to uncommit - [255] - $ hg status - $ hg uncommit file-abc - abort: nothing to uncommit - [255] - $ hg status - -Try partial uncommit - - $ hg uncommit files - $ hg status - M files - $ hg heads -T '{rev}:{node} {desc}' - 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol) - $ hg log -r . -p -T '{rev}:{node} {desc}' - 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde - --- /dev/null Thu Jan 01 00:00:00 1970 +0000 - +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000 - @@ -0,0 +1,1 @@ - +abcde - - $ hg commit -m 'update files for abcde' - -Uncommit with dirty state - - $ echo "foo" >> files - $ cat files - abcde - foo - $ hg status - M files - $ hg uncommit files - $ cat files - abcde - foo - $ hg commit -m "files abcde + foo" - -Uncommit in the middle of a stack - - $ hg checkout '.^^^' - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg log -r . -p -T '{rev}:{node} {desc}' - 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc - --- /dev/null Thu Jan 01 00:00:00 1970 +0000 - +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000 - @@ -0,0 +1,1 @@ - +abc - diff -r 69a232e754b0 -r abf2df566fc1 files - --- a/files Thu Jan 01 00:00:00 1970 +0000 - +++ b/files Thu Jan 01 00:00:00 1970 +0000 - @@ -1,1 +1,1 @@ - -ab - +abc - - $ hg uncommit - $ hg status - M files - A file-abc - $ hg heads -T '{rev}:{node} {desc}' - 8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol) - $ hg commit -m 'new abc' - created new head - -Partial uncommit in the middle - - $ hg checkout '.^' - 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - $ hg log -r . -p -T '{rev}:{node} {desc}' - 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab - --- /dev/null Thu Jan 01 00:00:00 1970 +0000 - +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000 - @@ -0,0 +1,1 @@ - +ab - diff -r 3004d2d9b508 -r 69a232e754b0 files - --- a/files Thu Jan 01 00:00:00 1970 +0000 - +++ b/files Thu Jan 01 00:00:00 1970 +0000 - @@ -1,1 +1,1 @@ - -a - +ab - - $ hg uncommit file-ab - $ hg status - A file-ab - $ hg heads -T '{rev}:{node} {desc}' - 10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol) - $ hg commit -m 'update ab' - $ hg status - $ hg heads -T '{rev}:{node} {desc}' - 11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo (no-eol) - $ hg log -G -T '{rev}:{node} {desc}' --hidden - @ 11:f21039c59242b085491bb58f591afc4ed1c04c09 update ab - | - o 10:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab - | - | o 9:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc - | | - | | o 8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo - | | | - | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde - | | |/ - | | o 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde - | | | - | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde - | | |/ - | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde - | | |/ - | | o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd - | | | - | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc - | |/ - | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab - |/ - o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a - -Uncommit with draft parent - - $ hg uncommit - $ hg phase -r . - 10: draft - $ hg commit -m 'update ab again' - -Uncommit with public parent - - $ hg phase -p "::.^" - $ hg uncommit - $ hg phase -r . - 10: public - -Partial uncommit with public parent - - $ echo xyz > xyz - $ hg add xyz - $ hg commit -m "update ab and add xyz" - $ hg uncommit xyz - $ hg status - A xyz - $ hg phase -r . - 14: draft - $ hg phase -r ".^" - 10: public - -Uncommit leaving an empty changeset - - $ cd $TESTTMP - $ hg init repo1 - $ cd repo1 - $ hg debugdrawdag <<'EOS' - > Q - > | - > P - > EOS - $ hg up Q -q - $ hg uncommit --config ui.allowemptycommit=1 - $ hg log -G -T '{desc} FILES: {files}' - @ Q FILES: - | - | x Q FILES: Q - |/ - o P FILES: P - - $ hg status - A Q