changeset 42616:5162753c4c14

unshelve: add interactive mode Until now, there is no way to `unshelve` selected changes only from the stored shelve as given in issue6162. This patch makes `unshelve` perform with certain changes only by adding an interactive mode. Differential Revision: https://phab.mercurial-scm.org/D6596
author Navaneeth Suresh <navaneeths1998@gmail.com>
date Tue, 02 Jul 2019 18:02:12 +0530
parents 56132ebd14c6
children 7629eb87e7f2
files mercurial/cmdutil.py mercurial/commands.py mercurial/shelve.py tests/test-completion.t tests/test-shelve.t
diffstat 5 files changed, 300 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/cmdutil.py	Sun Jul 07 10:54:41 2019 -0400
+++ b/mercurial/cmdutil.py	Tue Jul 02 18:02:12 2019 +0530
@@ -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:
--- a/mercurial/commands.py	Sun Jul 07 10:54:41 2019 -0400
+++ b/mercurial/commands.py	Tue Jul 02 18:02:12 2019 +0530
@@ -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
--- a/mercurial/shelve.py	Sun Jul 07 10:54:41 2019 -0400
+++ b/mercurial/shelve.py	Tue Jul 02 18:02:12 2019 +0530
@@ -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()
--- a/tests/test-completion.t	Sun Jul 07 10:54:41 2019 -0400
+++ b/tests/test-completion.t	Tue Jul 02 18:02:12 2019 +0530
@@ -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
--- a/tests/test-shelve.t	Sun Jul 07 10:54:41 2019 -0400
+++ b/tests/test-shelve.t	Tue Jul 02 18:02:12 2019 +0530
@@ -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 <<EOF
+  > 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 <<EOF
+  > 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 <<EOF
+  > 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 <<EOF
+  > 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 <<EOF
+  > A
+  > B
+  > C
+  > EOF
+  $ cat > bar2 <<EOF
+  > 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 <<EOF
+  > 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