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) 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/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