diff --git a/hgext/uncommit.py b/hgext/uncommit.py --- a/hgext/uncommit.py +++ b/hgext/uncommit.py @@ -28,11 +28,14 @@ copies as copiesmod, error, node, + obsolete, obsutil, + patch, pycompat, registrar, rewriteutil, scmutil, + util, ) cmdtable = {} @@ -45,6 +48,8 @@ default=False, ) +stringio = util.stringio + # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should # be specifying the version(s) of Mercurial they are tested with, or @@ -93,33 +98,83 @@ extra=ctx.extra()) return repo.commitctx(new) -def _fixdirstate(repo, oldctx, newctx, match=None): - """ fix the dirstate after switching the working directory from oldctx to - newctx which can be result of either unamend or uncommit. +def _uncommitdirstate(repo, oldctx, newctx, match, interactive): + """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 - ds.setparents(newctx.node(), node.nullid) copies = dict(ds.copies()) - s = newctx.status(oldctx, match=match) - for f in s.modified: - if ds[f] == 'r': - # modified + removed -> removed - continue - ds.normallookup(f) + if interactive: + # In interactive cases, we will find the status between oldctx and ctx + # and considering only the files which are changed between oldctx and + # ctx, and the status of what changed between oldctx and ctx will help + # us in defining the exact behavior + m, a, r = repo.status(oldctx, ctx, match=match)[:3] + for f in m: + # These are files which are modified between oldctx and ctx which + # contains two cases: 1) Were modified in oldctx and some + # modifications are uncommitted + # 2) Were added in oldctx but some part is uncommitted (this cannot + # contain the case when added files are uncommitted completely as + # that will result in status as removed not modified.) + # Also any modifications to a removed file will result the status as + # added, so we have only two cases. So in either of the cases, the + # resulting status can be modified or clean. + if ds[f] == 'r': + # But the file is removed in the working directory, leaving that + # as 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 a: + # These are the files which are added between oldctx and ctx(new + # one), which means the files which were removed in oldctx + # but uncommitted completely while making the ctx + # This file should be marked as removed if the working directory + # does not adds it back. If it's adds it back, we do a normallookup. + # The file can't be removed in working directory, because it was + # removed in oldctx + if ds[f] == 'a': + ds.normallookup(f) + continue + ds.remove(f) - for f in s.removed: - if ds[f] == 'a': - # removed + added -> normal + for f in r: + # These are files which are removed between oldctx and ctx, which + # means the files which were added in oldctx and were completely + # uncommitted in ctx. If a added file is partially uncommitted, that + # would have resulted in modified status, not removed. + # So a file added in a commit, and uncommitting that addition must + # result in file being stated as unknown. + if ds[f] == 'r': + # The working directory say it's removed, so lets make the file + # unknown + ds.drop(f) + continue + ds.add(f) + else: + s = newctx.status(oldctx, match=match) + for f in s.modified: + if ds[f] == 'r': + # modified + removed -> removed + continue ds.normallookup(f) - elif ds[f] != 'r': - ds.remove(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 = copiesmod.pathcopies(newctx, oldctx, match) @@ -133,7 +188,8 @@ ds.copy(src, dst) @command('uncommit', - [('', 'keep', False, _('allow an empty commit after uncommiting')), + [('i', 'interactive', False, _('interactive mode to uncommit')), + ('', 'keep', False, _('allow an empty commit after uncommiting')), ] + commands.walkopts, _('[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_CHANGE_MANAGEMENT) @@ -149,6 +205,7 @@ given. """ opts = pycompat.byteskwargs(opts) + interactive = opts.get('interactive') with repo.wlock(), repo.lock(): @@ -164,6 +221,9 @@ match = scmutil.match(old, pats, opts) keepcommit = opts.get('keep') or pats newid = _commitfiltered(repo, old, match, keepcommit) + if interactive: + newid = _interactiveuncommit(ui, repo, old, match) + if newid is None: ui.status(_("nothing to uncommit\n")) return 1 @@ -177,10 +237,106 @@ mapping[old.node()] = () with repo.dirstate.parentchange(): - _fixdirstate(repo, old, repo[newid], match) + repo.dirstate.setparents(newid, node.nullid) + _uncommitdirstate(repo, old, repo[newid], match, interactive) scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True) +def _interactiveuncommit(ui, repo, old, match): + """ The function which contains all the logic for interactively uncommiting + a commit. This function makes a temporary commit with the chunks which user + selected to uncommit. After that the diff of the parent and that commit is + applied to the working directory and committed again which results in the + new commit which should be one after uncommitted. + """ + + # create a temporary commit with hunks user selected + tempnode = _createtempcommit(ui, repo, old, match) + + diffopts = patch.difffeatureopts(repo.ui, whitespace=True) + diffopts.nodates = True + diffopts.git = True + fp = stringio() + for chunk, label in patch.diffui(repo, tempnode, old.node(), None, + opts=diffopts): + fp.write(chunk) + + fp.seek(0) + newnode = _patchtocommit(ui, repo, old, fp) + # creating obs marker temp -> () + obsolete.createmarkers(repo, [(repo[tempnode], ())], operation="uncommit") + return newnode + +def _createtempcommit(ui, repo, old, match): + """ Creates a temporary commit for `uncommit --interative` which contains + the hunks which were selected by the user to uncommit. + """ + + pold = old.p1() + # The logic to interactively selecting something copied from + # cmdutil.revert() + diffopts = patch.difffeatureopts(repo.ui, whitespace=True) + diffopts.nodates = True + diffopts.git = True + diff = patch.diff(repo, pold.node(), old.node(), match, opts=diffopts) + originalchunks = patch.parsepatch(diff) + # XXX: The interactive selection is buggy and does not let you + # uncommit a removed file partially. + # TODO: wrap the operations in mercurial/patch.py and mercurial/crecord.py + # to add uncommit as an operation taking care of BC. + chunks, opts = cmdutil.recordfilter(repo.ui, originalchunks, + operation='discard') + if not chunks: + raise error.Abort(_("nothing selected to uncommit")) + fp = stringio() + for c in chunks: + c.write(fp) + + fp.seek(0) + oldnode = node.hex(old.node())[:12] + message = 'temporary commit for uncommiting %s' % oldnode + tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode) + return tempnode + +def _patchtocommit(ui, repo, old, fp, message=None, extras=None): + """ A function which will apply the patch to the working directory and + make a commit whose parents are same as that of old argument. The message + argument tells us whether to use the message of the old commit or a + different message which is passed. Returns the node of new commit made. + """ + pold = old.p1() + parents = (old.p1().node(), old.p2().node()) + date = old.date() + branch = old.branch() + user = old.user() + extra = old.extra() + if extras: + extra['uncommit_source'] = extras + if not message: + message = old.description() + store = patch.filestore() + try: + files = set() + try: + patch.patchrepo(ui, repo, pold, store, fp, 1, '', + files=files, eolmode=None) + except patch.PatchError as err: + raise error.Abort(str(err)) + + finally: + del fp + + memctx = context.memctx(repo, parents, message, files=files, + filectxfn=store, + user=user, + date=date, + branch=branch, + extra=extra) + newcm = memctx.commit() + finally: + store.close() + return newcm + def predecessormarkers(ctx): """yields the obsolete markers marking the given changeset as a successor""" for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()): @@ -239,7 +395,9 @@ dirstate = repo.dirstate with dirstate.parentchange(): - _fixdirstate(repo, curctx, newpredctx) + match = None + interactive = 0 + _uncommitdirstate(repo, curctx, newpredctx, match, interactive) mapping = {curctx.node(): (newprednode,)} scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True) diff --git a/tests/test-unamend.t b/tests/test-unamend.t --- a/tests/test-unamend.t +++ b/tests/test-unamend.t @@ -83,9 +83,9 @@ $ hg unamend $ hg glog --hidden - @ 9:46d02d47eec6 Added h + o 9:46d02d47eec6 Added h | - | x 8:c9fa1a715c1b Added h + | @ 8:c9fa1a715c1b Added h |/ | x 7:ec2426147f0e Added h |/ @@ -104,34 +104,28 @@ o 0:18d04c59bb5d Added a $ hg diff - diff -r 46d02d47eec6 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 46d02d47eec6ca096b8dcab3f8f5579c40c3dd9a + # Node ID c9fa1a715c1b7661c0fafb362a9f30bd75878d7d # Parent 87d6d66763085b629e6d7ed56778c79827273022 Added h - diff -r 87d6d6676308 -r 46d02d47eec6 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,1 @@ + @@ -0,0 +1,2 @@ +foo + +bar $ hg status - M h $ hg log -r . -T '{extras % "{extra}\n"}' --config alias.log=log + amend_source=ec2426147f0e39dbc9cef599b066be6035ce691d branch=default - unamend_source=c9fa1a715c1b7661c0fafb362a9f30bd75878d7d Using unamend to undo an unamed (intentional) @@ -141,11 +135,11 @@ # User test # Date 0 0 # Thu Jan 01 00:00:00 1970 +0000 - # Node ID 850ddfc1bc662997ec6094ada958f01f0cc8070a + # Node ID c9fa1a715c1b7661c0fafb362a9f30bd75878d7d # Parent 87d6d66763085b629e6d7ed56778c79827273022 Added h - diff -r 87d6d6676308 -r 850ddfc1bc66 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 @@ @@ -157,6 +151,7 @@ $ echo "bar" >> a $ hg amend + 2 new content-divergent changesets $ echo "foobar" >> a $ echo "bar" >> b $ hg status @@ -170,14 +165,14 @@ M b $ hg diff - diff -r ec338db45d51 a + diff -r 924a1bde06b4 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 @@ + @@ -1,2 +1,3 @@ foo - +bar + bar +foobar - diff -r ec338db45d51 b + diff -r 924a1bde06b4 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 @@ @@ -187,6 +182,7 @@ Unamending an added file $ hg ci -m "Added things to a and b" + 1 new orphan changesets $ echo foo > bar $ hg add bar $ hg amend @@ -202,6 +198,8 @@ $ hg remove a $ hg amend + 1 new orphan changesets + 2 new content-divergent changesets $ hg unamend $ hg status @@ -210,37 +208,51 @@ $ hg revert --all undeleting a + abort: a@a7d3ff363911: not found in manifest! + [255] Unamending an added file with dirty wdir status $ hg add bar $ hg amend + 1 new orphan changesets + 1 new content-divergent changesets $ echo bar >> bar $ hg status M bar $ hg unamend $ hg status - A bar + M bar $ hg diff - diff -r 7f79409af972 bar - --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + diff -r cfcfb29e2ff7 bar + --- a/bar Thu Jan 01 00:00:00 1970 +0000 +++ b/bar Thu Jan 01 00:00:00 1970 +0000 - @@ -0,0 +1,2 @@ - +foo + @@ -1,1 +1,2 @@ + foo +bar $ hg revert --all - forgetting bar + reverting bar $ rm bar Unamending in middle of a stack $ hg glog - @ 19:7f79409af972 Added things to a and b + * 18:93b9747d736b Added things to a and b | - o 12:ec338db45d51 Added h - | + | @ 17:cfcfb29e2ff7 Added things to a and b + |/ + | * 16:f3e8006b11ed Added things to a and b + |/ + | * 14:61096993d6f1 Added things to a and b + |/ + | * 11:f392ea0dfe43 Added h + | | + x | 10:924a1bde06b4 Added h + |/ + | * 9:46d02d47eec6 Added h + |/ o 6:87d6d6676308 Added g | o 5:825660c69f0c Added f @@ -263,14 +275,26 @@ $ hg rebase -s 6 -d . -q $ hg glog - o 23:03ddd6fc5af1 Added things to a and b + * 22:744f14403439 Added h | - o 22:3e7b64ee157b Added h + | * 21:cf05c9d36833 Added h + |/ + o 20:49635b68477e Added g + | + @ 19:93f0e8ffab32 Added f | - o 21:49635b68477e Added g - | - @ 20:93f0e8ffab32 Added f - | + | * 18:93b9747d736b Added things to a and b + | | + | | * 16:f3e8006b11ed Added things to a and b + | |/ + | | * 14:61096993d6f1 Added things to a and b + | |/ + | x 10:924a1bde06b4 Added h + | | + | x 6:87d6d6676308 Added g + | | + | x 5:825660c69f0c Added f + |/ o 4:aa98ab95a928 Added e | o 3:62615734edd5 Added d @@ -292,9 +316,8 @@ Trying to unamend a public changeset $ hg up -C 23 - 5 files updated, 0 files merged, 0 files removed, 0 files unresolved + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg phase -r . -p - 1 new phase-divergent changesets $ hg unamend abort: cannot unamend public changesets (see 'hg help phases' for details) @@ -303,6 +326,7 @@ Testing whether unamend retains copies or not $ hg status + ? bar.orig $ hg mv a foo @@ -312,8 +336,8 @@ # User test # Date 0 0 # Thu Jan 01 00:00:00 1970 +0000 - # Node ID cfef290346fbee5126313d7e1aab51d877679b09 - # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231 + # Node ID 801c2513f166634bfae97bad18df32b999050c12 + # Parent 7a9352c9ddcedbf857d38c13bca4679f7a3ba78a Moved a to foo diff --git a/a b/foo @@ -332,8 +356,8 @@ # User test # Date 0 0 # Thu Jan 01 00:00:00 1970 +0000 - # Node ID eca050985275bb271ce3092b54e56ea5c85d29a3 - # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231 + # Node ID 7b9f94d173c7d15a9e32e33afb654de21dbc16bf + # Parent 7a9352c9ddcedbf857d38c13bca4679f7a3ba78a Moved a to foo diff --git a/a b/foo @@ -353,24 +377,32 @@ # User test # Date 0 0 # Thu Jan 01 00:00:00 1970 +0000 - # Node ID 552e3af4f01f620f88ca27be1f898316235b736a - # Parent 03ddd6fc5af19e028c44a2fd6d790dd22712f231 + # Node ID 7b9f94d173c7d15a9e32e33afb654de21dbc16bf + # Parent 7a9352c9ddcedbf857d38c13bca4679f7a3ba78a Moved a to foo diff --git a/a b/foo rename from a rename to foo + diff --git a/b b/foobar + rename from b + rename to foobar Retained copies in working directoy $ hg diff --git - diff --git a/b b/foobar - rename from b - rename to foobar + diff --git a/foobar b/foobar + new file mode 100644 + --- /dev/null + +++ b/foobar + @@ -0,0 +1,1 @@ + +foo diff --git a/c b/wat rename from c rename to wat $ hg revert -qa + abort: b@7b9f94d173c7: not found in manifest! + [255] $ rm foobar wat Rename a->b, then amend b->c. After unamend, should look like b->c. @@ -382,14 +414,17 @@ $ hg amend $ hg unamend $ hg st --copies --change . - A b + A c a R a $ hg st --copies A c b R b + ? bar.orig $ hg revert -qa + abort: b@a629a52c2105: not found in manifest! + [255] $ rm c Rename a->b, then amend b->c, and working copy change c->d. After unamend, should look like b->d @@ -402,10 +437,11 @@ $ hg mv c d $ hg unamend $ hg st --copies --change . - A b + A c a R a $ hg st --copies A d b R b + ? bar.orig diff --git a/tests/test-uncommit-interactive.t b/tests/test-uncommit-interactive.t new file mode 100644 --- /dev/null +++ b/tests/test-uncommit-interactive.t @@ -0,0 +1,969 @@ +================================================ +|| The test for `hg uncommit --interactive` || +================================================ + +Repo Setup +============ + + $ cat >> $HGRCPATH < [ui] + > interactive = true + > [experimental] + > evolution.createmarkers=True + > evolution.allowunstable=True + > uncommitondirtywdir = true + > [extensions] + > uncommit = + > amend = + > drawdag=$TESTDIR/drawdag.py + > EOF + $ glog() { + > hg log -G --template '{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n' "$@" + > } + + $ hg init repo + $ cd repo + + $ touch a + $ cat >> a << EOF + > 1 + > 2 + > 3 + > 4 + > 5 + > EOF + + $ hg add a + $ hg ci -m "The base commit" + +Make sure aborting the interactive selection does no magic +---------------------------------------------------------- + + $ hg status + $ hg uncommit -i< q + > EOF + diff --git a/a b/a + new file mode 100644 + examine changes to 'a'? [Ynesfdaq?] q + + abort: user quit + [255] + $ hg status + +Make a commit with multiple hunks +--------------------------------- + + $ cat > a << EOF + > -2 + > -1 + > 0 + > 1 + > 2 + > 3 + > foo + > bar + > 4 + > 5 + > babar + > EOF + + $ hg diff + diff -r 7733902a8d94 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,5 +1,11 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + +foo + +bar + 4 + 5 + +babar + + $ hg ci -m "another one" + +Not selecting anything to uncommit +================================== + + $ hg uncommit -i< y + > n + > n + > n + > EOF + diff --git a/a b/a + 3 hunks, 6 lines changed + examine changes to 'a'? [Ynesfdaq?] y + + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + discard change 1/3 to 'a'? [Ynesfdaq?] n + + @@ -1,5 +4,7 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + discard change 2/3 to 'a'? [Ynesfdaq?] n + + @@ -4,2 +9,3 @@ + 4 + 5 + +babar + discard change 3/3 to 'a'? [Ynesfdaq?] n + + abort: nothing selected to uncommit + [255] + $ hg status + +Uncommit a chunk +================ + + $ hg uncommit -i< y + > y + > n + > n + > EOF + diff --git a/a b/a + 3 hunks, 6 lines changed + examine changes to 'a'? [Ynesfdaq?] y + + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + discard change 1/3 to 'a'? [Ynesfdaq?] y + + @@ -1,5 +4,7 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + discard change 2/3 to 'a'? [Ynesfdaq?] n + + @@ -4,2 +9,3 @@ + 4 + 5 + +babar + discard change 3/3 to 'a'? [Ynesfdaq?] n + + + $ hg log -G --hidden + @ changeset: 3:678a59e5ff90 + | tag: tip + | parent: 0:7733902a8d94 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: another one + | + | x changeset: 2:e9635f4beaf1 + |/ parent: 0:7733902a8d94 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | obsolete: pruned using uncommit + | summary: temporary commit for uncommiting f70fb463d5bf + | + | x changeset: 1:f70fb463d5bf + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | obsolete: rewritten using uncommit as 3:678a59e5ff90 + | summary: another one + | + o changeset: 0:7733902a8d94 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: The base commit + +The unselected part should be in the diff +----------------------------------------- + + $ hg diff + diff -r 678a59e5ff90 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + +The commit should contain the rest of part +------------------------------------------ + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 678a59e5ff90754d5e94719bd82ad169be773c21 + # Parent 7733902a8d94c789ca81d866bea1893d79442db6 + another one + + diff -r 7733902a8d94 -r 678a59e5ff90 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,5 +1,8 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + +babar + +Uncommiting on dirty working directory +====================================== + + $ hg status + M a + $ hg diff + diff -r 678a59e5ff90 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + + $ hg uncommit -i< y + > n + > y + > EOF + diff --git a/a b/a + 2 hunks, 3 lines changed + examine changes to 'a'? [Ynesfdaq?] y + + @@ -1,5 +1,7 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + discard change 1/2 to 'a'? [Ynesfdaq?] n + + @@ -4,2 +6,3 @@ + 4 + 5 + +babar + discard change 2/2 to 'a'? [Ynesfdaq?] y + + patching file a + Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines). + + $ hg diff + diff -r 46e35360be47 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + @@ -5,3 +8,4 @@ + bar + 4 + 5 + +babar + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 46e35360be473bf761bedf3d05de4a68ffd9d9f8 + # Parent 7733902a8d94c789ca81d866bea1893d79442db6 + another one + + diff -r 7733902a8d94 -r 46e35360be47 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,5 +1,7 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + +Checking the obsolescence history + + $ hg log -G --hidden + @ changeset: 5:46e35360be47 + | tag: tip + | parent: 0:7733902a8d94 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: another one + | + | x changeset: 4:7ca9935a62f1 + |/ parent: 0:7733902a8d94 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | obsolete: pruned using uncommit + | summary: temporary commit for uncommiting 678a59e5ff90 + | + | x changeset: 3:678a59e5ff90 + |/ parent: 0:7733902a8d94 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | obsolete: rewritten using uncommit as 5:46e35360be47 + | summary: another one + | + | x changeset: 2:e9635f4beaf1 + |/ parent: 0:7733902a8d94 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | obsolete: pruned using uncommit + | summary: temporary commit for uncommiting f70fb463d5bf + | + | x changeset: 1:f70fb463d5bf + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | obsolete: rewritten using uncommit as 3:678a59e5ff90 + | summary: another one + | + o changeset: 0:7733902a8d94 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: The base commit + +Push the changes back to the commit and more commits for more testing + + $ hg amend + $ glog + @ 6:905eb2a23ea2@default(draft) another one + | + o 0:7733902a8d94@default(draft) The base commit + + $ touch foo + $ echo "hey" >> foo + $ hg ci -Am "Added foo" + adding foo + +Testing uncommiting a whole changeset and also for a file addition +================================================================== + + $ hg uncommit -i< y + > y + > EOF + diff --git a/foo b/foo + new file mode 100644 + examine changes to 'foo'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +hey + discard this change to 'foo'? [Ynesfdaq?] y + + + $ hg status + A foo + $ hg diff + diff -r 857367499298 foo + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/foo Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +hey + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 857367499298e999b5841bb01df65f73088b5d3b + # Parent 905eb2a23ea2d92073419d0e19165b90d36ea223 + Added foo + + $ hg amend + +Testing to uncommit removed files completely +============================================ + + $ hg rm a + $ hg ci -m "Removed a" + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 219cfe20964e93f8bb9bd82ceaa54d3b776046db + # Parent 42cc15efbec26c14d96d805dee2766ba91d1fd31 + Removed a + + diff -r 42cc15efbec2 -r 219cfe20964e a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 + @@ -1,11 +0,0 @@ + --2 + --1 + -0 + -1 + -2 + -3 + -foo + -bar + -4 + -5 + -babar + +Not examining the file +---------------------- + + $ hg uncommit -i< n + > EOF + diff --git a/a b/a + deleted file mode 100644 + examine changes to 'a'? [Ynesfdaq?] n + + abort: nothing selected to uncommit + [255] + +Examining the file +------------------ +XXX: there is a bug in interactive selection as it is not letting to examine the +file. Tried with curses too. In the curses UI, if you just unselect the hunks +and the not file mod thing at the top, it will show the same "nothing unselected +to uncommit" message which is a bug in interactive selection. + + $ hg uncommit -i< y + > EOF + diff --git a/a b/a + deleted file mode 100644 + examine changes to 'a'? [Ynesfdaq?] y + + + $ hg diff + diff -r 737487f1e5f8 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 + @@ -1,11 +0,0 @@ + --2 + --1 + -0 + -1 + -2 + -3 + -foo + -bar + -4 + -5 + -babar + $ hg status + R a + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 737487f1e5f853e55decb73ea31522c63e7f5980 + # Parent 42cc15efbec26c14d96d805dee2766ba91d1fd31 + Removed a + + + $ hg prune . + hg: unknown command 'prune' + (use 'hg help' for a list of commands) + [255] + $ hg revert --all + undeleting a + + $ glog + @ 13:737487f1e5f8@default(draft) Removed a + | + o 10:42cc15efbec2@default(draft) Added foo + | + o 6:905eb2a23ea2@default(draft) another one + | + o 0:7733902a8d94@default(draft) The base commit + + +Testing when a new file is added in the last commit +=================================================== + + $ echo "foo" >> foo + $ touch x + $ echo "abcd" >> x + $ hg add x + $ hg ci -m "Added x" + $ hg uncommit -i< y + > y + > y + > n + > EOF + diff --git a/foo b/foo + 1 hunks, 1 lines changed + examine changes to 'foo'? [Ynesfdaq?] y + + @@ -1,1 +1,2 @@ + hey + +foo + discard change 1/2 to 'foo'? [Ynesfdaq?] y + + diff --git a/x b/x + new file mode 100644 + examine changes to 'x'? [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + +abcd + discard change 2/2 to 'x'? [Ynesfdaq?] n + + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID f7b39cf595081f8e63fe1119953cc7f669663720 + # Parent 737487f1e5f853e55decb73ea31522c63e7f5980 + Added x + + diff -r 737487f1e5f8 -r f7b39cf59508 x + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/x Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +abcd + + $ hg diff + diff -r f7b39cf59508 foo + --- a/foo Thu Jan 01 00:00:00 1970 +0000 + +++ b/foo Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,2 @@ + hey + +foo + + $ hg status + M foo + + $ hg revert --all + reverting foo + +Testing between the stack and with dirty working copy +===================================================== + + $ glog + @ 16:f7b39cf59508@default(draft) Added x + | + o 13:737487f1e5f8@default(draft) Removed a + | + o 10:42cc15efbec2@default(draft) Added foo + | + o 6:905eb2a23ea2@default(draft) another one + | + o 0:7733902a8d94@default(draft) The base commit + + $ hg up 905eb2a23ea2 + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + + $ touch bar + $ echo "foo" >> bar + $ hg add bar + $ hg status + A bar + ? foo.orig + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 905eb2a23ea2d92073419d0e19165b90d36ea223 + # Parent 7733902a8d94c789ca81d866bea1893d79442db6 + another one + + diff -r 7733902a8d94 -r 905eb2a23ea2 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,5 +1,11 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + +foo + +bar + 4 + 5 + +babar + + $ hg uncommit -i< y + > n + > n + > y + > EOF + diff --git a/a b/a + 3 hunks, 6 lines changed + examine changes to 'a'? [Ynesfdaq?] y + + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + discard change 1/3 to 'a'? [Ynesfdaq?] n + + @@ -1,5 +4,7 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + discard change 2/3 to 'a'? [Ynesfdaq?] n + + @@ -4,2 +9,3 @@ + 4 + 5 + +babar + discard change 3/3 to 'a'? [Ynesfdaq?] y + + patching file a + Hunk #1 succeeded at 1 with fuzz 1 (offset -1 lines). + 3 new orphan changesets + + $ hg diff + diff -r 676366511f95 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -8,3 +8,4 @@ + bar + 4 + 5 + +babar + diff -r 676366511f95 bar + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/bar Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +foo + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 676366511f95ca4122413dcf79b45eaab61fb387 + # Parent 7733902a8d94c789ca81d866bea1893d79442db6 + another one + + diff -r 7733902a8d94 -r 676366511f95 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,5 +1,10 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + +foo + +bar + 4 + 5 + $ hg status + M a + A bar + ? foo.orig + +More uncommit on the same dirty working copy +============================================= + + $ hg uncommit -i< y + > y + > n + > EOF + diff --git a/a b/a + 2 hunks, 5 lines changed + examine changes to 'a'? [Ynesfdaq?] y + + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + discard change 1/2 to 'a'? [Ynesfdaq?] y + + @@ -1,5 +4,7 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + discard change 2/2 to 'a'? [Ynesfdaq?] n + + + $ hg exp + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 62d907d0c4fa13b4b8bfeed05f13751035daf963 + # Parent 7733902a8d94c789ca81d866bea1893d79442db6 + another one + + diff -r 7733902a8d94 -r 62d907d0c4fa a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,5 +1,7 @@ + 1 + 2 + 3 + +foo + +bar + 4 + 5 + + $ hg diff + diff -r 62d907d0c4fa a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,3 +1,6 @@ + +-2 + +-1 + +0 + 1 + 2 + 3 + @@ -5,3 +8,4 @@ + bar + 4 + 5 + +babar + diff -r 62d907d0c4fa bar + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/bar Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +foo + + $ hg status + M a + A bar + ? foo.orig + +Interactive uncommit with a pattern +----------------------------------- + +(more setup) + + $ hg ci -m 'roaming changes' + $ cat > b << EOF + > a + > b + > c + > d + > e + > f + > h + > EOF + $ hg add b + $ hg ci -m 'add b' + $ echo 'celeste' >> a + $ echo 'i' >> b + $ hg ci -m 'some more changes' + $ hg export + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID be5c67225e80b050867862bbd9f4755c4e9207c5 + # Parent c280a907fddcef2ffe9fadcc2d87f29998e22b2f + some more changes + + diff -r c280a907fddc -r be5c67225e80 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -9,3 +9,4 @@ + 4 + 5 + babar + +celeste + diff -r c280a907fddc -r be5c67225e80 b + --- a/b Thu Jan 01 00:00:00 1970 +0000 + +++ b/b Thu Jan 01 00:00:00 1970 +0000 + @@ -5,3 +5,4 @@ + e + f + h + +i + + $ hg uncommit -i a << DONE + > y + > y + > DONE + diff --git a/a b/a + 1 hunks, 1 lines changed + examine changes to 'a'? [Ynesfdaq?] y + + @@ -9,3 +9,4 @@ + 4 + 5 + babar + +celeste + discard this change to 'a'? [Ynesfdaq?] y + + $ hg status + M a + ? foo.orig + $ hg diff + diff -r c701d7c8d18b a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -9,3 +9,4 @@ + 4 + 5 + babar + +celeste + $ hg export + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID c701d7c8d18be55a92688f4458c26bd74fb1f525 + # Parent c280a907fddcef2ffe9fadcc2d87f29998e22b2f + some more changes + + diff -r c280a907fddc -r c701d7c8d18b b + --- a/b Thu Jan 01 00:00:00 1970 +0000 + +++ b/b Thu Jan 01 00:00:00 1970 +0000 + @@ -5,3 +5,4 @@ + e + f + h + +i + +(reset) + + $ cat << EOF > a + > -3 + > -2 + > -1 + > 0 + > 1 + > 2 + > 3 + > foo + > bar + > 4 + > 5 + > babar + > celeste + > EOF + $ hg amend + +Same but do not select some change in 'a' + + $ hg uncommit -i a << DONE + > y + > y + > n + > DONE + diff --git a/a b/a + 2 hunks, 2 lines changed + examine changes to 'a'? [Ynesfdaq?] y + + @@ -1,3 +1,4 @@ + +-3 + -2 + -1 + 0 + discard change 1/2 to 'a'? [Ynesfdaq?] y + + @@ -9,3 +10,4 @@ + 4 + 5 + babar + +celeste + discard change 2/2 to 'a'? [Ynesfdaq?] n + + $ hg status + M a + ? foo.orig + + $ hg diff + diff -r 28d5de12b225 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -1,3 +1,4 @@ + +-3 + -2 + -1 + 0 + + $ hg export + # HG changeset patch + # User test + # Date 0 0 + # Thu Jan 01 00:00:00 1970 +0000 + # Node ID 28d5de12b225d1e0951110cced8d8994227be026 + # Parent c280a907fddcef2ffe9fadcc2d87f29998e22b2f + some more changes + + diff -r c280a907fddc -r 28d5de12b225 a + --- a/a Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -9,3 +9,4 @@ + 4 + 5 + babar + +celeste + diff -r c280a907fddc -r 28d5de12b225 b + --- a/b Thu Jan 01 00:00:00 1970 +0000 + +++ b/b Thu Jan 01 00:00:00 1970 +0000 + @@ -5,3 +5,4 @@ + e + f + h + +i + + $ cat b + a + b + c + d + e + f + h + i diff --git a/tests/test-uncommit.t b/tests/test-uncommit.t --- a/tests/test-uncommit.t +++ b/tests/test-uncommit.t @@ -34,6 +34,7 @@ options ([+] can be repeated): + -i --interactive interactive mode to uncommit --keep allow an empty commit after uncommiting -I --include PATTERN [+] include names matching the given patterns -X --exclude PATTERN [+] exclude names matching the given patterns