diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -266,8 +266,8 @@ In the end we'll record interesting changes, and everything else will be left in place, so the user can continue working. """ - - checkunfinished(repo, commit=True) + if not opts.get('interactive-unshelve'): + checkunfinished(repo, commit=True) wctx = repo[None] merge = len(wctx.parents()) > 1 if merge: diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -6168,6 +6168,8 @@ _('abort an incomplete unshelve operation')), ('c', 'continue', None, _('continue an incomplete unshelve operation')), + ('i', 'interactive', None, + _('use interactive mode')), ('k', 'keep', None, _('keep shelve after unshelving')), ('n', 'name', '', @@ -6175,7 +6177,7 @@ ('t', 'tool', '', _('specify merge tool')), ('', 'date', '', _('set date for temporary commits (DEPRECATED)'), _('DATE'))], - _('hg unshelve [[-n] SHELVED]'), + _('hg unshelve [OPTION]... [FILE]... [-n SHELVED]'), helpcategory=command.CATEGORY_WORKING_DIRECTORY) def unshelve(ui, repo, *shelved, **opts): """restore a shelved change to the working directory diff --git a/mercurial/shelve.py b/mercurial/shelve.py --- a/mercurial/shelve.py +++ b/mercurial/shelve.py @@ -694,10 +694,11 @@ if shfile.exists(): shfile.movetobackup() cleanupoldbackups(repo) -def unshelvecontinue(ui, repo, state, opts): +def unshelvecontinue(ui, repo, state, opts, basename=None): """subcommand to continue an in-progress unshelve""" # We're finishing off a merge. First parent is our original # parent, second is the temporary "fake" commit we're unshelving. + interactive = opts.get('interactive') with repo.lock(): checkparents(repo, state) ms = merge.mergestate.read(repo) @@ -720,10 +721,15 @@ with repo.ui.configoverride(overrides, 'unshelve'): with repo.dirstate.parentchange(): repo.setparents(state.parents[0], nodemod.nullid) - newnode = repo.commit(text=shelvectx.description(), - extra=shelvectx.extra(), - user=shelvectx.user(), - date=shelvectx.date()) + if not interactive: + ispartialunshelve = False + newnode = repo.commit(text=shelvectx.description(), + extra=shelvectx.extra(), + user=shelvectx.user(), + date=shelvectx.date()) + else: + newnode, ispartialunshelve = _dounshelveinteractive(ui, + repo, shelvectx, basename, opts) if newnode is None: # If it ended up being a no-op commit, then the normal @@ -743,12 +749,13 @@ mergefiles(ui, repo, state.wctx, shelvectx) restorebranch(ui, repo, state.branchtorestore) - if not phases.supportinternal(repo): - repair.strip(ui, repo, state.nodestoremove, backup=False, - topic='shelve') + if not ispartialunshelve: + if not phases.supportinternal(repo): + repair.strip(ui, repo, state.nodestoremove, backup=False, + topic='shelve') + shelvedstate.clear(repo) + unshelvecleanup(ui, repo, state.name, opts) _restoreactivebookmark(repo, state.activebookmark) - shelvedstate.clear(repo) - unshelvecleanup(ui, repo, state.name, opts) ui.status(_("unshelve of '%s' complete\n") % state.name) def hgcontinueunshelve(ui, repo): @@ -797,14 +804,40 @@ return repo, shelvectx +def _dounshelveinteractive(ui, repo, shelvectx, basename, opts): + """The user might want to unshelve certain changes only from the stored + shelve. So, we would create two commits. One with requested changes to + unshelve at that time and the latter is shelved for future. + """ + opts['message'] = shelvectx.description() + opts['interactive-unshelve'] = True + pats = [] + commitfunc = getcommitfunc(shelvectx.extra(), interactive=True, + editor=True) + newnode = cmdutil.dorecord(ui, repo, commitfunc, None, False, + cmdutil.recordfilter, *pats, + **pycompat.strkwargs(opts)) + snode = repo.commit(text=shelvectx.description(), + extra=shelvectx.extra(), + user=shelvectx.user(), + date=shelvectx.date()) + m = scmutil.matchfiles(repo, repo[snode].files()) + if snode: + _shelvecreatedcommit(repo, snode, basename, m) + + return newnode, bool(snode) + def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx, tmpwctx, shelvectx, branchtorestore, activebookmark): """Rebase restored commit from its original location to a destination""" # If the shelve is not immediately on top of the commit # we'll be merging with, rebase it to be on top. - if tmpwctx.node() == shelvectx.p1().node(): - return shelvectx + interactive = opts.get('interactive') + if tmpwctx.node() == shelvectx.p1().node() and not interactive: + # We won't skip on interactive mode because, the user might want to + # unshelve certain changes only. + return shelvectx, False overrides = { ('ui', 'forcemerge'): opts.get('tool', ''), @@ -828,10 +861,15 @@ with repo.dirstate.parentchange(): repo.setparents(tmpwctx.node(), nodemod.nullid) - newnode = repo.commit(text=shelvectx.description(), - extra=shelvectx.extra(), - user=shelvectx.user(), - date=shelvectx.date()) + if not interactive: + ispartialunshelve = False + newnode = repo.commit(text=shelvectx.description(), + extra=shelvectx.extra(), + user=shelvectx.user(), + date=shelvectx.date()) + else: + newnode, ispartialunshelve = _dounshelveinteractive(ui, repo, + shelvectx, basename, opts) if newnode is None: # If it ended up being a no-op commit, then the normal @@ -846,7 +884,7 @@ shelvectx = repo[newnode] hg.updaterepo(repo, tmpwctx.node(), False) - return shelvectx + return shelvectx, ispartialunshelve def _forgetunknownfiles(repo, shelvectx, addedbefore): # Forget any files that were unknown before the shelve, unknown before @@ -883,13 +921,14 @@ opts = pycompat.byteskwargs(opts) abortf = opts.get('abort') continuef = opts.get('continue') + interactive = opts.get('interactive') if not abortf and not continuef: cmdutil.checkunfinished(repo) shelved = list(shelved) if opts.get("name"): shelved.append(opts["name"]) - if abortf or continuef: + if abortf or continuef and not interactive: if abortf and continuef: raise error.Abort(_('cannot use both abort and continue')) if shelved: @@ -911,8 +950,11 @@ raise error.Abort(_('no shelved changes to apply!')) basename = util.split(shelved[0][1])[1] ui.status(_("unshelving change '%s'\n") % basename) - else: + elif shelved: basename = shelved[0] + if continuef and interactive: + state = _loadshelvedstate(ui, repo, opts) + return unshelvecontinue(ui, repo, state, opts, basename) if not shelvedfile(repo, basename, patchextension).exists(): raise error.Abort(_("shelved change '%s' not found") % basename) @@ -941,19 +983,19 @@ if shelvectx.branch() != shelvectx.p1().branch(): branchtorestore = shelvectx.branch() - shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, - basename, pctx, tmpwctx, - shelvectx, branchtorestore, - activebookmark) + shelvectx, ispartialunshelve = _rebaserestoredcommit(ui, repo, opts, + tr, oldtiprev, basename, pctx, tmpwctx, shelvectx, + branchtorestore, activebookmark) overrides = {('ui', 'forcemerge'): opts.get('tool', '')} with ui.configoverride(overrides, 'unshelve'): mergefiles(ui, repo, pctx, shelvectx) restorebranch(ui, repo, branchtorestore) - _forgetunknownfiles(repo, shelvectx, addedbefore) + if not ispartialunshelve: + _forgetunknownfiles(repo, shelvectx, addedbefore) - shelvedstate.clear(repo) - _finishunshelve(repo, oldtiprev, tr, activebookmark) - unshelvecleanup(ui, repo, basename, opts) + shelvedstate.clear(repo) + _finishunshelve(repo, oldtiprev, tr, activebookmark) + unshelvecleanup(ui, repo, basename, opts) finally: if tr: tr.release() diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -354,7 +354,7 @@ tags: template tip: patch, git, style, template unbundle: update - unshelve: abort, continue, keep, name, tool, date + unshelve: abort, continue, interactive, keep, name, tool, date update: clean, check, merge, date, rev, tool verify: full version: template diff --git a/tests/test-shelve.t b/tests/test-shelve.t --- a/tests/test-shelve.t +++ b/tests/test-shelve.t @@ -1158,3 +1158,228 @@ [255] $ cd .. + +-- test for interactive mode on unshelve + + $ hg init a + $ cd a + $ echo > b + $ hg ci -Am b + adding b + $ echo > c + $ echo > d + $ hg add . + adding c + adding d + $ hg shelve + shelved as default + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ echo > e + $ hg add e + $ hg ci -m e + $ hg shelve --patch + default (1s ago) changes to: b + + diff --git a/c b/c + new file mode 100644 + --- /dev/null + +++ b/c + @@ -0,0 +1,1 @@ + + + diff --git a/d b/d + new file mode 100644 + --- /dev/null + +++ b/d + @@ -0,0 +1,1 @@ + + + $ hg unshelve -i < y + > y + > y + > n + > EOF + unshelving change 'default' + rebasing shelved changes + diff --git a/c b/c + new file mode 100644 + examine changes to 'c'? + (enter ? for help) [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + + + record change 1/2 to 'c'? + (enter ? for help) [Ynesfdaq?] y + + diff --git a/d b/d + new file mode 100644 + examine changes to 'd'? + (enter ? for help) [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + + + record change 2/2 to 'd'? + (enter ? for help) [Ynesfdaq?] n + + $ ls + b + c + e +-- shelve should not contain `c` now + $ hg shelve --patch + default (1s ago) changes to: b + + diff --git a/d b/d + new file mode 100644 + --- /dev/null + +++ b/d + @@ -0,0 +1,1 @@ + + + $ hg unshelve -i < y + > y + > EOF + unshelving change 'default' + rebasing shelved changes + diff --git a/d b/d + new file mode 100644 + examine changes to 'd'? + (enter ? for help) [Ynesfdaq?] y + + @@ -0,0 +1,1 @@ + + + record this change to 'd'? + (enter ? for help) [Ynesfdaq?] y + + $ ls + b + c + d + e + $ hg shelve --list + +-- now, unshelve selected changes from a file + + $ echo B > foo + $ hg add foo + $ hg ci -m 'add B to foo' + $ cat > foo < A + > B + > C + > EOF + $ hg shelve + shelved as default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat foo + B + $ hg unshelve -i < y + > y + > n + > EOF + unshelving change 'default' + rebasing shelved changes + diff --git a/foo b/foo + 2 hunks, 2 lines changed + examine changes to 'foo'? + (enter ? for help) [Ynesfdaq?] y + + @@ -1,1 +1,2 @@ + +A + B + record change 1/2 to 'foo'? + (enter ? for help) [Ynesfdaq?] y + + @@ -1,1 +2,2 @@ + B + +C + record change 2/2 to 'foo'? + (enter ? for help) [Ynesfdaq?] n + + $ cat foo + A + B + $ hg shelve --patch + default (1s ago) changes to: add B to foo + + diff --git a/foo b/foo + --- a/foo + +++ b/foo + @@ -1,2 +1,3 @@ + A + B + +C + +-- unshelve interactive on conflicts + + $ echo A >> bar1 + $ echo A >> bar2 + $ hg add bar1 bar2 + $ hg ci -m 'add A to bars' + $ echo B >> bar1 + $ echo B >> bar2 + $ hg shelve + shelved as default-01 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo C >> bar1 + $ echo C >> bar2 + $ hg ci -m 'add C to bars' + $ hg unshelve -i + unshelving change 'default-01' + rebasing shelved changes + merging bar1 + merging bar2 + warning: conflicts while merging bar1! (edit, then use 'hg resolve --mark') + warning: conflicts while merging bar2! (edit, then use 'hg resolve --mark') + unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue') + [1] + + $ cat > bar1 < A + > B + > C + > EOF + $ cat > bar2 < A + > B + > C + > EOF + $ hg resolve -m bar1 bar2 + (no more unresolved files) + continue: hg unshelve --continue + $ cat bar1 + A + B + C + $ hg unshelve --continue -i < y + > y + > y + > y + > EOF + unshelving change 'default-01' + diff --git a/bar1 b/bar1 + 1 hunks, 1 lines changed + examine changes to 'bar1'? + (enter ? for help) [Ynesfdaq?] y + + @@ -1,2 +1,3 @@ + A + +B + C + record change 1/2 to 'bar1'? + (enter ? for help) [Ynesfdaq?] y + + diff --git a/bar2 b/bar2 + 1 hunks, 1 lines changed + examine changes to 'bar2'? + (enter ? for help) [Ynesfdaq?] y + + @@ -1,2 +1,3 @@ + A + +B + C + record change 2/2 to 'bar2'? + (enter ? for help) [Ynesfdaq?] y + + unshelve of 'default-01' complete