Patchwork D11037: amend: make `hg amend -r` rebase temporary commit onto target commit

login
register
mail settings
Submitter phabricator
Date July 9, 2021, 8:28 p.m.
Message ID <differential-rev-PHID-DREV-hgewh3buxzngmi2mgkar-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/49351/
State New
Headers show

Comments

phabricator - July 9, 2021, 8:28 p.m.
martinvonz created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  hgext/amend.py
  mercurial/state.py
  tests/test-amend-rev.t

CHANGE DETAILS




To: martinvonz, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/tests/test-amend-rev.t b/tests/test-amend-rev.t
--- a/tests/test-amend-rev.t
+++ b/tests/test-amend-rev.t
@@ -2,6 +2,7 @@ 
   $ cat << EOF >> $HGRCPATH
   > [extensions]
   > amend=
+  > rebase=
   > debugdrawdag=$TESTDIR/drawdag.py
   > [experimental]
   > evolution.createmarkers=True
@@ -31,9 +32,15 @@ 
   nothing changed
   [1]
 
+Fails if rebase is not enabled
+
+  $ echo a3 > a
+  $ hg amend -r '.^' --config extensions.rebase=!
+  config error: amend --rev requires the rebase extension to be enabled
+  [30]
+
 Fails if evolution is not enabled
 
-  $ echo a3 > a
   $ hg amend -r 'desc("modify a")' --config experimental.evolution.createmarkers=False
   abort: --rev requires evolution.createmarkers to be enabled
   [20]
@@ -42,10 +49,10 @@ 
 
   $ hg amend -r 'desc("modify a")'
   $ hg log -G -T '{rev} {desc}'
-  @  3 temporary commit for "amend --rev" (known-bad-output !)
+  @  4 temporary commit for "amend --rev" (known-bad-output !)
   | (known-bad-output !)
-  o  2 add b
-  |
+  | o  2 add b (known-bad-output !)
+  |/ (known-bad-output !)
   o  1 modify a
   |
   o  0 add a
@@ -56,3 +63,88 @@ 
   a3 (missing-correct-output !)
 The working copy is clean and there is no unfinished operation
   $ hg st -v
+
+
+Can abort or continue after conflict while rebasing temporary commit
+--------------------------------------------------------------------------------
+
+Common setup for abort and continue
+  $ cd "$TESTTMP"
+  $ hg init conflict-rebasing-temp-commit
+  $ cd conflict-rebasing-temp-commit
+  $ echo a > a
+  $ hg ci -Aqm 'add a'
+  $ echo a2 > a
+  $ hg ci -m 'modify a'
+  $ echo a3 > a
+  $ hg log -G -T '{rev} {desc}'
+  @  1 modify a
+  |
+  o  0 add a
+  
+  $ hg amend -r 'desc("add a")'
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see 'hg resolve', then 'hg amend --continue')
+  [240]
+  $ hg st -v
+  M a
+  ? a.orig
+  # The repository is in an unfinished *amend* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     a
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:    hg amend --continue
+  # To abort:       hg amend --abort
+  
+
+Make a copy of the repo and working copy to test continuing
+  $ cp -R . ../conflict-rebasing-temp-commit-continue
+
+Can abort
+  $ hg abort
+  rebase aborted
+  saved backup bundle to $TESTTMP/conflict-rebasing-temp-commit/.hg/strip-backup/5d5724f8921e-27ec44bb-backup.hg
+The log output looks like it did before we started
+  $ hg log -G -T '{rev} {desc}'
+  @  1 modify a
+  |
+  o  0 add a
+  
+The working copy has the change it had before we started
+  $ hg diff
+  diff -r 41c4ea50d4cf a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -a2
+  +a3
+There is no unfinished operation
+  $ hg st -v
+  M a
+  ? a.orig
+
+Can continue
+  $ cd ../conflict-rebasing-temp-commit-continue
+  $ echo resolved > a
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg amend --continue
+  $ hg continue
+  $ hg log -G -T '{rev} {desc}'
+  @  3 temporary commit for "amend --rev" (known-bad-output !)
+  | (known-bad-output !)
+  | o  1 modify a (known-bad-output !)
+  |/ (known-bad-output !)
+  o  0 add a
+  
+Target commit has new content
+  $ hg cat -r 'desc("add a")' a
+  a (known-bad-output !)
+  resolved (missing-correct-output !)
+The working copy is clean and there is no unfinished operation
+  $ hg st -v
+  ? a.orig
diff --git a/mercurial/state.py b/mercurial/state.py
--- a/mercurial/state.py
+++ b/mercurial/state.py
@@ -373,6 +373,22 @@ 
     return c.continuefunc(ui, repo)
 
 
+def abortchild(ui, repo, opname, childopname):
+    """Checks that childopname is in progress, and aborts it."""
+
+    p, c = _getparentandchild(opname, childopname)
+    if not ischildunfinished(repo, opname, childopname):
+        raise error.ProgrammingError(
+            _(b'child op %s of parent %s is not unfinished')
+            % (childopname, opname)
+        )
+    if not c.abortfunc:
+        raise error.ProgrammingError(
+            _(b'op %s has no abort function') % childopname
+        )
+    return c.abortfunc(ui, repo)
+
+
 addunfinished(
     b'update',
     fname=b'updatestate',
diff --git a/hgext/amend.py b/hgext/amend.py
--- a/hgext/amend.py
+++ b/hgext/amend.py
@@ -17,10 +17,12 @@ 
     cmdutil,
     commands,
     error,
+    extensions,
     merge,
     obsolete,
     registrar,
     repair,
+    revsetlang,
     rewriteutil,
     scmutil,
     state as statemod,
@@ -151,6 +153,7 @@ 
             _(b'--rev requires evolution.createmarkers to be enabled')
         )
 
+    rebase = _find_rebase()
     state = {}
     state_store = statemod.cmdstate(repo, b'amend-state')
 
@@ -172,6 +175,7 @@ 
             temp_ctx = repo[b'tip']
             state[b'target_node'] = target_ctx.node()
             state[b'temp_node'] = temp_ctx.node()
+            _do_continue_amend_rev(ui, repo, state, rebase)
         except error.InterventionRequired:
             raise
         except Exception:
@@ -179,8 +183,56 @@ 
             raise
 
 
+def _find_rebase():
+    try:
+        return extensions.find(b'rebase')
+    except KeyError:
+        raise error.ConfigError(
+            b'amend --rev requires the rebase extension to be enabled'
+        )
+
+
 def _continue_amend_rev(ui, repo):
-    raise error.Abort(_(b'--continue is not yet implemented'))
+    rebase = _find_rebase()
+
+    state_store = statemod.cmdstate(repo, b'amend-state')
+    state = state_store.read()
+
+    with repo.wlock(), repo.lock(), util.acceptintervention(
+        repo.transaction(b'amend')
+    ), state_store.save_on_conflicts(1, state):
+        _do_continue_amend_rev(ui, repo, state, rebase)
+
+
+def _do_continue_amend_rev(ui, repo, state, rebase):
+    unfi = repo.unfiltered()
+    target_ctx = unfi[state[b'target_node']]
+    temp_ctx = unfi[state[b'temp_node']]
+
+    _rebase_temp_node(ui, repo, state, rebase, temp_ctx, target_ctx)
+
+
+def _rebase_temp_node(ui, repo, state, rebase, temp_ctx, target_ctx):
+    if b'rebased_temp_node' in state:
+        return repo.unfiltered()[state[b'rebased_temp_node']]
+    elif statemod.ischildunfinished(repo, b'amend', b'rebase'):
+        with ui.silent(), statemod.delegating(repo, b'amend', b'rebase'):
+            ret = statemod.continuechild(ui, repo, b'amend', b'rebase')
+    elif temp_ctx.p1() != target_ctx:
+        with ui.silent(), statemod.delegating(repo, b'amend', b'rebase'):
+            ret = rebase.rebase(
+                ui,
+                repo,
+                rev=[revsetlang.formatspec(b'%d', temp_ctx.rev())],
+                dest=revsetlang.formatspec(b'%d', target_ctx.rev()),
+            )
+
+    if ret:
+        raise error.Abort(_(b'failed to rebase temporary commit'))
+
+    rebased_temp_ctx = repo[b'tip']
+    state[b'rebased_temp_node'] = rebased_temp_ctx.node()
+    return rebased_temp_ctx
 
 
 def _abort_amend_rev(ui, repo):
@@ -193,20 +245,35 @@ 
 
 def _do_abort_amend_rev(ui, repo, state):
     unfi = repo.unfiltered()
+    if statemod.ischildunfinished(repo, b'amend', b'rebase'):
+        with ui.silent():
+            statemod.abortchild(ui, repo, b'amend', b'rebase')
+    to_strip = []
     temp_node = state.get(b'temp_node')
     if temp_node and temp_node in unfi:
+        to_strip.append(temp_node)
         temp_ctx = unfi[temp_node]
         merge.clean_update(temp_ctx)
         with repo.dirstate.parentchange():
-            scmutil.movedirstate(repo, temp_ctx.p1())
-        repair.delayedstrip(ui, repo, [temp_node])
+            scmutil.movedirstate(unfi, temp_ctx.p1())
+    rebased_temp_node = state.get(b'rebased_temp_node')
+    to_strip.append(rebased_temp_node)
+    to_strip = [node for node in to_strip if node and node in unfi]
+    if to_strip:
+        repair.delayedstrip(ui, unfi, to_strip)
 
 
 def extsetup(ui):
-    statemod.addunfinished(
-        b'amend',
-        fname=b'amend-state',
-        allowcommit=False,
-        abortfunc=_abort_amend_rev,
-        continuefunc=_continue_amend_rev,
-    )
+    def _after_rebase_loaded(loaded):
+        if loaded:
+            statemod.addunfinished(
+                b'amend',
+                fname=b'amend-state',
+                allowcommit=False,
+                childopnames=[b'rebase'],
+                abortfunc=_abort_amend_rev,
+                continueflag=True,
+                continuefunc=_continue_amend_rev,
+            )
+
+    extensions.afterloaded(b'rebase', _after_rebase_loaded)