D6479: unshelve: first prototype of restoring unresolved changes

navaneeth.suresh (Navaneeth Suresh) phabricator at mercurial-scm.org
Wed Jun 5 19:05:30 UTC 2019


navaneeth.suresh created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This patch adds an `--unresolved` flag to `unshelve`.
  
  This will get the user back to the old unresolved merge by the following
  steps:
  
  step 1: If the user has committed new changesets after shelving the changes,
  they must update the working directory to one of the merge parents.
  
  step 2: Internally, merge `p1` with `p2` with the merge tool `:fail`.
  This will not update the contents of the files with conflicting changes.
  Instead, it will mark them as unresolved.
  
  step 3: This internal merge will also mark the files which are already
  resolved by the user in the unresolved shelve changeset as unresolved. But,
  we will move the contents of `$HGRCPATH/merge-unresolved/<basename>/`
  to `$HGRCPATH/merge/` so that we can restore the partially resolved states.
  
  step 4: We now have a state in which files marked as resolved might have
  conflicts. But, we will apply the changes in shelve on the top of this so that we
  can get our old unresolved merge again by the usual `unshelve` mechanism.
  The usual rebase step is avoided on unresolved shelve changesets.
  
  `$ hg unshelve --unresolved` will abort when:
  
  1. The working directory is dirty.
  2. If there is an ongoing merge.
  3. If the working directory is not at either p1 or p2.
  
  (p1, p2 are the parents of the unresolved shelve changeset)

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D6479

AFFECTED FILES
  hgext/shelve.py
  tests/test-shelve-unresolved.t

CHANGE DETAILS

diff --git a/tests/test-shelve-unresolved.t b/tests/test-shelve-unresolved.t
--- a/tests/test-shelve-unresolved.t
+++ b/tests/test-shelve-unresolved.t
@@ -1,3 +1,17 @@
+  $ addunresolvedmerge() {
+  >   echo A >> $1
+  >   echo A >> $2
+  >   hg ci -m A
+  >   echo B >> $1
+  >   echo B >> $2
+  >   hg ci -m B
+  >   hg up $3
+  >   echo C >> $1
+  >   echo C >> $2
+  >   hg ci -m C
+  >   hg merge -r $(($3+1))
+  > }
+
 Test shelve with unresolved mergestate
 
   $ cat >> $HGRCPATH <<EOF
@@ -156,3 +170,283 @@
   +=======
   +B
   +>>>>>>> merge rev:    fd9a4049234b - test: B
+
+-- now, fix an urgent bug
+  $ echo fixed >> bug
+  $ ls
+  bar
+  bug
+  file1
+  file1.orig
+  file2
+  file2.orig
+  $ hg add bug
+  $ hg ci -m "fix bug"
+  $ hg log -G
+  @  changeset:   3:a53a9a7475b3
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     fix bug
+  |
+  o  changeset:   2:69004294ad57
+  |  parent:      0:c32ef6121744
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  | o  changeset:   1:fd9a4049234b
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     B
+  |
+  o  changeset:   0:c32ef6121744
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+-- let's get back to the old mergestate
+-- we need to update to one of the merge parents. otherwise, abort.
+  $ hg unshelve --unresolved
+  unshelving change 'default'
+  abort: dirstate is not on either of the merge parents.
+  use hg update to one of the merge parents.
+  [255]
+  $ hg up 2
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ cat file2
+  A
+  C
+
+-- flag --unshelve is not passed. but, the last shelve is unresolved
+  $ hg unshelve
+  unshelving change 'default'
+  abort: default is an unresolved shelve, use --unresolved to unshelve it
+  [255]
+
+  $ hg unshelve --unresolved
+  unshelving change 'default'
+  $ hg log -G
+  o  changeset:   3:a53a9a7475b3
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     fix bug
+  |
+  @  changeset:   2:69004294ad57
+  |  parent:      0:c32ef6121744
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  | @  changeset:   1:fd9a4049234b
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     B
+  |
+  o  changeset:   0:c32ef6121744
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+  $ cat file1
+  A
+  B
+  C
+  $ hg diff
+  diff -r 69004294ad57 file1
+  --- a/file1	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file1	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,2 +1,3 @@
+   A
+  +B
+   C
+  diff -r 69004294ad57 file2
+  --- a/file2	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file2	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,2 +1,6 @@
+   A
+  +<<<<<<< working copy: 69004294ad57 - test: C
+   C
+  +=======
+  +B
+  +>>>>>>> merge rev:    fd9a4049234b - test: B
+  $ cat file2
+  A
+  <<<<<<< working copy: 69004294ad57 - test: C
+  C
+  =======
+  B
+  >>>>>>> merge rev:    fd9a4049234b - test: B
+  $ hg resolve -l
+  R file1
+  U file2
+
+-- flag --unresolved is passed but the top most shelve is not unresolved
+  $ hg shelve --unresolved
+  shelved as default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo garbage >> bug
+  $ hg st
+  ? bar
+  ? bug
+  ? file1.orig
+  ? file2.orig
+  $ hg add bug
+  $ hg shelve
+  shelved as default-01
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg unshelve --unresolved
+  unshelving change 'default-01'
+  abort: default-01 is not an unresolved shelve
+  
+  [255]
+
+-- now, unshelve default
+  $ hg unshelve -n default --unresolved
+
+-- commit the merge after completing conflict resolution
+  $ cat >> file2 <<EOF
+  > A
+  > B
+  > C
+  > EOF
+  $ hg resolve -m file2
+  (no more unresolved files)
+  $ hg ci -m merge
+  $ hg verify
+  checking changesets
+  checking manifests
+  crosschecking files in changesets and manifests
+  checking files
+  checked 5 changesets with 9 changes to 3 files
+  $ hg log -G
+  @    changeset:   4:745dca2ee1f1
+  |\   tag:         tip
+  | |  parent:      2:69004294ad57
+  | |  parent:      1:fd9a4049234b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge
+  | |
+  | | o  changeset:   3:a53a9a7475b3
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     fix bug
+  | |
+  | o  changeset:   2:69004294ad57
+  | |  parent:      0:c32ef6121744
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     C
+  | |
+  o |  changeset:   1:fd9a4049234b
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     B
+  |
+  o  changeset:   0:c32ef6121744
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+
+-- flag --unsresolved is passed but we don’t have any unresolved shelve
+  $ hg unshelve --unresolved
+  unshelving change 'default-01'
+  abort: default-01 is not an unresolved shelve
+  
+  [255]
+
+-- when working directory is dirty
+  $ addunresolvedmerge file1 file2 5
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  created new head
+  merging file1
+  merging file2
+  warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 2 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+  [1]
+  $ hg shelve --unresolved
+  shelved as default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo dirt >> bar
+  $ hg add bar
+  $ hg unshelve --unresolved
+  unshelving change 'default'
+  abort: uncommitted changes
+  [255]
+
+--- unshelve --unresolved when there is another merge going on
+  $ hg ci -m dirt
+  $ hg up 7
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo foo >> bug
+  $ hg add bug
+  $ hg ci -m foo2
+  created new head
+  $ hg log -G
+  @  changeset:   9:c74a624102ed
+  |  tag:         tip
+  |  parent:      7:974ec4298b79
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo2
+  |
+  | o  changeset:   8:4acf09fb3a59
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     dirt
+  |
+  o  changeset:   7:974ec4298b79
+  |  parent:      5:db68c6c84fe6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C
+  |
+  | o  changeset:   6:e236d497f76b
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     B
+  |
+  o  changeset:   5:db68c6c84fe6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A
+  |
+  o    changeset:   4:745dca2ee1f1
+  |\   parent:      2:69004294ad57
+  | |  parent:      1:fd9a4049234b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge
+  | |
+  | | o  changeset:   3:a53a9a7475b3
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     fix bug
+  | |
+  | o  changeset:   2:69004294ad57
+  | |  parent:      0:c32ef6121744
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     C
+  | |
+  o |  changeset:   1:fd9a4049234b
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     B
+  |
+  o  changeset:   0:c32ef6121744
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+  $ hg merge -r 8
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg unshelve --unresolved
+  abort: cannot unshelve while merging
+  [255]
diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -26,6 +26,7 @@
 import errno
 import itertools
 import os
+import shutil
 import stat
 
 from mercurial.i18n import _
@@ -714,6 +715,29 @@
         ui.status(_('marked working directory as branch %s\n')
                   % branchtorestore)
 
+def restoreunresolvedshelve(repo, ui, basename, pctx, shelvectx):
+    p1, p2 = shelvectx.parents()
+    overrides = {('ui', 'forcemerge'): ':fail'}
+    # Rather than attempting to merge files that were modified on
+    # both branches, `:fail` marks them as unresolved.
+    # This will also mark resolved files in unresolved shelvectx as
+    # unresolved. We'll restore their status and content later.
+    with ui.configoverride(overrides, 'unshelve'):
+        # dirstate can be either p1 or p2.
+        targetctx = p2
+        if pctx.node() == p2.node():
+            targetctx = p1
+        merge.update(repo, targetctx, branchmerge=True, mergeforce=False,
+                     force=True)
+
+    if not os.path.exists(repo.vfs.join('merge-unresolved')):
+        util.makedir(repo.vfs.join('merge-unresolved'), False)
+    shutil.rmtree(repo.vfs.join('merge'))
+    # Replace `merge/` with `merge-unresolved/<basename>/` to
+    # restore the status of resolved files in shelvectx.
+    util.rename(repo.vfs.join('merge-unresolved/%s/' % basename),
+                repo.vfs.join('merge'))
+
 def unshelvecleanup(ui, repo, name, opts):
     """remove related files after an unshelve"""
     if not opts.get('keep'):
@@ -918,7 +942,9 @@
            _('restore shelved change with given name'), _('NAME')),
           ('t', 'tool', '', _('specify merge tool')),
           ('', 'date', '',
-           _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
+           _('set date for temporary commits (DEPRECATED)'), _('DATE')),
+          ('', 'unresolved', None,
+           _('unshelve mergestate with unresolved files'))],
          _('hg unshelve [[-n] SHELVED]'),
          helpcategory=command.CATEGORY_WORKING_DIRECTORY)
 def unshelve(ui, repo, *shelved, **opts):
@@ -964,6 +990,7 @@
     opts = pycompat.byteskwargs(opts)
     abortf = opts.get('abort')
     continuef = opts.get('continue')
+    unresolved = opts.get('unresolved')
     if not abortf and not continuef:
         cmdutil.checkunfinished(repo)
     shelved = list(shelved)
@@ -1019,6 +1046,14 @@
 
     if not shelvedfile(repo, basename, patchextension).exists():
         raise error.Abort(_("shelved change '%s' not found") % basename)
+    if unresolved:
+        cmdutil.bailifchanged(repo)
+        if not os.path.exists(repo.vfs.join('merge-unresolved/%s' % basename)):
+            raise error.Abort(_('%s is not an unresolved shelve\n') %
+                                basename)
+    elif os.path.exists(repo.vfs.join('merge-unresolved/%s' % basename)):
+        raise error.Abort(_('%s is an unresolved shelve,'
+                            ' use --unresolved to unshelve it') % basename)
 
     repo = repo.unfiltered()
     lock = tr = None
@@ -1044,13 +1079,22 @@
         if shelvectx.branch() != shelvectx.p1().branch():
             branchtorestore = shelvectx.branch()
 
-        shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
-                                          basename, pctx, tmpwctx,
-                                          shelvectx, branchtorestore,
-                                          activebookmark)
+        if unresolved:
+            p1, p2 = shelvectx.parents()
+            if pctx.node() not in [p1.node(), p2.node()]:
+                raise error.Abort(_('dirstate is not on either of the merge'
+                                    ' parents.\nuse hg update to one of the'
+                                    ' merge parents.'))
+        else:
+            shelvectx = _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)
+        if unresolved:
+            restoreunresolvedshelve(repo, ui, basename, pctx, shelvectx)
         restorebranch(ui, repo, branchtorestore)
         _forgetunknownfiles(repo, shelvectx, addedbefore)
 



To: navaneeth.suresh, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list