diff --git a/hgext3rd/undo.py b/hgext3rd/undo.py --- a/hgext3rd/undo.py +++ b/hgext3rd/undo.py @@ -334,10 +334,11 @@ # Undo: @command('undo', [ - ('n', 'index', 1, _("how many steps to undo back")), - ('f', 'force', False, _("undo across a gap in memory (ADVANCED)")), ('a', 'absolute', False, _("absolute based on command index instead of " "relative undo")), + ('f', 'force', False, _("undo across a gap in memory (ADVANCED)")), + ('k', 'keep', False, _("keep working copy changes")), + ('n', 'index', 1, _("how many steps to undo back")), ]) def undo(ui, repo, *args, **opts): """ perform an undo @@ -378,9 +379,22 @@ repo or another command breaks something. Undoing across this "gap" can be forced, but isn't advised unless its known how the before and after states are connected. Undoing up to a gap is safe. + + Use keep to maintain working copy changes. With keep, undo mimics hg + unamend and hg uncommit. Specifcally, files that exsist currently that + don't exsist at the repo state we are undoing to will remain in your + workingcopy but not in your changeset. """ + try: + extensions.find('inhibit') + except KeyError: + hint = _("please add inhibit to the list of enabled extensions") + e = _("undo requires inhibit extension to be enabled") + raise error.Abort(e, hint=hint) + reverseindex = opts.get("index") relativeundo = not opts.get("absolute") + keep = opts.get("keep") if repo is not None: with repo.wlock(), repo.lock(), repo.transaction("undo"): @@ -392,7 +406,7 @@ if not (opts.get("force") or _gapcheck(repo, reverseindex)): raise error.Abort(_("attempted risky undo across" " missing history")) - _undoto(ui, repo, reverseindex) + _undoto(ui, repo, reverseindex, keep=keep) # store undo data # for absolute undos, think of this as a reset # for relative undos, think of this as an update @@ -411,6 +425,13 @@ and you can use any number of undos/redos up to the current state or back to when the undo extension was first active. """ + try: + extensions.find('inhibit') + except KeyError: + hint = _("please add inhibit to the list of enabled extensions") + e = _("redo requires inhibit extension to be enabled") + raise error.Abort(e, hint=hint) + reverseindex = -1 * abs(opts.get("index")) reverseindex = _computerelative(repo, reverseindex) @@ -422,7 +443,7 @@ _undoto(ui, repo, reverseindex) _logundoredoindex(repo, reverseindex) -def _undoto(ui, repo, reverseindex): +def _undoto(ui, repo, reverseindex, keep=False): # undo to specific reverseindex try: nodedict = _readindex(repo, reverseindex) @@ -446,8 +467,33 @@ # working copy parent workingcopyparent = _readnode(repo, "workingparent.i", nodedict["workingparent"]) - revealcommits(ui, repo, workingcopyparent) - commands.update(ui, repo, rev=workingcopyparent) + if not keep: + revealcommits(ui, repo, workingcopyparent) + commands.update(ui, repo, rev=workingcopyparent) + else: + # keeps working copy files + curctx = repo['.'] + precnode = bin(workingcopyparent) + precctx = repo[precnode] + + changedfiles = [] + wctx = repo[None] + wctxmanifest = wctx.manifest() + precctxmanifest = precctx.manifest() + dirstate = repo.dirstate + diff = precctxmanifest.diff(wctxmanifest) + changedfiles.extend(diff.iterkeys()) + + with dirstate.parentchange(): + dirstate.rebuild(precnode, precctxmanifest, 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) + obsolete.createmarkers(repo, [(curctx, (precctx,))]) # visible changesets addedrevs = revsetlang.formatspec('olddraft(0) - olddraft(%d)', diff --git a/tests/test-undo.t b/tests/test-undo.t --- a/tests/test-undo.t +++ b/tests/test-undo.t @@ -276,3 +276,13 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg undo -a 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +hg undo --keep tests + $ touch kfl1 && hg add kfl1 + $ hg st + A kfl1 + $ hg commit --amend + $ hg st + $ hg undo --keep + $ hg st + A kfl1