Hi Phillip, On Mon, 18 Jun 2018, Phillip Wood wrote: > On 17/06/18 20:28, Johannes Schindelin wrote: > > > > On Sun, 17 Jun 2018, Phillip Wood wrote: > > > >> On 17/06/18 06:37, Elijah Newren wrote: > >>> Ever since commit 18633e1a22 ("rebase -i: use the rebase--helper > >>> builtin", 2017-02-09), when a commit marked as 'reword' in an > >>> interactive rebase has conflicts and fails to apply, when the rebase > >>> is resumed that commit will be squashed into its parent with its > >>> commit message taken. > >>> > >>> The issue can be understood better by looking at commit 56dc3ab04b > >>> ("sequencer (rebase -i): implement the 'edit' command", 2017-01-02), > >>> which introduced error_with_patch() for the edit command. For the > >>> edit command, it needs to stop the rebase whether or not the patch > >>> applies cleanly. If the patch does apply cleanly, then when it > >>> resumes it knows it needs to amend all changes into the previous > >>> commit. If it does not apply cleanly, then the changes should not > >>> be amended. Thus, it passes !res (success of applying the 'edit' > >>> commit) to error_with_patch() for the to_amend flag. > >>> > >>> The problematic line of code actually came from commit 04efc8b57c > >>> ("sequencer (rebase -i): implement the 'reword' command", 2017-01-02). > >>> Note that to get to this point in the code: > >>> * !!res (i.e. patch application failed) > >>> * item->command < TODO_SQUASH > >>> * item->command != TODO_EDIT > >>> * !is_fixup(item->command) [i.e. not squash or fixup] > >>> So that means this can only be a failed patch application that is > >>> either a pick, revert, or reword. For any of those cases we want a > >>> new commit, so we should not set the to_amend flag. > >> > >> Unfortunately I'm not sure it's that simple. Looking and do_pick() > >> sometimes reword amends HEAD and sometimes it does not. In the > >> "normal" case then the commit is picked and committed with '--edit'. > >> However when fast-forwarding the code fast forwards to the commit to > >> be reworded and then amends it. If the root commit is being reworded > >> it takes the same code path. While these cases cannot fail with > >> conflicts, it is possible for the user to cancel the commit or for > >> them to fail due to collisions with untracked files. > >> > >> If I remember correctly the shell version always picks the commit and > >> then calls 'git commit --amend' afterwards which is less efficient > >> but consistent. > >> > >> I'm afraid I don't have a simple solution for fixing this, as > >> currently pick_commits() does not know if the commit was called with > >> AMEND_MSG, I guess that means adding some kind of flag for do_pick() > >> to set. > > > > Oh, you're right, the fast-forwarding path would pose a problem. I > > think there is an easy way to resolve this, though: in the case that > > we do want to amend the to-be-reworded commit, we simply have to see > > whether HEAD points to the very same commit mentioned in the `reword` > > command: > > That's clever, I think to get it to work for rewording the root commit, > it will need to do something like comparing HEAD to squash-onto as well. ... because squash-onto is a fresh, empty root commit (to be "amended" when a non-root commit is to be picked as a new root commit). Good point. > > -- snip -- > > diff --git a/sequencer.c b/sequencer.c > > index 2dad7041960..99d33d4e063 100644 > > --- a/sequencer.c > > +++ b/sequencer.c > > @@ -3691,10 +3691,22 @@ static int pick_commits(struct todo_list > > *todo_list, struct replay_opts *opts) > > intend_to_amend(); > > return error_failed_squash(item->commit, opts, > > item->arg_len, item->arg); > > - } else if (res && is_rebase_i(opts) && item->commit) > > + } else if (res && is_rebase_i(opts) && item->commit) { > > + int to_amend = 0; > > + > > + if (item->command == TODO_REWORD) { > > + struct object_id head; > > + > > + if (!get_oid("HEAD", &head) && > > + !oidcmp(&item->commit->object.oid, > > + &head)) > > + to_amend = 1; This would now become if (!get_oid("HEAD", &head) && (!oidcmp(&item->commit->object.oid, &head) || (opts->have_squash_onto && !oidcmp(&opts->squash_onto, &head)))) to_amend = 1; This is awfully indented, so a better idea would probably be to avoid the extra block just to declare `head`: - } else if (res && is_rebase_i(opts) && item->commit) + } else if (res && is_rebase_i(opts) && item->commit) { + int to_amend = 0; + struct object_id oid; + + /* + * If we fast-forwarded already, or if we + * are about to create a new root commit, + * we definitely want to amend, otherwise + * we do not. + */ + if (item->command == TODO_REWORD && + !get_oid("HEAD", &oid) && + (!oidcmp(&item->commit->object.oid, &oid) || + (opts->have_squash_onto && + !oidcmp(&opts->squash_onto, &head)))) + to_amend = 1; + return res | error_with_patch(item->commit, item->arg, item->arg_len, opts, res, - item->command == TODO_REWORD); + to_amend); + } } else if (item->command == TODO_EXEC) { What do you think? And: could you perchance add a regression test for a failing pick onto squash-onto (I am desperately in need of sleeping, otherwise I would do it myself)? Something that executes `reset [new root]` and then `pick abcdef` where abcdef would overwrite an untracked file should trigger this relatively easily. Thanks, Dscho