diff mbox series

rebase -i: do not update "done" when rescheduling command

Message ID pull.1492.git.1679237337683.gitgitgadget@gmail.com (mailing list archive)
State New, archived
Headers show
Series rebase -i: do not update "done" when rescheduling command | expand

Commit Message

Phillip Wood March 19, 2023, 2:48 p.m. UTC
From: Phillip Wood <phillip.wood@dunelm.org.uk>

As the sequencer executes todo commands it appends them to
.git/rebase-merge/done. This file is used by "git status" to show the
recently executed commands. Unfortunately when a command is rescheduled
the command preceding it is erroneously appended to the "done" file.
This means that when rebase stops after rescheduling "pick B" the "done"
file contains

	pick A
	pick B
	pick A

instead of

	pick A
	pick B

Fix this by not updating the "done" file when adding a rescheduled
command back into the "todo" file. A couple of the existing tests are
modified to improve their coverage as none of them trigger this bug or
check the "done" file.

Note that the rescheduled command will still be appended to the "done"
file again when it is successfully executed. Arguably it would be better
not to do that but fixing it would be more involved.

Reported-by: Stefan Haller <lists@haller-berlin.de>
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
    rebase -i: do not update "done" when rescheduling command
    
    This fixes the output of "git status" when rebase stops for a
    rescheduled command.
    
    Stefan - thanks for reporting this, hopefully it will be easy enough to
    update lazygit to check the last command in the done file as well as the
    second to last command for older versions of git.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1492%2Fphillipwood%2Frebase-dont-write-done-when-rescheduling-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1492/phillipwood/rebase-dont-write-done-when-rescheduling-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1492

 sequencer.c                   | 15 +++++++--------
 t/t3404-rebase-interactive.sh | 27 +++++++++++++++++----------
 t/t3430-rebase-merges.sh      | 22 ++++++++++++++++------
 3 files changed, 40 insertions(+), 24 deletions(-)


base-commit: 73876f4861cd3d187a4682290ab75c9dccadbc56

Comments

Stefan Haller March 20, 2023, 7:29 a.m. UTC | #1
On 19.03.23 15:48, Phillip Wood via GitGitGadget wrote:
>     Stefan - thanks for reporting this, hopefully it will be easy enough to
>     update lazygit to check the last command in the done file as well as the
>     second to last command for older versions of git.

Thanks for the fix. Yes, it should be easy to adapt lazygit to the new
behavior.

-Stefan
Junio C Hamano March 20, 2023, 5:46 p.m. UTC | #2
"Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>
> As the sequencer executes todo commands it appends them to
> .git/rebase-merge/done. This file is used by "git status" to show the
> recently executed commands. Unfortunately when a command is rescheduled
> the command preceding it is erroneously appended to the "done" file.
> This means that when rebase stops after rescheduling "pick B" the "done"
> file contains
>
> 	pick A
> 	pick B
> 	pick A
>
> instead of
>
> 	pick A
> 	pick B

Here it may not be clear what you meant with the verb "reschedule"
to those who weren't closely following the previous discussion that
led to this fix.

Is it the same as "the command attempted to execute a step, couldn't
complete it (e.g. due to conflicts), and gave control to the end
user until they say 'git rebase --continue'"?  What cases, other
than interrupted step due to conflicts, involve "rescheduling"?

> Note that the rescheduled command will still be appended to the "done"
> file again when it is successfully executed. Arguably it would be better
> not to do that but fixing it would be more involved.

And without quite understanding what "reschedule" refers to, it is
unclear why it is even arguable---it is perfectly sensible that a
command that is rescheduled (hence not yet done) would not be sent
to 'done'.  If a command that was once rescheduled (hence it wasn't
finished initially) gets finished now, shouldn't it be sent to
'done'?  It is unclear why is it better not to.

> -static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
> +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,
> +		     int reschedule)
>  {

OK, all callers to save_todo() are in pick_commits() that knows what
the value of "reschedule" is, and it is passed down to this helper ...

> @@ -3389,7 +3390,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
>  	 * rebase -i writes "git-rebase-todo" without the currently executing
>  	 * command, appending it to "done" instead.
>  	 */
> -	if (is_rebase_i(opts))
> +	if (is_rebase_i(opts) && !reschedule)
>  		next++;
>  
>  	fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
> @@ -3402,7 +3403,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
>  	if (commit_lock_file(&todo_lock) < 0)
>  		return error(_("failed to finalize '%s'"), todo_path);
>  
> -	if (is_rebase_i(opts) && next > 0) {
> +	if (is_rebase_i(opts) && !reschedule && next > 0) {
>  		const char *done = rebase_path_done();
>  		int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
>  		int ret = 0;

... and the change here is quite straight-forward.  With reschedule,
we do not advance because by definition we haven't finished the
step yet.  OK.

> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,
>  		const char *arg = todo_item_get_arg(todo_list, item);
>  		int check_todo = 0;
>  
> -		if (save_todo(todo_list, opts))
> +		if (save_todo(todo_list, opts, 0))
>  			return -1;

I wonder why we pass a hardcoded 0 here---shouldn't the value match
the local variable 'reschedule'? here?

The same question for the other two callers, but I admit that when
the second one is called, the local variable "reschedule" is not
set...

>  		if (is_rebase_i(opts)) {
>  			if (item->command != TODO_COMMENT) {
> @@ -4695,8 +4696,7 @@ static int pick_commits(struct repository *r,
>  							    todo_list->current),
>  				       get_item_line(todo_list,
>  						     todo_list->current));
> -				todo_list->current--;
> -				if (save_todo(todo_list, opts))
> +				if (save_todo(todo_list, opts, 1))
>  					return -1;

... yet we call the helper with reschedule set to 1.  Puzzled.

> @@ -4788,8 +4788,7 @@ static int pick_commits(struct repository *r,
>  			       get_item_line_length(todo_list,
>  						    todo_list->current),
>  			       get_item_line(todo_list, todo_list->current));
> -			todo_list->current--;
> -			if (save_todo(todo_list, opts))
> +			if (save_todo(todo_list, opts, 1))
>  				return -1;

At this point, reschedule is set and passing it instead of 1 would
be OK.

Thanks.
Phillip Wood March 24, 2023, 10:50 a.m. UTC | #3
Hi Junio

Thanks for your comments

On 20/03/2023 17:46, Junio C Hamano wrote:
> "Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
>> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>>
>> As the sequencer executes todo commands it appends them to
>> .git/rebase-merge/done. This file is used by "git status" to show the
>> recently executed commands. Unfortunately when a command is rescheduled
>> the command preceding it is erroneously appended to the "done" file.
>> This means that when rebase stops after rescheduling "pick B" the "done"
>> file contains
>>
>> 	pick A
>> 	pick B
>> 	pick A
>>
>> instead of
>>
>> 	pick A
>> 	pick B
> 
> Here it may not be clear what you meant with the verb "reschedule"
> to those who weren't closely following the previous discussion that
> led to this fix.
> 
> Is it the same as "the command attempted to execute a step, couldn't
> complete it (e.g. due to conflicts), and gave control to the end
> user until they say 'git rebase --continue'"?  What cases, other
> than interrupted step due to conflicts, involve "rescheduling"?

I'll expand the commit message to explain that if we cannot pick a 
commit because it would overwrite untracked files then we add the 
command back into the todo list and give control to the user until they 
say 'git rebase --continue'. Hopefully they'll have removed the 
problematic files and we try to pick the commit again it will succeed. 
We do the same if an exec command fails and --reschedule-failed-exec was 
given. For conflicts we don't add the command back into the todo list 
because the cherry-pick has happened the user "just" needs to fix the 
conflicts before continuing to the next command.

>> Note that the rescheduled command will still be appended to the "done"
>> file again when it is successfully executed. Arguably it would be better
>> not to do that but fixing it would be more involved.
> 
> And without quite understanding what "reschedule" refers to, it is
> unclear why it is even arguable---it is perfectly sensible that a
> command that is rescheduled (hence not yet done) would not be sent
> to 'done'.  If a command that was once rescheduled (hence it wasn't
> finished initially) gets finished now, shouldn't it be sent to
> 'done'?  It is unclear why is it better not to.

The command is only successfully executed once but may end up in 'done' 
multiple times. While that means we can see which commands ended up 
being rescheduled I'm not sure it is very useful and think really we're 
just cluttering the 'done' file with failed attempts.

>> -static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
>> +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,
>> +		     int reschedule)
>>   {
> 
> OK, all callers to save_todo() are in pick_commits() that knows what
> the value of "reschedule" is, and it is passed down to this helper ...
> 
>> @@ -3389,7 +3390,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
>>   	 * rebase -i writes "git-rebase-todo" without the currently executing
>>   	 * command, appending it to "done" instead.
>>   	 */
>> -	if (is_rebase_i(opts))
>> +	if (is_rebase_i(opts) && !reschedule)
>>   		next++;
>>   
>>   	fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
>> @@ -3402,7 +3403,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
>>   	if (commit_lock_file(&todo_lock) < 0)
>>   		return error(_("failed to finalize '%s'"), todo_path);
>>   
>> -	if (is_rebase_i(opts) && next > 0) {
>> +	if (is_rebase_i(opts) && !reschedule && next > 0) {
>>   		const char *done = rebase_path_done();
>>   		int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
>>   		int ret = 0;
> 
> ... and the change here is quite straight-forward.  With reschedule,
> we do not advance because by definition we haven't finished the
> step yet.  OK.
> 
>> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,
>>   		const char *arg = todo_item_get_arg(todo_list, item);
>>   		int check_todo = 0;
>>   
>> -		if (save_todo(todo_list, opts))
>> +		if (save_todo(todo_list, opts, 0))
>>   			return -1;
> 
> I wonder why we pass a hardcoded 0 here---shouldn't the value match
> the local variable 'reschedule'? here?
> 
> The same question for the other two callers, but I admit that when
> the second one is called, the local variable "reschedule" is not
> set...

The rescheduling code is a bit of a mess as rescheduling commands that 
pick a commit does not use the "reschedule" variable and is handled 
separately to other commands like "reset", "merge" and "exec" which do 
use the "reschedule" varibale. I did try and add a preparatory step to 
fix that but failed to find a good way of doing so. The reason I went 
with hardcoded parameters is that for each call the purpose is fixed and 
as you noticed the "reschedule" variable is only used for rescheduling 
"reset", "merge" and "exec". I could expand the commit message or do you 
think a couple of code comments be more helpful?

Best Wishes

Phillip

>>   		if (is_rebase_i(opts)) {
>>   			if (item->command != TODO_COMMENT) {
>> @@ -4695,8 +4696,7 @@ static int pick_commits(struct repository *r,
>>   							    todo_list->current),
>>   				       get_item_line(todo_list,
>>   						     todo_list->current));
>> -				todo_list->current--;
>> -				if (save_todo(todo_list, opts))
>> +				if (save_todo(todo_list, opts, 1))
>>   					return -1;
> 
> ... yet we call the helper with reschedule set to 1.  Puzzled.
> 
>> @@ -4788,8 +4788,7 @@ static int pick_commits(struct repository *r,
>>   			       get_item_line_length(todo_list,
>>   						    todo_list->current),
>>   			       get_item_line(todo_list, todo_list->current));
>> -			todo_list->current--;
>> -			if (save_todo(todo_list, opts))
>> +			if (save_todo(todo_list, opts, 1))
>>   				return -1;
> 
> At this point, reschedule is set and passing it instead of 1 would
> be OK.
> 
> Thanks.
Junio C Hamano March 24, 2023, 3:49 p.m. UTC | #4
Phillip Wood <phillip.wood123@gmail.com> writes:

>>> Note that the rescheduled command will still be appended to the "done"
>>> file again when it is successfully executed. Arguably it would be better
>>> not to do that but fixing it would be more involved.
>> And without quite understanding what "reschedule" refers to, it is
>> unclear why it is even arguable---it is perfectly sensible that a
>> command that is rescheduled (hence not yet done) would not be sent
>> to 'done'.  If a command that was once rescheduled (hence it wasn't
>> finished initially) gets finished now, shouldn't it be sent to
>> 'done'?  It is unclear why is it better not to.
>
> The command is only successfully executed once but may end up in
> 'done' multiple times. While that means we can see which commands
> ended up being rescheduled I'm not sure it is very useful and think
> really we're just cluttering the 'done' file with failed attempts.

Sorry, but you utterly confused me.  I thought the point of this
change was to avoid such a failed attempt to be recorded in "done",
and if that is the case, we (1) do not record any failing attempts,
(2) record the successful execution, and (3) will not re-attempt
once it is successful.  And if all of these three hold, we wont
clutter 'done' with failed attempts at all, no?

>>> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,
>>>   		const char *arg = todo_item_get_arg(todo_list, item);
>>>   		int check_todo = 0;
>>>   -		if (save_todo(todo_list, opts))
>>> +		if (save_todo(todo_list, opts, 0))
>>>   			return -1;
>> I wonder why we pass a hardcoded 0 here---shouldn't the value match
>> the local variable 'reschedule'? here?
>> The same question for the other two callers, but I admit that when
>> the second one is called, the local variable "reschedule" is not
>> set...
>
> The rescheduling code is a bit of a mess as rescheduling commands that
> pick a commit does not use the "reschedule" variable and is handled
> separately to other commands like "reset", "merge" and "exec" which do
> use the "reschedule" varibale. I did try and add a preparatory step to
> fix that but failed to find a good way of doing so.

I see.  It may be a sign, taken together with the fact that I found
that it was very hard---even after reading the patch twice---to
understand the verb "reschedule" in the proposed commit log to
explain the change, that the concept of "reschedule" in this
codepath may not be clearly capturing what it is attempting to do in
the first place.

> The reason I went
> with hardcoded parameters is that for each call the purpose is fixed
> and as you noticed the "reschedule" variable is only used for
> rescheduling "reset", "merge" and "exec". I could expand the commit
> message or do you think a couple of code comments be more helpful?

Yeah, at least it sounds like the code deserves a "NEEDSWORK: this
is messy in such and such way and we need to clean it up to make it
understandable" comment somehow.

Thanks.
Phillip Wood March 24, 2023, 4:22 p.m. UTC | #5
Hi Junio

On 24/03/2023 15:49, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>>>> Note that the rescheduled command will still be appended to the "done"
>>>> file again when it is successfully executed. Arguably it would be better
>>>> not to do that but fixing it would be more involved.
>>> And without quite understanding what "reschedule" refers to, it is
>>> unclear why it is even arguable---it is perfectly sensible that a
>>> command that is rescheduled (hence not yet done) would not be sent
>>> to 'done'.  If a command that was once rescheduled (hence it wasn't
>>> finished initially) gets finished now, shouldn't it be sent to
>>> 'done'?  It is unclear why is it better not to.
>>
>> The command is only successfully executed once but may end up in
>> 'done' multiple times. While that means we can see which commands
>> ended up being rescheduled I'm not sure it is very useful and think
>> really we're just cluttering the 'done' file with failed attempts.
> 
> Sorry, but you utterly confused me.

Perhaps I should have not have added that last paragraph to the commit 
message.

>  I thought the point of this
> change was to avoid such a failed attempt to be recorded in "done",

No. This change fixes a bug where when we add the failed command back 
into "git-rebase-todo" we accidentally add the previous command to the 
"done" file. If "pick A" succeeds and "pick B" fails because it would 
overwrite an untracked file then currently when the rebase stops after 
the failed "done" will contain

	pick A
	pick B
	pick A

When it should contain

	pick A
	pick B

i.e. the last line should be the last command we tried to execute.

> and if that is the case, we (1) do not record any failing attempts,

unfortunately "done" is updated just before we try and execute the 
command so all the failing attempts are recorded. I'm not trying to 
change that in this patch, I mentioned it in the commit message as a 
suggestion for a future improvement.

> (2) record the successful execution, and (3) will not re-attempt
> once it is successful.  And if all of these three hold, we wont
> clutter 'done' with failed attempts at all, no?

Yes, unfortunately that's not how it works at the moment.

>>>> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,
>>>>    		const char *arg = todo_item_get_arg(todo_list, item);
>>>>    		int check_todo = 0;
>>>>    -		if (save_todo(todo_list, opts))
>>>> +		if (save_todo(todo_list, opts, 0))
>>>>    			return -1;
>>> I wonder why we pass a hardcoded 0 here---shouldn't the value match
>>> the local variable 'reschedule'? here?
>>> The same question for the other two callers, but I admit that when
>>> the second one is called, the local variable "reschedule" is not
>>> set...
>>
>> The rescheduling code is a bit of a mess as rescheduling commands that
>> pick a commit does not use the "reschedule" variable and is handled
>> separately to other commands like "reset", "merge" and "exec" which do
>> use the "reschedule" varibale. I did try and add a preparatory step to
>> fix that but failed to find a good way of doing so.
> 
> I see.  It may be a sign, taken together with the fact that I found
> that it was very hard---even after reading the patch twice---to
> understand the verb "reschedule" in the proposed commit log to
> explain the change, that the concept of "reschedule" in this
> codepath may not be clearly capturing what it is attempting to do in
> the first place.

I'll try and come up with some better wording (if you have any 
suggestions please let me know). What's happening is that just before we 
try and execute a command it it removed from "git-rebase-todo" and added 
to "done". If the command then fails because it would overwrite an 
untracked file we need to add it back into "git-rebase-todo" before 
handing control to the user to remove the offending files. When the user 
runs "git rebase --continue" we'll try and execute the command again. It 
is the adding the command back into "git-rebase-todo" so that it is 
executed by "git rebase --continue" that "reschedule" was intended to 
capture.

The basic problem is that rebase updates "git-rebase-todo" and "done" 
before it has successfully executed the command (cherry-pick and revert 
only remove a command from their todo list after it is executed 
successfully). I fear it may be too late to change that now though.

>> The reason I went
>> with hardcoded parameters is that for each call the purpose is fixed
>> and as you noticed the "reschedule" variable is only used for
>> rescheduling "reset", "merge" and "exec". I could expand the commit
>> message or do you think a couple of code comments be more helpful?
> 
> Yeah, at least it sounds like the code deserves a "NEEDSWORK: this
> is messy in such and such way and we need to clean it up to make it
> understandable" comment somehow.

I'll have another think about how we could clean it up, if that fails 
I'll add a code comment. I'll be offline next week so I'll re-roll after 
that.

Best Wishes

Phillip
> Thanks.
Johannes Schindelin March 27, 2023, 7:04 a.m. UTC | #6
Hi Phillip,

On Sun, 19 Mar 2023, Phillip Wood via GitGitGadget wrote:

> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>
> As the sequencer executes todo commands it appends them to
> .git/rebase-merge/done. This file is used by "git status" to show the
> recently executed commands. Unfortunately when a command is rescheduled
> the command preceding it is erroneously appended to the "done" file.
> This means that when rebase stops after rescheduling "pick B" the "done"
> file contains
>
> 	pick A
> 	pick B
> 	pick A
>
> instead of
>
> 	pick A
> 	pick B
>
> Fix this by not updating the "done" file when adding a rescheduled
> command back into the "todo" file. A couple of the existing tests are
> modified to improve their coverage as none of them trigger this bug or
> check the "done" file.

I am purposefully not yet focusing on the patch, as I have a concern about
the reasoning above.

When a command fails that needs to be rescheduled, I actually _like_ that
there is a record in `done` about said command. It is very much like a
`pick` that failed (but was not rescheduled) and was then `--skip`ed: it
still shows up on `done`.

I do understand the concern that the rescheduled command now shows up in
both `done` and `git-rebase-todo` (which is very different from the failed
`pick` command that would show up _only_ in `git-rebase-todo`). So maybe
we can find some compromise, e.g. adding a commented-out line to `done` à
la:

	# rescheduled: pick A

What do you think?

Ciao,
Johannes
Phillip Wood Aug. 3, 2023, 12:56 p.m. UTC | #7
Hi Dscho

Sorry for not replying sooner.

On 27/03/2023 08:04, Johannes Schindelin wrote:
> Hi Phillip,
> 
> On Sun, 19 Mar 2023, Phillip Wood via GitGitGadget wrote:
> 
>> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>>
>> As the sequencer executes todo commands it appends them to
>> .git/rebase-merge/done. This file is used by "git status" to show the
>> recently executed commands. Unfortunately when a command is rescheduled
>> the command preceding it is erroneously appended to the "done" file.
>> This means that when rebase stops after rescheduling "pick B" the "done"
>> file contains
>>
>> 	pick A
>> 	pick B
>> 	pick A
>>
>> instead of
>>
>> 	pick A
>> 	pick B
>>
>> Fix this by not updating the "done" file when adding a rescheduled
>> command back into the "todo" file. A couple of the existing tests are
>> modified to improve their coverage as none of them trigger this bug or
>> check the "done" file.
> 
> I am purposefully not yet focusing on the patch, as I have a concern about
> the reasoning above.
> 
> When a command fails that needs to be rescheduled, I actually _like_ that
> there is a record in `done` about said command. It is very much like a
> `pick` that failed (but was not rescheduled) and was then `--skip`ed: it
> still shows up on `done`.

We still do that after this patch. What changes is that when "pick B" 
fails we don't add "pick A" to the "done" file when "pick B" is added 
back into "git-rebase-todo"

> I do understand the concern that the rescheduled command now shows up in
> both `done` and `git-rebase-todo` (which is very different from the failed
> `pick` command that would show up _only_ in `git-rebase-todo`). So maybe
> we can find some compromise, e.g. adding a commented-out line to `done` à
> la:
> 
> 	# rescheduled: pick A
> 
> What do you think?

If a commit is rescheduled we still end up with multiple entries in the 
"done". In the example above if "pick B" fails the first time it is 
executed and then succeeds on the second attempt "done" will contain

	pick A
	pick B
	pick B

It might be nice to mark it as rescheduled as you suggest but this 
series focuses on removing the incorrect entry from the "done" file, not 
de-duplicating the "done" entities when a command fails.

Best Wishes

Phillip

> Ciao,
> Johannes
Johannes Schindelin Aug. 23, 2023, 8:54 a.m. UTC | #8
Hi Phillip,

On Thu, 3 Aug 2023, Phillip Wood wrote:

> On 27/03/2023 08:04, Johannes Schindelin wrote:
> >
> > On Sun, 19 Mar 2023, Phillip Wood via GitGitGadget wrote:
> >
> > > From: Phillip Wood <phillip.wood@dunelm.org.uk>
> > >
> > > As the sequencer executes todo commands it appends them to
> > > .git/rebase-merge/done. This file is used by "git status" to show the
> > > recently executed commands. Unfortunately when a command is rescheduled
> > > the command preceding it is erroneously appended to the "done" file.
> > > This means that when rebase stops after rescheduling "pick B" the "done"
> > > file contains
> > >
> > >  pick A
> > >  pick B
> > >  pick A
> > >
> > > instead of
> > >
> > >  pick A
> > >  pick B
> > >
> > > Fix this by not updating the "done" file when adding a rescheduled
> > > command back into the "todo" file. A couple of the existing tests are
> > > modified to improve their coverage as none of them trigger this bug or
> > > check the "done" file.
> >
> > I am purposefully not yet focusing on the patch, as I have a concern about
> > the reasoning above.
> >
> > When a command fails that needs to be rescheduled, I actually _like_ that
> > there is a record in `done` about said command. It is very much like a
> > `pick` that failed (but was not rescheduled) and was then `--skip`ed: it
> > still shows up on `done`.
>
> We still do that after this patch. What changes is that when "pick B" fails we
> don't add "pick A" to the "done" file when "pick B" is added back into
> "git-rebase-todo"
>
> > I do understand the concern that the rescheduled command now shows up in
> > both `done` and `git-rebase-todo` (which is very different from the failed
> > `pick` command that would show up _only_ in `git-rebase-todo`). So maybe
> > we can find some compromise, e.g. adding a commented-out line to `done` à
> > la:
> >
> >  # rescheduled: pick A
> >
> > What do you think?
>
> If a commit is rescheduled we still end up with multiple entries in the
> "done". In the example above if "pick B" fails the first time it is executed
> and then succeeds on the second attempt "done" will contain
>
> 	pick A
> 	pick B
> 	pick B
>
> It might be nice to mark it as rescheduled as you suggest but this series
> focuses on removing the incorrect entry from the "done" file, not
> de-duplicating the "done" entities when a command fails.

After having had plenty of time to let this issue simmer in the back of my
head, and after reviewing the latest iteration of the patch series, I am
no longer concerned, as it now sounds more logical to me, too, that
rescheduled commands don't show up in `done`.

Thank you,
Johannes
diff mbox series

Patch

diff --git a/sequencer.c b/sequencer.c
index 1c96a75b1e9..87eeda52595 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3379,7 +3379,8 @@  give_advice:
 	return -1;
 }
 
-static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
+static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,
+		     int reschedule)
 {
 	struct lock_file todo_lock = LOCK_INIT;
 	const char *todo_path = get_todo_path(opts);
@@ -3389,7 +3390,7 @@  static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
 	 * rebase -i writes "git-rebase-todo" without the currently executing
 	 * command, appending it to "done" instead.
 	 */
-	if (is_rebase_i(opts))
+	if (is_rebase_i(opts) && !reschedule)
 		next++;
 
 	fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
@@ -3402,7 +3403,7 @@  static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
 	if (commit_lock_file(&todo_lock) < 0)
 		return error(_("failed to finalize '%s'"), todo_path);
 
-	if (is_rebase_i(opts) && next > 0) {
+	if (is_rebase_i(opts) && !reschedule && next > 0) {
 		const char *done = rebase_path_done();
 		int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);
 		int ret = 0;
@@ -4648,7 +4649,7 @@  static int pick_commits(struct repository *r,
 		const char *arg = todo_item_get_arg(todo_list, item);
 		int check_todo = 0;
 
-		if (save_todo(todo_list, opts))
+		if (save_todo(todo_list, opts, 0))
 			return -1;
 		if (is_rebase_i(opts)) {
 			if (item->command != TODO_COMMENT) {
@@ -4695,8 +4696,7 @@  static int pick_commits(struct repository *r,
 							    todo_list->current),
 				       get_item_line(todo_list,
 						     todo_list->current));
-				todo_list->current--;
-				if (save_todo(todo_list, opts))
+				if (save_todo(todo_list, opts, 1))
 					return -1;
 			}
 			if (item->command == TODO_EDIT) {
@@ -4788,8 +4788,7 @@  static int pick_commits(struct repository *r,
 			       get_item_line_length(todo_list,
 						    todo_list->current),
 			       get_item_line(todo_list, todo_list->current));
-			todo_list->current--;
-			if (save_todo(todo_list, opts))
+			if (save_todo(todo_list, opts, 1))
 				return -1;
 			if (item->commit)
 				return error_with_patch(r,
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ff0afad63e2..39c1ce51f69 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -1276,20 +1276,27 @@  test_expect_success 'todo count' '
 '
 
 test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '
-	git checkout --force branch2 &&
+	git checkout --force A &&
 	git clean -f &&
+	cat >todo <<-EOF &&
+	exec >file2
+	pick $(git rev-parse B) B
+	pick $(git rev-parse C) C
+	pick $(git rev-parse D) D
+	exec cat .git/rebase-merge/done >actual
+	EOF
 	(
-		set_fake_editor &&
-		FAKE_LINES="edit 1 2" git rebase -i A
+		set_replace_editor todo &&
+		test_must_fail git rebase -i A
 	) &&
-	test_cmp_rev HEAD F &&
-	test_path_is_missing file6 &&
-	>file6 &&
-	test_must_fail git rebase --continue &&
-	test_cmp_rev HEAD F &&
-	rm file6 &&
+	test_cmp_rev HEAD B &&
+	head -n3 todo >expect &&
+	test_cmp expect .git/rebase-merge/done &&
+	rm file2 &&
 	git rebase --continue &&
-	test_cmp_rev HEAD I
+	test_cmp_rev HEAD D &&
+	tail -n3 todo >>expect &&
+	test_cmp expect actual
 '
 
 test_expect_success 'rebase -i commits that overwrite untracked files (squash)' '
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh
index fa2a06c19f0..2ad1f9504da 100755
--- a/t/t3430-rebase-merges.sh
+++ b/t/t3430-rebase-merges.sh
@@ -128,14 +128,24 @@  test_expect_success 'generate correct todo list' '
 '
 
 test_expect_success '`reset` refuses to overwrite untracked files' '
-	git checkout -b refuse-to-reset &&
+	git checkout B &&
 	test_commit dont-overwrite-untracked &&
-	git checkout @{-1} &&
-	: >dont-overwrite-untracked.t &&
-	echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch &&
+	cat >script-from-scratch <<-EOF &&
+	exec >dont-overwrite-untracked.t
+	pick $(git rev-parse B) B
+	reset refs/tags/dont-overwrite-untracked
+	pick $(git rev-parse C) C
+	exec cat .git/rebase-merge/done >actual
+	EOF
 	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
-	test_must_fail git rebase -ir HEAD &&
-	git rebase --abort
+	test_must_fail git rebase -ir A &&
+	test_cmp_rev HEAD B &&
+	head -n3 script-from-scratch >expect &&
+	test_cmp expect .git/rebase-merge/done &&
+	rm dont-overwrite-untracked.t &&
+	git rebase --continue &&
+	tail -n3 script-from-scratch >>expect &&
+	test_cmp expect actual
 '
 
 test_expect_success '`reset` rejects trees' '