{"thread":{"id":"59416","subject":"[PATCH] rebase -i: do not update \"done\" when rescheduling command","startedAt":"2023-03-19T14:49:05Z","lastAt":"2023-09-07T20:34:02Z","messageCount":80,"participants":["Phillip Wood via GitGitGadget","Stefan Haller","Junio C Hamano","Phillip Wood","Johannes Schindelin","Eric Sunshine","Glen Choo"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"473730","messageId":"pull.1492.git.1679237337683.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":null,"subject":"[PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-19T14:48:57Z","receivedAt":"2023-03-19T14:49:05Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAs the sequencer executes todo commands it appends them to\n.git/rebase-merge/done. This file is used by \"git status\" to show the\nrecently executed commands. Unfortunately when a command is rescheduled\nthe command preceding it is erroneously appended to the \"done\" file.\nThis means that when rebase stops after rescheduling \"pick B\" the \"done\"\nfile contains\n\n\tpick A\n\tpick B\n\tpick A\n\ninstead of\n\n\tpick A\n\tpick B\n\nFix this by not updating the \"done\" file when adding a rescheduled\ncommand back into the \"todo\" file. A couple of the existing tests are\nmodified to improve their coverage as none of them trigger this bug or\ncheck the \"done\" file.\n\nNote that the rescheduled command will still be appended to the \"done\"\nfile again when it is successfully executed. Arguably it would be better\nnot to do that but fixing it would be more involved.\n\nReported-by: Stefan Haller <lists@haller-berlin.de>\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n    rebase -i: do not update \"done\" when rescheduling command\n    \n    This fixes the output of \"git status\" when rebase stops for a\n    rescheduled command.\n    \n    Stefan - thanks for reporting this, hopefully it will be easy enough to\n    update lazygit to check the last command in the done file as well as the\n    second to last command for older versions of git.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1492%2Fphillipwood%2Frebase-dont-write-done-when-rescheduling-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1492/phillipwood/rebase-dont-write-done-when-rescheduling-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1492\n\n sequencer.c                   | 15 +++++++--------\n t/t3404-rebase-interactive.sh | 27 +++++++++++++++++----------\n t/t3430-rebase-merges.sh      | 22 ++++++++++++++++------\n 3 files changed, 40 insertions(+), 24 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 1c96a75b1e9..87eeda52595 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3379,7 +3379,8 @@ give_advice:\n \treturn -1;\n }\n \n-static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n+static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,\n+\t\t     int reschedule)\n {\n \tstruct lock_file todo_lock = LOCK_INIT;\n \tconst char *todo_path = get_todo_path(opts);\n@@ -3389,7 +3390,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \t * rebase -i writes \"git-rebase-todo\" without the currently executing\n \t * command, appending it to \"done\" instead.\n \t */\n-\tif (is_rebase_i(opts))\n+\tif (is_rebase_i(opts) && !reschedule)\n \t\tnext++;\n \n \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n@@ -3402,7 +3403,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tif (commit_lock_file(&todo_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n \n-\tif (is_rebase_i(opts) && next > 0) {\n+\tif (is_rebase_i(opts) && !reschedule && next > 0) {\n \t\tconst char *done = rebase_path_done();\n \t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n \t\tint ret = 0;\n@@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,\n \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n \t\tint check_todo = 0;\n \n-\t\tif (save_todo(todo_list, opts))\n+\t\tif (save_todo(todo_list, opts, 0))\n \t\t\treturn -1;\n \t\tif (is_rebase_i(opts)) {\n \t\t\tif (item->command != TODO_COMMENT) {\n@@ -4695,8 +4696,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\t\t\t\t    todo_list->current),\n \t\t\t\t       get_item_line(todo_list,\n \t\t\t\t\t\t     todo_list->current));\n-\t\t\t\ttodo_list->current--;\n-\t\t\t\tif (save_todo(todo_list, opts))\n+\t\t\t\tif (save_todo(todo_list, opts, 1))\n \t\t\t\t\treturn -1;\n \t\t\t}\n \t\t\tif (item->command == TODO_EDIT) {\n@@ -4788,8 +4788,7 @@ static int pick_commits(struct repository *r,\n \t\t\t       get_item_line_length(todo_list,\n \t\t\t\t\t\t    todo_list->current),\n \t\t\t       get_item_line(todo_list, todo_list->current));\n-\t\t\ttodo_list->current--;\n-\t\t\tif (save_todo(todo_list, opts))\n+\t\t\tif (save_todo(todo_list, opts, 1))\n \t\t\t\treturn -1;\n \t\t\tif (item->commit)\n \t\t\t\treturn error_with_patch(r,\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ff0afad63e2..39c1ce51f69 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1276,20 +1276,27 @@ test_expect_success 'todo count' '\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n-\tgit checkout --force branch2 &&\n+\tgit checkout --force A &&\n \tgit clean -f &&\n+\tcat >todo <<-EOF &&\n+\texec >file2\n+\tpick $(git rev-parse B) B\n+\tpick $(git rev-parse C) C\n+\tpick $(git rev-parse D) D\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \t(\n-\t\tset_fake_editor &&\n-\t\tFAKE_LINES=\"edit 1 2\" git rebase -i A\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i A\n \t) &&\n-\ttest_cmp_rev HEAD F &&\n-\ttest_path_is_missing file6 &&\n-\t>file6 &&\n-\ttest_must_fail git rebase --continue &&\n-\ttest_cmp_rev HEAD F &&\n-\trm file6 &&\n+\ttest_cmp_rev HEAD B &&\n+\thead -n3 todo >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm file2 &&\n \tgit rebase --continue &&\n-\ttest_cmp_rev HEAD I\n+\ttest_cmp_rev HEAD D &&\n+\ttail -n3 todo >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (squash)' '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex fa2a06c19f0..2ad1f9504da 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -128,14 +128,24 @@ test_expect_success 'generate correct todo list' '\n '\n \n test_expect_success '`reset` refuses to overwrite untracked files' '\n-\tgit checkout -b refuse-to-reset &&\n+\tgit checkout B &&\n \ttest_commit dont-overwrite-untracked &&\n-\tgit checkout @{-1} &&\n-\t: >dont-overwrite-untracked.t &&\n-\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n+\tcat >script-from-scratch <<-EOF &&\n+\texec >dont-overwrite-untracked.t\n+\tpick $(git rev-parse B) B\n+\treset refs/tags/dont-overwrite-untracked\n+\tpick $(git rev-parse C) C\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n-\ttest_must_fail git rebase -ir HEAD &&\n-\tgit rebase --abort\n+\ttest_must_fail git rebase -ir A &&\n+\ttest_cmp_rev HEAD B &&\n+\thead -n3 script-from-scratch >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm dont-overwrite-untracked.t &&\n+\tgit rebase --continue &&\n+\ttail -n3 script-from-scratch >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success '`reset` rejects trees' '\n\nbase-commit: 73876f4861cd3d187a4682290ab75c9dccadbc56\n-- \ngitgitgadget\n"},{"id":"473749","messageId":"7917bb3f-d5cb-a691-6be3-ab453271d2e7@haller-berlin.de","threadId":"59416","inReplyTo":"pull.1492.git.1679237337683.gitgitgadget@gmail.com","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Stefan Haller","fromEmail":"lists@haller-berlin.de","sentAt":"2023-03-20T07:29:22Z","receivedAt":"2023-03-20T07:29:29Z","isPatch":true,"sender":{"key":"lists@haller-berlin.de","avatar":null},"body":"On 19.03.23 15:48, Phillip Wood via GitGitGadget wrote:\n>     Stefan - thanks for reporting this, hopefully it will be easy enough to\n>     update lazygit to check the last command in the done file as well as the\n>     second to last command for older versions of git.\n\nThanks for the fix. Yes, it should be easy to adapt lazygit to the new\nbehavior.\n\n-Stefan\n"},{"id":"473791","messageId":"xmqq4jqfmi2k.fsf@gitster.g","threadId":"59416","inReplyTo":"pull.1492.git.1679237337683.gitgitgadget@gmail.com","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-20T17:46:43Z","receivedAt":"2023-03-20T17:53:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> As the sequencer executes todo commands it appends them to\n> .git/rebase-merge/done. This file is used by \"git status\" to show the\n> recently executed commands. Unfortunately when a command is rescheduled\n> the command preceding it is erroneously appended to the \"done\" file.\n> This means that when rebase stops after rescheduling \"pick B\" the \"done\"\n> file contains\n>\n> \tpick A\n> \tpick B\n> \tpick A\n>\n> instead of\n>\n> \tpick A\n> \tpick B\n\nHere it may not be clear what you meant with the verb \"reschedule\"\nto those who weren't closely following the previous discussion that\nled to this fix.\n\nIs it the same as \"the command attempted to execute a step, couldn't\ncomplete it (e.g. due to conflicts), and gave control to the end\nuser until they say 'git rebase --continue'\"?  What cases, other\nthan interrupted step due to conflicts, involve \"rescheduling\"?\n\n> Note that the rescheduled command will still be appended to the \"done\"\n> file again when it is successfully executed. Arguably it would be better\n> not to do that but fixing it would be more involved.\n\nAnd without quite understanding what \"reschedule\" refers to, it is\nunclear why it is even arguable---it is perfectly sensible that a\ncommand that is rescheduled (hence not yet done) would not be sent\nto 'done'.  If a command that was once rescheduled (hence it wasn't\nfinished initially) gets finished now, shouldn't it be sent to\n'done'?  It is unclear why is it better not to.\n\n> -static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n> +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,\n> +\t\t     int reschedule)\n>  {\n\nOK, all callers to save_todo() are in pick_commits() that knows what\nthe value of \"reschedule\" is, and it is passed down to this helper ...\n\n> @@ -3389,7 +3390,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n>  \t * rebase -i writes \"git-rebase-todo\" without the currently executing\n>  \t * command, appending it to \"done\" instead.\n>  \t */\n> -\tif (is_rebase_i(opts))\n> +\tif (is_rebase_i(opts) && !reschedule)\n>  \t\tnext++;\n>  \n>  \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n> @@ -3402,7 +3403,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n>  \tif (commit_lock_file(&todo_lock) < 0)\n>  \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n>  \n> -\tif (is_rebase_i(opts) && next > 0) {\n> +\tif (is_rebase_i(opts) && !reschedule && next > 0) {\n>  \t\tconst char *done = rebase_path_done();\n>  \t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n>  \t\tint ret = 0;\n\n... and the change here is quite straight-forward.  With reschedule,\nwe do not advance because by definition we haven't finished the\nstep yet.  OK.\n\n> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,\n>  \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n>  \t\tint check_todo = 0;\n>  \n> -\t\tif (save_todo(todo_list, opts))\n> +\t\tif (save_todo(todo_list, opts, 0))\n>  \t\t\treturn -1;\n\nI wonder why we pass a hardcoded 0 here---shouldn't the value match\nthe local variable 'reschedule'? here?\n\nThe same question for the other two callers, but I admit that when\nthe second one is called, the local variable \"reschedule\" is not\nset...\n\n>  \t\tif (is_rebase_i(opts)) {\n>  \t\t\tif (item->command != TODO_COMMENT) {\n> @@ -4695,8 +4696,7 @@ static int pick_commits(struct repository *r,\n>  \t\t\t\t\t\t\t    todo_list->current),\n>  \t\t\t\t       get_item_line(todo_list,\n>  \t\t\t\t\t\t     todo_list->current));\n> -\t\t\t\ttodo_list->current--;\n> -\t\t\t\tif (save_todo(todo_list, opts))\n> +\t\t\t\tif (save_todo(todo_list, opts, 1))\n>  \t\t\t\t\treturn -1;\n\n... yet we call the helper with reschedule set to 1.  Puzzled.\n\n> @@ -4788,8 +4788,7 @@ static int pick_commits(struct repository *r,\n>  \t\t\t       get_item_line_length(todo_list,\n>  \t\t\t\t\t\t    todo_list->current),\n>  \t\t\t       get_item_line(todo_list, todo_list->current));\n> -\t\t\ttodo_list->current--;\n> -\t\t\tif (save_todo(todo_list, opts))\n> +\t\t\tif (save_todo(todo_list, opts, 1))\n>  \t\t\t\treturn -1;\n\nAt this point, reschedule is set and passing it instead of 1 would\nbe OK.\n\nThanks.\n"},{"id":"474079","messageId":"9e5093ab-2aa8-16e7-227a-f5c56983be9a@dunelm.org.uk","threadId":"59416","inReplyTo":"xmqq4jqfmi2k.fsf@gitster.g","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-03-24T10:50:52Z","receivedAt":"2023-03-24T10:51:05Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nThanks for your comments\n\nOn 20/03/2023 17:46, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> As the sequencer executes todo commands it appends them to\n>> .git/rebase-merge/done. This file is used by \"git status\" to show the\n>> recently executed commands. Unfortunately when a command is rescheduled\n>> the command preceding it is erroneously appended to the \"done\" file.\n>> This means that when rebase stops after rescheduling \"pick B\" the \"done\"\n>> file contains\n>>\n>> \tpick A\n>> \tpick B\n>> \tpick A\n>>\n>> instead of\n>>\n>> \tpick A\n>> \tpick B\n> \n> Here it may not be clear what you meant with the verb \"reschedule\"\n> to those who weren't closely following the previous discussion that\n> led to this fix.\n> \n> Is it the same as \"the command attempted to execute a step, couldn't\n> complete it (e.g. due to conflicts), and gave control to the end\n> user until they say 'git rebase --continue'\"?  What cases, other\n> than interrupted step due to conflicts, involve \"rescheduling\"?\n\nI'll expand the commit message to explain that if we cannot pick a \ncommit because it would overwrite untracked files then we add the \ncommand back into the todo list and give control to the user until they \nsay 'git rebase --continue'. Hopefully they'll have removed the \nproblematic files and we try to pick the commit again it will succeed. \nWe do the same if an exec command fails and --reschedule-failed-exec was \ngiven. For conflicts we don't add the command back into the todo list \nbecause the cherry-pick has happened the user \"just\" needs to fix the \nconflicts before continuing to the next command.\n\n>> Note that the rescheduled command will still be appended to the \"done\"\n>> file again when it is successfully executed. Arguably it would be better\n>> not to do that but fixing it would be more involved.\n> \n> And without quite understanding what \"reschedule\" refers to, it is\n> unclear why it is even arguable---it is perfectly sensible that a\n> command that is rescheduled (hence not yet done) would not be sent\n> to 'done'.  If a command that was once rescheduled (hence it wasn't\n> finished initially) gets finished now, shouldn't it be sent to\n> 'done'?  It is unclear why is it better not to.\n\nThe command is only successfully executed once but may end up in 'done' \nmultiple times. While that means we can see which commands ended up \nbeing rescheduled I'm not sure it is very useful and think really we're \njust cluttering the 'done' file with failed attempts.\n\n>> -static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n>> +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,\n>> +\t\t     int reschedule)\n>>   {\n> \n> OK, all callers to save_todo() are in pick_commits() that knows what\n> the value of \"reschedule\" is, and it is passed down to this helper ...\n> \n>> @@ -3389,7 +3390,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n>>   \t * rebase -i writes \"git-rebase-todo\" without the currently executing\n>>   \t * command, appending it to \"done\" instead.\n>>   \t */\n>> -\tif (is_rebase_i(opts))\n>> +\tif (is_rebase_i(opts) && !reschedule)\n>>   \t\tnext++;\n>>   \n>>   \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n>> @@ -3402,7 +3403,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n>>   \tif (commit_lock_file(&todo_lock) < 0)\n>>   \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n>>   \n>> -\tif (is_rebase_i(opts) && next > 0) {\n>> +\tif (is_rebase_i(opts) && !reschedule && next > 0) {\n>>   \t\tconst char *done = rebase_path_done();\n>>   \t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n>>   \t\tint ret = 0;\n> \n> ... and the change here is quite straight-forward.  With reschedule,\n> we do not advance because by definition we haven't finished the\n> step yet.  OK.\n> \n>> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,\n>>   \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n>>   \t\tint check_todo = 0;\n>>   \n>> -\t\tif (save_todo(todo_list, opts))\n>> +\t\tif (save_todo(todo_list, opts, 0))\n>>   \t\t\treturn -1;\n> \n> I wonder why we pass a hardcoded 0 here---shouldn't the value match\n> the local variable 'reschedule'? here?\n> \n> The same question for the other two callers, but I admit that when\n> the second one is called, the local variable \"reschedule\" is not\n> set...\n\nThe rescheduling code is a bit of a mess as rescheduling commands that \npick a commit does not use the \"reschedule\" variable and is handled \nseparately to other commands like \"reset\", \"merge\" and \"exec\" which do \nuse the \"reschedule\" varibale. I did try and add a preparatory step to \nfix that but failed to find a good way of doing so. The reason I went \nwith hardcoded parameters is that for each call the purpose is fixed and \nas you noticed the \"reschedule\" variable is only used for rescheduling \n\"reset\", \"merge\" and \"exec\". I could expand the commit message or do you \nthink a couple of code comments be more helpful?\n\nBest Wishes\n\nPhillip\n\n>>   \t\tif (is_rebase_i(opts)) {\n>>   \t\t\tif (item->command != TODO_COMMENT) {\n>> @@ -4695,8 +4696,7 @@ static int pick_commits(struct repository *r,\n>>   \t\t\t\t\t\t\t    todo_list->current),\n>>   \t\t\t\t       get_item_line(todo_list,\n>>   \t\t\t\t\t\t     todo_list->current));\n>> -\t\t\t\ttodo_list->current--;\n>> -\t\t\t\tif (save_todo(todo_list, opts))\n>> +\t\t\t\tif (save_todo(todo_list, opts, 1))\n>>   \t\t\t\t\treturn -1;\n> \n> ... yet we call the helper with reschedule set to 1.  Puzzled.\n> \n>> @@ -4788,8 +4788,7 @@ static int pick_commits(struct repository *r,\n>>   \t\t\t       get_item_line_length(todo_list,\n>>   \t\t\t\t\t\t    todo_list->current),\n>>   \t\t\t       get_item_line(todo_list, todo_list->current));\n>> -\t\t\ttodo_list->current--;\n>> -\t\t\tif (save_todo(todo_list, opts))\n>> +\t\t\tif (save_todo(todo_list, opts, 1))\n>>   \t\t\t\treturn -1;\n> \n> At this point, reschedule is set and passing it instead of 1 would\n> be OK.\n> \n> Thanks.\n"},{"id":"474096","messageId":"xmqqfs9u409m.fsf@gitster.g","threadId":"59416","inReplyTo":"9e5093ab-2aa8-16e7-227a-f5c56983be9a@dunelm.org.uk","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-24T15:49:57Z","receivedAt":"2023-03-24T15:50:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>>> Note that the rescheduled command will still be appended to the \"done\"\n>>> file again when it is successfully executed. Arguably it would be better\n>>> not to do that but fixing it would be more involved.\n>> And without quite understanding what \"reschedule\" refers to, it is\n>> unclear why it is even arguable---it is perfectly sensible that a\n>> command that is rescheduled (hence not yet done) would not be sent\n>> to 'done'.  If a command that was once rescheduled (hence it wasn't\n>> finished initially) gets finished now, shouldn't it be sent to\n>> 'done'?  It is unclear why is it better not to.\n>\n> The command is only successfully executed once but may end up in\n> 'done' multiple times. While that means we can see which commands\n> ended up being rescheduled I'm not sure it is very useful and think\n> really we're just cluttering the 'done' file with failed attempts.\n\nSorry, but you utterly confused me.  I thought the point of this\nchange was to avoid such a failed attempt to be recorded in \"done\",\nand if that is the case, we (1) do not record any failing attempts,\n(2) record the successful execution, and (3) will not re-attempt\nonce it is successful.  And if all of these three hold, we wont\nclutter 'done' with failed attempts at all, no?\n\n>>> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,\n>>>   \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n>>>   \t\tint check_todo = 0;\n>>>   -\t\tif (save_todo(todo_list, opts))\n>>> +\t\tif (save_todo(todo_list, opts, 0))\n>>>   \t\t\treturn -1;\n>> I wonder why we pass a hardcoded 0 here---shouldn't the value match\n>> the local variable 'reschedule'? here?\n>> The same question for the other two callers, but I admit that when\n>> the second one is called, the local variable \"reschedule\" is not\n>> set...\n>\n> The rescheduling code is a bit of a mess as rescheduling commands that\n> pick a commit does not use the \"reschedule\" variable and is handled\n> separately to other commands like \"reset\", \"merge\" and \"exec\" which do\n> use the \"reschedule\" varibale. I did try and add a preparatory step to\n> fix that but failed to find a good way of doing so.\n\nI see.  It may be a sign, taken together with the fact that I found\nthat it was very hard---even after reading the patch twice---to\nunderstand the verb \"reschedule\" in the proposed commit log to\nexplain the change, that the concept of \"reschedule\" in this\ncodepath may not be clearly capturing what it is attempting to do in\nthe first place.\n\n> The reason I went\n> with hardcoded parameters is that for each call the purpose is fixed\n> and as you noticed the \"reschedule\" variable is only used for\n> rescheduling \"reset\", \"merge\" and \"exec\". I could expand the commit\n> message or do you think a couple of code comments be more helpful?\n\nYeah, at least it sounds like the code deserves a \"NEEDSWORK: this\nis messy in such and such way and we need to clean it up to make it\nunderstandable\" comment somehow.\n\nThanks.\n"},{"id":"474098","messageId":"5e49f936-5281-6645-7bf7-78e658087c8a@dunelm.org.uk","threadId":"59416","inReplyTo":"xmqqfs9u409m.fsf@gitster.g","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-03-24T16:22:36Z","receivedAt":"2023-03-24T16:22:44Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 24/03/2023 15:49, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>>>> Note that the rescheduled command will still be appended to the \"done\"\n>>>> file again when it is successfully executed. Arguably it would be better\n>>>> not to do that but fixing it would be more involved.\n>>> And without quite understanding what \"reschedule\" refers to, it is\n>>> unclear why it is even arguable---it is perfectly sensible that a\n>>> command that is rescheduled (hence not yet done) would not be sent\n>>> to 'done'.  If a command that was once rescheduled (hence it wasn't\n>>> finished initially) gets finished now, shouldn't it be sent to\n>>> 'done'?  It is unclear why is it better not to.\n>>\n>> The command is only successfully executed once but may end up in\n>> 'done' multiple times. While that means we can see which commands\n>> ended up being rescheduled I'm not sure it is very useful and think\n>> really we're just cluttering the 'done' file with failed attempts.\n> \n> Sorry, but you utterly confused me.\n\nPerhaps I should have not have added that last paragraph to the commit \nmessage.\n\n>  I thought the point of this\n> change was to avoid such a failed attempt to be recorded in \"done\",\n\nNo. This change fixes a bug where when we add the failed command back \ninto \"git-rebase-todo\" we accidentally add the previous command to the \n\"done\" file. If \"pick A\" succeeds and \"pick B\" fails because it would \noverwrite an untracked file then currently when the rebase stops after \nthe failed \"done\" will contain\n\n\tpick A\n\tpick B\n\tpick A\n\nWhen it should contain\n\n\tpick A\n\tpick B\n\ni.e. the last line should be the last command we tried to execute.\n\n> and if that is the case, we (1) do not record any failing attempts,\n\nunfortunately \"done\" is updated just before we try and execute the \ncommand so all the failing attempts are recorded. I'm not trying to \nchange that in this patch, I mentioned it in the commit message as a \nsuggestion for a future improvement.\n\n> (2) record the successful execution, and (3) will not re-attempt\n> once it is successful.  And if all of these three hold, we wont\n> clutter 'done' with failed attempts at all, no?\n\nYes, unfortunately that's not how it works at the moment.\n\n>>>> @@ -4648,7 +4649,7 @@ static int pick_commits(struct repository *r,\n>>>>    \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n>>>>    \t\tint check_todo = 0;\n>>>>    -\t\tif (save_todo(todo_list, opts))\n>>>> +\t\tif (save_todo(todo_list, opts, 0))\n>>>>    \t\t\treturn -1;\n>>> I wonder why we pass a hardcoded 0 here---shouldn't the value match\n>>> the local variable 'reschedule'? here?\n>>> The same question for the other two callers, but I admit that when\n>>> the second one is called, the local variable \"reschedule\" is not\n>>> set...\n>>\n>> The rescheduling code is a bit of a mess as rescheduling commands that\n>> pick a commit does not use the \"reschedule\" variable and is handled\n>> separately to other commands like \"reset\", \"merge\" and \"exec\" which do\n>> use the \"reschedule\" varibale. I did try and add a preparatory step to\n>> fix that but failed to find a good way of doing so.\n> \n> I see.  It may be a sign, taken together with the fact that I found\n> that it was very hard---even after reading the patch twice---to\n> understand the verb \"reschedule\" in the proposed commit log to\n> explain the change, that the concept of \"reschedule\" in this\n> codepath may not be clearly capturing what it is attempting to do in\n> the first place.\n\nI'll try and come up with some better wording (if you have any \nsuggestions please let me know). What's happening is that just before we \ntry and execute a command it it removed from \"git-rebase-todo\" and added \nto \"done\". If the command then fails because it would overwrite an \nuntracked file we need to add it back into \"git-rebase-todo\" before \nhanding control to the user to remove the offending files. When the user \nruns \"git rebase --continue\" we'll try and execute the command again. It \nis the adding the command back into \"git-rebase-todo\" so that it is \nexecuted by \"git rebase --continue\" that \"reschedule\" was intended to \ncapture.\n\nThe basic problem is that rebase updates \"git-rebase-todo\" and \"done\" \nbefore it has successfully executed the command (cherry-pick and revert \nonly remove a command from their todo list after it is executed \nsuccessfully). I fear it may be too late to change that now though.\n\n>> The reason I went\n>> with hardcoded parameters is that for each call the purpose is fixed\n>> and as you noticed the \"reschedule\" variable is only used for\n>> rescheduling \"reset\", \"merge\" and \"exec\". I could expand the commit\n>> message or do you think a couple of code comments be more helpful?\n> \n> Yeah, at least it sounds like the code deserves a \"NEEDSWORK: this\n> is messy in such and such way and we need to clean it up to make it\n> understandable\" comment somehow.\n\nI'll have another think about how we could clean it up, if that fails \nI'll add a code comment. I'll be offline next week so I'll re-roll after \nthat.\n\nBest Wishes\n\nPhillip\n> Thanks.\n"},{"id":"474214","messageId":"f05deb00-1bcd-9e05-739f-6a30d6d8cf3b@gmx.de","threadId":"59416","inReplyTo":"pull.1492.git.1679237337683.gitgitgadget@gmail.com","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-03-27T07:04:17Z","receivedAt":"2023-03-27T07:05:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Sun, 19 Mar 2023, Phillip Wood via GitGitGadget wrote:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> As the sequencer executes todo commands it appends them to\n> .git/rebase-merge/done. This file is used by \"git status\" to show the\n> recently executed commands. Unfortunately when a command is rescheduled\n> the command preceding it is erroneously appended to the \"done\" file.\n> This means that when rebase stops after rescheduling \"pick B\" the \"done\"\n> file contains\n>\n> \tpick A\n> \tpick B\n> \tpick A\n>\n> instead of\n>\n> \tpick A\n> \tpick B\n>\n> Fix this by not updating the \"done\" file when adding a rescheduled\n> command back into the \"todo\" file. A couple of the existing tests are\n> modified to improve their coverage as none of them trigger this bug or\n> check the \"done\" file.\n\nI am purposefully not yet focusing on the patch, as I have a concern about\nthe reasoning above.\n\nWhen a command fails that needs to be rescheduled, I actually _like_ that\nthere is a record in `done` about said command. It is very much like a\n`pick` that failed (but was not rescheduled) and was then `--skip`ed: it\nstill shows up on `done`.\n\nI do understand the concern that the rescheduled command now shows up in\nboth `done` and `git-rebase-todo` (which is very different from the failed\n`pick` command that would show up _only_ in `git-rebase-todo`). So maybe\nwe can find some compromise, e.g. adding a commented-out line to `done` à\nla:\n\n\t# rescheduled: pick A\n\nWhat do you think?\n\nCiao,\nJohannes\n"},{"id":"475785","messageId":"3dfb2c6903bcc61258c72ba5c8e4201c9db2665b.1682089074.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"[PATCH v2 1/6] rebase -i: move unlink() calls","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T14:57:49Z","receivedAt":"2023-04-21T14:58:05Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAt the start of each iteration the loop that picks commits removes\nstate files from the previous pick. However some of these are only\nwritten if there are conflicts so only need to be removed before\nstarting the loop, not in each iteration.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex d2c7698c48c..5073ec5902b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4639,6 +4639,10 @@ static int pick_commits(struct repository *r,\n \tif (read_and_refresh_cache(r, opts))\n \t\treturn -1;\n \n+\tunlink(rebase_path_message());\n+\tunlink(rebase_path_stopped_sha());\n+\tunlink(rebase_path_amend());\n+\n \twhile (todo_list->current < todo_list->nr) {\n \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n@@ -4662,10 +4666,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\t\t\ttodo_list->total_nr,\n \t\t\t\t\t\topts->verbose ? \"\\n\" : \"\\r\");\n \t\t\t}\n-\t\t\tunlink(rebase_path_message());\n \t\t\tunlink(rebase_path_author_script());\n-\t\t\tunlink(rebase_path_stopped_sha());\n-\t\t\tunlink(rebase_path_amend());\n \t\t\tunlink(git_path_merge_head(r));\n \t\t\tunlink(git_path_auto_merge(r));\n \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n-- \ngitgitgadget\n\n"},{"id":"475787","messageId":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.git.1679237337683.gitgitgadget@gmail.com","subject":"[PATCH v2 0/6] rebase -i: impove handling of failed commands","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T14:57:48Z","receivedAt":"2023-04-21T14:58:06Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"This series fixes several bugs in the way we handle a commit cannot be\npicked because it would overwrite an untracked file.\n\n * after a failed pick \"git rebase --continue\" will happily commit any\n   staged changes even though no commit was picked.\n\n * the commit of the failed pick is recorded as rewritten even though no\n   commit was picked.\n\n * the \"done\" file used by \"git status\" to show the recently executed\n   commands contains an incorrect entry.\n\nThanks for the comments on V1, this series has now grown somewhat.\nPreviously I was worried that refactoring would change the behavior, but\nhaving thought about it the current behavior is wrong and should be changed.\n\nChanges since V1:\n\nRebased onto master to avoid a conflict with\nab/remove-implicit-use-of-the-repository\n\n * Patches 1-3 are new preparatory changes\n * Patches 4 & 5 are new and fix the first two issues listed above.\n * Patch 6 is the old patch 1 which has been rebased and the commit message\n   reworded. It fixes the last issues listed above.\n\nPhillip Wood (6):\n  rebase -i: move unlink() calls\n  rebase -i: remove patch file after conflict resolution\n  sequencer: factor out part of pick_commits()\n  rebase --continue: refuse to commit after failed command\n  rebase: fix rewritten list for failed pick\n  rebase -i: fix adding failed command to the todo list\n\n sequencer.c                   | 170 ++++++++++++++++++----------------\n t/t3404-rebase-interactive.sh |  49 +++++++---\n t/t3430-rebase-merges.sh      |  35 +++++--\n t/t5407-post-rewrite-hook.sh  |  11 +++\n 4 files changed, 165 insertions(+), 100 deletions(-)\n\n\nbase-commit: 9c6990cca24301ae8f82bf6291049667a0aef14b\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1492%2Fphillipwood%2Frebase-dont-write-done-when-rescheduling-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1492/phillipwood/rebase-dont-write-done-when-rescheduling-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1492\n\nRange-diff vs v1:\n\n -:  ----------- > 1:  3dfb2c6903b rebase -i: move unlink() calls\n -:  ----------- > 2:  227aea031b5 rebase -i: remove patch file after conflict resolution\n -:  ----------- > 3:  31bb644e769 sequencer: factor out part of pick_commits()\n -:  ----------- > 4:  9356d14b09a rebase --continue: refuse to commit after failed command\n -:  ----------- > 5:  f8e64c1b631 rebase: fix rewritten list for failed pick\n 1:  dc51a7499bc ! 6:  a836b049b90 rebase -i: do not update \"done\" when rescheduling command\n     @@ Metadata\n      Author: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## Commit message ##\n     -    rebase -i: do not update \"done\" when rescheduling command\n     +    rebase -i: fix adding failed command to the todo list\n      \n     -    As the sequencer executes todo commands it appends them to\n     -    .git/rebase-merge/done. This file is used by \"git status\" to show the\n     -    recently executed commands. Unfortunately when a command is rescheduled\n     +    When rebasing commands are moved from the todo list in \"git-rebase-todo\"\n     +    to the \"done\" file (which is used by \"git status\" to show the recently\n     +    executed commands) just before they are executed. This means that if a\n     +    command fails because it would overwrite an untracked file it has to be\n     +    added back into the todo list before the rebase stops for the user to\n     +    fix the problem.\n     +\n     +    Unfortunately when a failed command is added back into the todo list\n          the command preceding it is erroneously appended to the \"done\" file.\n     -    This means that when rebase stops after rescheduling \"pick B\" the \"done\"\n     +    This means that when rebase stops after \"pick B\" fails the \"done\"\n          file contains\n      \n                  pick A\n     @@ Commit message\n                  pick A\n                  pick B\n      \n     -    Fix this by not updating the \"done\" file when adding a rescheduled\n     -    command back into the \"todo\" file. A couple of the existing tests are\n     +    Fix this by not updating the \"done\" file when adding a failed command\n     +    back into the \"git-rebase-todo\" file. A couple of the existing tests are\n          modified to improve their coverage as none of them trigger this bug or\n          check the \"done\" file.\n      \n     -    Note that the rescheduled command will still be appended to the \"done\"\n     -    file again when it is successfully executed. Arguably it would be better\n     -    not to do that but fixing it would be more involved.\n     -\n          Reported-by: Stefan Haller <lists@haller-berlin.de>\n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n     @@ sequencer.c: static int pick_commits(struct repository *r,\n       \t\tint check_todo = 0;\n       \n      -\t\tif (save_todo(todo_list, opts))\n     -+\t\tif (save_todo(todo_list, opts, 0))\n     ++\t\tif (save_todo(todo_list, opts, reschedule))\n       \t\t\treturn -1;\n       \t\tif (is_rebase_i(opts)) {\n       \t\t\tif (item->command != TODO_COMMENT) {\n     -@@ sequencer.c: static int pick_commits(struct repository *r,\n     - \t\t\t\t\t\t\t    todo_list->current),\n     - \t\t\t\t       get_item_line(todo_list,\n     - \t\t\t\t\t\t     todo_list->current));\n     --\t\t\t\ttodo_list->current--;\n     --\t\t\t\tif (save_todo(todo_list, opts))\n     -+\t\t\t\tif (save_todo(todo_list, opts, 1))\n     - \t\t\t\t\treturn -1;\n     - \t\t\t}\n     - \t\t\tif (item->command == TODO_EDIT) {\n      @@ sequencer.c: static int pick_commits(struct repository *r,\n       \t\t\t       get_item_line_length(todo_list,\n       \t\t\t\t\t\t    todo_list->current),\n       \t\t\t       get_item_line(todo_list, todo_list->current));\n      -\t\t\ttodo_list->current--;\n      -\t\t\tif (save_todo(todo_list, opts))\n     -+\t\t\tif (save_todo(todo_list, opts, 1))\n     ++\t\t\tif (save_todo(todo_list, opts, reschedule))\n       \t\t\t\treturn -1;\n       \t\t\tif (item->commit)\n     - \t\t\t\treturn error_with_patch(r,\n     + \t\t\t\twrite_rebase_head(&item->commit->object.oid);\n      \n       ## t/t3404-rebase-interactive.sh ##\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'todo count' '\n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'todo count' '\n      +\thead -n3 todo >expect &&\n      +\ttest_cmp expect .git/rebase-merge/done &&\n      +\trm file2 &&\n     + \ttest_path_is_missing .git/rebase-merge/author-script &&\n     + \ttest_path_is_missing .git/rebase-merge/patch &&\n     + \ttest_path_is_missing .git/MERGE_MSG &&\n     +@@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n     + \tgrep \"error: you have staged changes in your working tree\" err &&\n     + \tgit reset --hard HEAD &&\n       \tgit rebase --continue &&\n      -\ttest_cmp_rev HEAD I\n      +\ttest_cmp_rev HEAD D &&\n\n-- \ngitgitgadget\n"},{"id":"475786","messageId":"227aea031b588977f22f3f97faee981d79ade05c.1682089074.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"[PATCH v2 2/6] rebase -i: remove patch file after conflict resolution","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T14:57:50Z","receivedAt":"2023-04-21T14:58:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen rebase stops for the user to resolve conflicts it writes a patch\nfor the conflicting commit to .git/rebase-merge/patch. This file\nshould be deleted when the rebase continues. As the path is now used\nin two different places rebase_path_patch() is added and used to\nobtain the path for the patch.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 9 +++++++--\n 1 file changed, 7 insertions(+), 2 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 5073ec5902b..c4a548f2c98 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -132,6 +132,11 @@ static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n  * the commit object name of the corresponding patch.\n  */\n static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n+/*\n+ * When we stop for the user to resolve conflicts this file contains\n+ * the patch of the commit that is being picked.\n+ */\n+static GIT_PATH_FUNC(rebase_path_patch, \"rebase-merge/patch\")\n /*\n  * For the post-rewrite hook, we make a list of rewritten commits and\n  * their new sha1s.  The rewritten-pending list keeps the sha1s of\n@@ -3490,7 +3495,6 @@ static int make_patch(struct repository *r,\n \t\treturn -1;\n \tres |= write_rebase_head(&commit->object.oid);\n \n-\tstrbuf_addf(&buf, \"%s/patch\", get_dir(opts));\n \tmemset(&log_tree_opt, 0, sizeof(log_tree_opt));\n \trepo_init_revisions(r, &log_tree_opt, NULL);\n \tlog_tree_opt.abbrev = 0;\n@@ -3498,7 +3502,7 @@ static int make_patch(struct repository *r,\n \tlog_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;\n \tlog_tree_opt.disable_stdin = 1;\n \tlog_tree_opt.no_commit_id = 1;\n-\tlog_tree_opt.diffopt.file = fopen(buf.buf, \"w\");\n+\tlog_tree_opt.diffopt.file = fopen(rebase_path_patch(), \"w\");\n \tlog_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;\n \tif (!log_tree_opt.diffopt.file)\n \t\tres |= error_errno(_(\"could not open '%s'\"), buf.buf);\n@@ -4642,6 +4646,7 @@ static int pick_commits(struct repository *r,\n \tunlink(rebase_path_message());\n \tunlink(rebase_path_stopped_sha());\n \tunlink(rebase_path_amend());\n+\tunlink(rebase_path_patch());\n \n \twhile (todo_list->current < todo_list->nr) {\n \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n-- \ngitgitgadget\n\n"},{"id":"475788","messageId":"31bb644e769a085bd2db97cdb5aae78729efc52d.1682089075.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"[PATCH v2 3/6] sequencer: factor out part of pick_commits()","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T14:57:51Z","receivedAt":"2023-04-21T14:58:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis is simplifies a change in a later commit. If a pick fails we now\nreturn the error at then end of the loop body rather than returning\nearly but there is no change in behavior.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 129 ++++++++++++++++++++++++++++------------------------\n 1 file changed, 69 insertions(+), 60 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex c4a548f2c98..2d463818dd1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4628,6 +4628,72 @@ N_(\"Could not execute the todo command\\n\"\n \"    git rebase --edit-todo\\n\"\n \"    git rebase --continue\\n\");\n \n+static int pick_one_commit(struct repository *r,\n+\t\t\t   struct todo_list *todo_list,\n+\t\t\t   struct replay_opts *opts,\n+\t\t\t   int *check_todo)\n+{\n+\tint res;\n+\tstruct todo_item *item = todo_list->items + todo_list->current;\n+\tconst char *arg = todo_item_get_arg(todo_list, item);\n+\tif (is_rebase_i(opts))\n+\t\topts->reflog_message = reflog_message(\n+\t\t\topts, command_to_string(item->command), NULL);\n+\n+\tres = do_pick_commit(r, item, opts, is_final_fixup(todo_list),\n+\t\t\t     check_todo);\n+\tif (is_rebase_i(opts) && res < 0) {\n+\t\t/* Reschedule */\n+\t\tadvise(_(rescheduled_advice),\n+\t\t       get_item_line_length(todo_list, todo_list->current),\n+\t\t       get_item_line(todo_list, todo_list->current));\n+\t\ttodo_list->current--;\n+\t\tif (save_todo(todo_list, opts))\n+\t\t\treturn -1;\n+\t}\n+\tif (item->command == TODO_EDIT) {\n+\t\tstruct commit *commit = item->commit;\n+\t\tif (!res) {\n+\t\t\tif (!opts->verbose)\n+\t\t\t\tterm_clear_line();\n+\t\t\tfprintf(stderr, _(\"Stopped at %s...  %.*s\\n\"),\n+\t\t\t\tshort_commit_name(commit), item->arg_len, arg);\n+\t\t}\n+\t\treturn error_with_patch(r, commit,\n+\t\t\t\t\targ, item->arg_len, opts, res, !res);\n+\t}\n+\tif (is_rebase_i(opts) && !res)\n+\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t    peek_command(todo_list, 1));\n+\tif (res && is_fixup(item->command)) {\n+\t\tif (res == 1)\n+\t\t\tintend_to_amend();\n+\t\treturn error_failed_squash(r, item->commit, opts,\n+\t\t\t\t\t   item->arg_len, arg);\n+\t} else if (res && is_rebase_i(opts) && item->commit) {\n+\t\tint to_amend = 0;\n+\t\tstruct object_id oid;\n+\n+\t\t/*\n+\t\t * If we are rewording and have either\n+\t\t * fast-forwarded already, or are about to\n+\t\t * create a new root commit, we want to amend,\n+\t\t * otherwise we do not.\n+\t\t */\n+\t\tif (item->command == TODO_REWORD &&\n+\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n+\t\t    (oideq(&item->commit->object.oid, &oid) ||\n+\t\t     (opts->have_squash_onto &&\n+\t\t      oideq(&opts->squash_onto, &oid))))\n+\t\t\tto_amend = 1;\n+\n+\t\treturn res | error_with_patch(r, item->commit,\n+\t\t\t\t\t      arg, item->arg_len, opts,\n+\t\t\t\t\t      res, to_amend);\n+\t}\n+\treturn res;\n+}\n+\n static int pick_commits(struct repository *r,\n \t\t\tstruct todo_list *todo_list,\n \t\t\tstruct replay_opts *opts)\n@@ -4683,66 +4749,9 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n-\t\t\tif (is_rebase_i(opts))\n-\t\t\t\topts->reflog_message = reflog_message(opts,\n-\t\t\t\t      command_to_string(item->command), NULL);\n-\n-\t\t\tres = do_pick_commit(r, item, opts,\n-\t\t\t\t\t     is_final_fixup(todo_list),\n-\t\t\t\t\t     &check_todo);\n-\t\t\tif (is_rebase_i(opts) && res < 0) {\n-\t\t\t\t/* Reschedule */\n-\t\t\t\tadvise(_(rescheduled_advice),\n-\t\t\t\t       get_item_line_length(todo_list,\n-\t\t\t\t\t\t\t    todo_list->current),\n-\t\t\t\t       get_item_line(todo_list,\n-\t\t\t\t\t\t     todo_list->current));\n-\t\t\t\ttodo_list->current--;\n-\t\t\t\tif (save_todo(todo_list, opts))\n-\t\t\t\t\treturn -1;\n-\t\t\t}\n-\t\t\tif (item->command == TODO_EDIT) {\n-\t\t\t\tstruct commit *commit = item->commit;\n-\t\t\t\tif (!res) {\n-\t\t\t\t\tif (!opts->verbose)\n-\t\t\t\t\t\tterm_clear_line();\n-\t\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\t_(\"Stopped at %s...  %.*s\\n\"),\n-\t\t\t\t\t\tshort_commit_name(commit),\n-\t\t\t\t\t\titem->arg_len, arg);\n-\t\t\t\t}\n-\t\t\t\treturn error_with_patch(r, commit,\n-\t\t\t\t\targ, item->arg_len, opts, res, !res);\n-\t\t\t}\n-\t\t\tif (is_rebase_i(opts) && !res)\n-\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n-\t\t\t\t\tpeek_command(todo_list, 1));\n-\t\t\tif (res && is_fixup(item->command)) {\n-\t\t\t\tif (res == 1)\n-\t\t\t\t\tintend_to_amend();\n-\t\t\t\treturn error_failed_squash(r, item->commit, opts,\n-\t\t\t\t\titem->arg_len, arg);\n-\t\t\t} else if (res && is_rebase_i(opts) && item->commit) {\n-\t\t\t\tint to_amend = 0;\n-\t\t\t\tstruct object_id oid;\n-\n-\t\t\t\t/*\n-\t\t\t\t * If we are rewording and have either\n-\t\t\t\t * fast-forwarded already, or are about to\n-\t\t\t\t * create a new root commit, we want to amend,\n-\t\t\t\t * otherwise we do not.\n-\t\t\t\t */\n-\t\t\t\tif (item->command == TODO_REWORD &&\n-\t\t\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n-\t\t\t\t    (oideq(&item->commit->object.oid, &oid) ||\n-\t\t\t\t     (opts->have_squash_onto &&\n-\t\t\t\t      oideq(&opts->squash_onto, &oid))))\n-\t\t\t\t\tto_amend = 1;\n-\n-\t\t\t\treturn res | error_with_patch(r, item->commit,\n-\t\t\t\t\t\targ, item->arg_len, opts,\n-\t\t\t\t\t\tres, to_amend);\n-\t\t\t}\n+\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n+\t\t\tif (!res && item->command == TODO_EDIT)\n+\t\t\t\treturn 0;\n \t\t} else if (item->command == TODO_EXEC) {\n \t\t\tchar *end_of_arg = (char *)(arg + item->arg_len);\n \t\t\tint saved = *end_of_arg;\n-- \ngitgitgadget\n\n"},{"id":"475789","messageId":"9356d14b09a468d8ef2884cd7d76e59ec5c16691.1682089075.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"[PATCH v2 4/6] rebase --continue: refuse to commit after failed command","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T14:57:52Z","receivedAt":"2023-04-21T14:58:19Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf a commit cannot be picked because it would overwrite an untracked\nfile then \"git rebase --continue\" should refuse to commit any staged\nchanges as the commit was not picked. Do this by using the existing\ncheck for a missing author script in run_git_commit() which prevents\n\"rebase --continue\" from committing staged changes after failed exec\ncommands.\n\nWhen fast-forwarding it is not necessary to write the author script as\nwe're reusing an existing commit, not creating a new one. If a\nfast-forwarded commit is modified by an \"edit\" or \"reword\" command then\nthe modification is committed with \"git commit --amend\" which reuses the\nauthor of the commit being amended so the author script is not needed.\nbaf8ec8d3a (rebase -r: don't write .git/MERGE_MSG when fast-forwarding,\n2021-08-20) changed run_git_commit() to allow a missing author script\nwhen rewording a commit. This changes extends that to allow a missing\nauthor script whenever the commit is being amended.\n\nIf we're not fast-forwarding then we must remove the author script if\nthe pick fails.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   | 10 +++++-----\n t/t3404-rebase-interactive.sh |  8 ++++++++\n t/t3430-rebase-merges.sh      |  4 +++-\n 3 files changed, 16 insertions(+), 6 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 2d463818dd1..55bf0a72c3a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1055,7 +1055,7 @@ static int run_git_commit(const char *defmsg,\n \n \tif (is_rebase_i(opts) &&\n \t    ((opts->committer_date_is_author_date && !opts->ignore_date) ||\n-\t     !(!defmsg && (flags & AMEND_MSG))) &&\n+\t     !(flags & AMEND_MSG)) &&\n \t    read_env_script(&cmd.env)) {\n \t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n \n@@ -2216,8 +2216,6 @@ static int do_pick_commit(struct repository *r,\n \tif (opts->allow_ff && !is_fixup(command) &&\n \t    ((parent && oideq(&parent->object.oid, &head)) ||\n \t     (!parent && unborn))) {\n-\t\tif (is_rebase_i(opts))\n-\t\t\twrite_author_script(msg.message);\n \t\tres = fast_forward_to(r, &commit->object.oid, &head, unborn,\n \t\t\topts);\n \t\tif (res || command != TODO_REWORD)\n@@ -2324,9 +2322,10 @@ static int do_pick_commit(struct repository *r,\n \t\t command == TODO_REVERT) {\n \t\tres = do_recursive_merge(r, base, next, base_label, next_label,\n \t\t\t\t\t &head, &msgbuf, opts);\n-\t\tif (res < 0)\n+\t\tif (res < 0) {\n+\t\t\tunlink(rebase_path_author_script());\n \t\t\tgoto leave;\n-\n+\t\t}\n \t\tres |= write_message(msgbuf.buf, msgbuf.len,\n \t\t\t\t     git_path_merge_msg(r), 0);\n \t} else {\n@@ -4141,6 +4140,7 @@ static int do_merge(struct repository *r,\n \tif (ret < 0) {\n \t\terror(_(\"could not even attempt to merge '%.*s'\"),\n \t\t      merge_arg_len, arg);\n+\t\tunlink(rebase_path_author_script());\n \t\tgoto leave_merge;\n \t}\n \t/*\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ff0afad63e2..c1fe55dc2c1 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1288,6 +1288,12 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \ttest_must_fail git rebase --continue &&\n \ttest_cmp_rev HEAD F &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/author-script &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n+\tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n \ttest_cmp_rev HEAD I\n '\n@@ -1306,6 +1312,7 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n \ttest_must_fail git rebase --continue &&\n \ttest_cmp_rev HEAD F &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/author-script &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n \tgit reset --hard original-branch2\n@@ -1324,6 +1331,7 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n \ttest_must_fail git rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = F &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/author-script &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex f03599c63b9..360ec787ffd 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -168,13 +168,15 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n \ttest_path_is_file .git/rebase-merge/patch &&\n+\ttest_path_is_missing .git/rebase-merge/author-script &&\n \n \t: fail because of merge conflict &&\n \trm G.t .git/rebase-merge/patch &&\n \tgit reset --hard conflicting-G &&\n \ttest_must_fail git rebase --continue &&\n \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n-\ttest_path_is_file .git/rebase-merge/patch\n+\ttest_path_is_file .git/rebase-merge/patch &&\n+\ttest_path_is_file .git/rebase-merge/author-script\n '\n \n test_expect_success 'failed `merge <branch>` does not crash' '\n-- \ngitgitgadget\n\n"},{"id":"475790","messageId":"f8e64c1b631116367e6e68fcfde711b507a03a94.1682089075.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"[PATCH v2 5/6] rebase: fix rewritten list for failed pick","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T14:57:53Z","receivedAt":"2023-04-21T14:58:22Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen rebasing commands are moved from the todo list in \"git-rebase-todo\"\nto the \"done\" file just before they are executed. This means that if a\ncommand fails because it would overwrite an untracked file it has to be\nadded back into the todo list before the rebase stops for the user to\nfix the problem. Unfortunately the way this is done results in the\nfailed pick being recorded as rewritten.\n\nFix this by not calling error_with_patch() for failed commands. The pick\nhas failed so there is nothing to commit and therefore we do not want to\nset up the message file for committing staged changes when the rebase\ncontinues. This change means we no-longer write a patch for the failed\ncommand or display the error message printed by error_with_patch(). As\nthe command has failed the patch isn't really useful in that case and\nREBASE_HEAD is still written so the user can inspect the commit\nassociated with the failed command. Unless the user has disabled it we\nprint an advice message that is more helpful than the message from\nerror_with_patch(). If the advice is disabled the user will still see\nthe messages from the merge machinery detailing the problem.\n\nTo simplify writing REBASE_HEAD in this case pick_one_commit() is\nmodified to avoid duplicating the code that adds the failed command back\ninto the todo list.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   | 19 +++++++------------\n t/t3404-rebase-interactive.sh | 12 ++++++++++++\n t/t3430-rebase-merges.sh      | 11 ++++++++---\n t/t5407-post-rewrite-hook.sh  | 11 +++++++++++\n 4 files changed, 38 insertions(+), 15 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 55bf0a72c3a..db2daecb23e 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4141,6 +4141,7 @@ static int do_merge(struct repository *r,\n \t\terror(_(\"could not even attempt to merge '%.*s'\"),\n \t\t      merge_arg_len, arg);\n \t\tunlink(rebase_path_author_script());\n+\t\tunlink(git_path_merge_msg(r));\n \t\tgoto leave_merge;\n \t}\n \t/*\n@@ -4631,7 +4632,7 @@ N_(\"Could not execute the todo command\\n\"\n static int pick_one_commit(struct repository *r,\n \t\t\t   struct todo_list *todo_list,\n \t\t\t   struct replay_opts *opts,\n-\t\t\t   int *check_todo)\n+\t\t\t   int *check_todo, int* reschedule)\n {\n \tint res;\n \tstruct todo_item *item = todo_list->items + todo_list->current;\n@@ -4644,12 +4645,8 @@ static int pick_one_commit(struct repository *r,\n \t\t\t     check_todo);\n \tif (is_rebase_i(opts) && res < 0) {\n \t\t/* Reschedule */\n-\t\tadvise(_(rescheduled_advice),\n-\t\t       get_item_line_length(todo_list, todo_list->current),\n-\t\t       get_item_line(todo_list, todo_list->current));\n-\t\ttodo_list->current--;\n-\t\tif (save_todo(todo_list, opts))\n-\t\t\treturn -1;\n+\t\t*reschedule = 1;\n+\t\treturn -1;\n \t}\n \tif (item->command == TODO_EDIT) {\n \t\tstruct commit *commit = item->commit;\n@@ -4749,7 +4746,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n-\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n+\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo,\n+\t\t\t\t\t      &reschedule);\n \t\t\tif (!res && item->command == TODO_EDIT)\n \t\t\t\treturn 0;\n \t\t} else if (item->command == TODO_EXEC) {\n@@ -4803,10 +4801,7 @@ static int pick_commits(struct repository *r,\n \t\t\tif (save_todo(todo_list, opts))\n \t\t\t\treturn -1;\n \t\t\tif (item->commit)\n-\t\t\t\treturn error_with_patch(r,\n-\t\t\t\t\t\t\titem->commit,\n-\t\t\t\t\t\t\targ, item->arg_len,\n-\t\t\t\t\t\t\topts, res, 0);\n+\t\t\t\twrite_rebase_head(&item->commit->object.oid);\n \t\t} else if (is_rebase_i(opts) && check_todo && !res &&\n \t\t\t   reread_todo_if_changed(r, todo_list, opts)) {\n \t\t\treturn -1;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex c1fe55dc2c1..a657167befd 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1289,6 +1289,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \ttest_cmp_rev HEAD F &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/author-script &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n+\ttest_path_is_missing .git/MERGE_MSG &&\n+\ttest_path_is_missing .git/rebase-merge/message &&\n+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n \techo changed >file1 &&\n \tgit add file1 &&\n \ttest_must_fail git rebase --continue 2>err &&\n@@ -1313,6 +1317,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n \ttest_cmp_rev HEAD F &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/author-script &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n+\ttest_path_is_missing .git/MERGE_MSG &&\n+\ttest_path_is_missing .git/rebase-merge/message &&\n+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n \tgit reset --hard original-branch2\n@@ -1332,6 +1340,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = F &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/author-script &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n+\ttest_path_is_missing .git/MERGE_MSG &&\n+\ttest_path_is_missing .git/rebase-merge/message &&\n+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 360ec787ffd..18a0bc8fafb 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -167,16 +167,21 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n \ttest_must_fail git rebase -ir HEAD &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n-\ttest_path_is_file .git/rebase-merge/patch &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \ttest_path_is_missing .git/rebase-merge/author-script &&\n+\ttest_path_is_missing .git/MERGE_MSG &&\n+\ttest_path_is_missing .git/rebase-merge/message &&\n+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n \n \t: fail because of merge conflict &&\n-\trm G.t .git/rebase-merge/patch &&\n \tgit reset --hard conflicting-G &&\n \ttest_must_fail git rebase --continue &&\n \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n \ttest_path_is_file .git/rebase-merge/patch &&\n-\ttest_path_is_file .git/rebase-merge/author-script\n+\ttest_path_is_file .git/rebase-merge/author-script &&\n+\ttest_path_is_file .git/MERGE_MSG &&\n+\ttest_path_is_file .git/rebase-merge/message &&\n+\ttest_path_is_file .git/rebase-merge/stopped-sha\n '\n \n test_expect_success 'failed `merge <branch>` does not crash' '\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 5f3ff051ca2..c490a5137fe 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -173,6 +173,17 @@ test_fail_interactive_rebase () {\n \t)\n }\n \n+test_expect_success 'git rebase with failed pick' '\n+\ttest_fail_interactive_rebase \"exec_>bar pick 1\" --onto C A E &&\n+\trm bar &&\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<-EOF &&\n+\t$(git rev-parse E) $(git rev-parse HEAD)\n+\tEOF\n+\tverify_hook_input\n+'\n+\n test_expect_success 'git rebase -i (unchanged)' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-- \ngitgitgadget\n\n"},{"id":"475791","messageId":"a836b049b900fa9d7c03ed5426a28b5cc754d4c5.1682089075.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"[PATCH v2 6/6] rebase -i: fix adding failed command to the todo list","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T14:57:54Z","receivedAt":"2023-04-21T14:58:24Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen rebasing commands are moved from the todo list in \"git-rebase-todo\"\nto the \"done\" file (which is used by \"git status\" to show the recently\nexecuted commands) just before they are executed. This means that if a\ncommand fails because it would overwrite an untracked file it has to be\nadded back into the todo list before the rebase stops for the user to\nfix the problem.\n\nUnfortunately when a failed command is added back into the todo list\nthe command preceding it is erroneously appended to the \"done\" file.\nThis means that when rebase stops after \"pick B\" fails the \"done\"\nfile contains\n\n\tpick A\n\tpick B\n\tpick A\n\ninstead of\n\n\tpick A\n\tpick B\n\nFix this by not updating the \"done\" file when adding a failed command\nback into the \"git-rebase-todo\" file. A couple of the existing tests are\nmodified to improve their coverage as none of them trigger this bug or\ncheck the \"done\" file.\n\nReported-by: Stefan Haller <lists@haller-berlin.de>\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   | 12 ++++++------\n t/t3404-rebase-interactive.sh | 27 +++++++++++++++++----------\n t/t3430-rebase-merges.sh      | 22 ++++++++++++++++------\n 3 files changed, 39 insertions(+), 22 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex db2daecb23e..9769dde00e8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3379,7 +3379,8 @@ give_advice:\n \treturn -1;\n }\n \n-static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n+static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,\n+\t\t     int reschedule)\n {\n \tstruct lock_file todo_lock = LOCK_INIT;\n \tconst char *todo_path = get_todo_path(opts);\n@@ -3389,7 +3390,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \t * rebase -i writes \"git-rebase-todo\" without the currently executing\n \t * command, appending it to \"done\" instead.\n \t */\n-\tif (is_rebase_i(opts))\n+\tif (is_rebase_i(opts) && !reschedule)\n \t\tnext++;\n \n \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n@@ -3402,7 +3403,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tif (commit_lock_file(&todo_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n \n-\tif (is_rebase_i(opts) && next > 0) {\n+\tif (is_rebase_i(opts) && !reschedule && next > 0) {\n \t\tconst char *done = rebase_path_done();\n \t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n \t\tint ret = 0;\n@@ -4716,7 +4717,7 @@ static int pick_commits(struct repository *r,\n \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n \t\tint check_todo = 0;\n \n-\t\tif (save_todo(todo_list, opts))\n+\t\tif (save_todo(todo_list, opts, reschedule))\n \t\t\treturn -1;\n \t\tif (is_rebase_i(opts)) {\n \t\t\tif (item->command != TODO_COMMENT) {\n@@ -4797,8 +4798,7 @@ static int pick_commits(struct repository *r,\n \t\t\t       get_item_line_length(todo_list,\n \t\t\t\t\t\t    todo_list->current),\n \t\t\t       get_item_line(todo_list, todo_list->current));\n-\t\t\ttodo_list->current--;\n-\t\t\tif (save_todo(todo_list, opts))\n+\t\t\tif (save_todo(todo_list, opts, reschedule))\n \t\t\t\treturn -1;\n \t\t\tif (item->commit)\n \t\t\t\twrite_rebase_head(&item->commit->object.oid);\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex a657167befd..653c19bc9c8 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1276,18 +1276,23 @@ test_expect_success 'todo count' '\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n-\tgit checkout --force branch2 &&\n+\tgit checkout --force A &&\n \tgit clean -f &&\n+\tcat >todo <<-EOF &&\n+\texec >file2\n+\tpick $(git rev-parse B) B\n+\tpick $(git rev-parse C) C\n+\tpick $(git rev-parse D) D\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \t(\n-\t\tset_fake_editor &&\n-\t\tFAKE_LINES=\"edit 1 2\" git rebase -i A\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i A\n \t) &&\n-\ttest_cmp_rev HEAD F &&\n-\ttest_path_is_missing file6 &&\n-\t>file6 &&\n-\ttest_must_fail git rebase --continue &&\n-\ttest_cmp_rev HEAD F &&\n-\trm file6 &&\n+\ttest_cmp_rev HEAD B &&\n+\thead -n3 todo >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm file2 &&\n \ttest_path_is_missing .git/rebase-merge/author-script &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n \ttest_path_is_missing .git/MERGE_MSG &&\n@@ -1299,7 +1304,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \tgrep \"error: you have staged changes in your working tree\" err &&\n \tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n-\ttest_cmp_rev HEAD I\n+\ttest_cmp_rev HEAD D &&\n+\ttail -n3 todo >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (squash)' '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 18a0bc8fafb..86f4e0e4d6f 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -128,14 +128,24 @@ test_expect_success 'generate correct todo list' '\n '\n \n test_expect_success '`reset` refuses to overwrite untracked files' '\n-\tgit checkout -b refuse-to-reset &&\n+\tgit checkout B &&\n \ttest_commit dont-overwrite-untracked &&\n-\tgit checkout @{-1} &&\n-\t: >dont-overwrite-untracked.t &&\n-\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n+\tcat >script-from-scratch <<-EOF &&\n+\texec >dont-overwrite-untracked.t\n+\tpick $(git rev-parse B) B\n+\treset refs/tags/dont-overwrite-untracked\n+\tpick $(git rev-parse C) C\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n-\ttest_must_fail git rebase -ir HEAD &&\n-\tgit rebase --abort\n+\ttest_must_fail git rebase -ir A &&\n+\ttest_cmp_rev HEAD B &&\n+\thead -n3 script-from-scratch >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm dont-overwrite-untracked.t &&\n+\tgit rebase --continue &&\n+\ttail -n3 script-from-scratch >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success '`reset` rejects trees' '\n-- \ngitgitgadget\n"},{"id":"475794","messageId":"xmqqcz3xkwbv.fsf@gitster.g","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/6] rebase -i: impove handling of failed commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-21T16:56:52Z","receivedAt":"2023-04-21T16:58:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series fixes several bugs in the way we handle a commit cannot be\n> picked because it would overwrite an untracked file.\n>\n>  * after a failed pick \"git rebase --continue\" will happily commit any\n>    staged changes even though no commit was picked.\n>\n>  * the commit of the failed pick is recorded as rewritten even though no\n>    commit was picked.\n>\n>  * the \"done\" file used by \"git status\" to show the recently executed\n>    commands contains an incorrect entry.\n>\n> Thanks for the comments on V1, this series has now grown somewhat.\n> Previously I was worried that refactoring would change the behavior, but\n> having thought about it the current behavior is wrong and should be changed.\n\nSo much has changed since the original one, I did not even recognise\nthis was a redoing of the \"rescheduled step\" topic (which I do not\nthink I have kept in 'seen').  Building on top of the more recent\n'master' like you did here is very much appreciated.\n"},{"id":"475795","messageId":"xmqq8relkv52.fsf@gitster.g","threadId":"59416","inReplyTo":"3dfb2c6903bcc61258c72ba5c8e4201c9db2665b.1682089074.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/6] rebase -i: move unlink() calls","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-21T17:22:33Z","receivedAt":"2023-04-21T17:22:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> At the start of each iteration the loop that picks commits removes\n> state files from the previous pick. However some of these are only\n> written if there are conflicts so only need to be removed before\n> starting the loop, not in each iteration.\n\nI do not doubt your reasoning is correct, but could you explain this\na bit better?\n\nI think the reason why others, e.g. author-script, need to be\nremoved on every iteration is because the previous iteration that\ncalled do_pick_commit() can come back successfully after calling\nwrite_author_script(), and we would want to clear the deck before\ngoing into the next iteration, so I can guess that you meant by \"if\nthere are conflicts\" that the loop will not iterate to the next step\nafter conflicts happened (and these files like \"amend\" and\n\"stopped-sha\" may have been written)?  The latter, i.e. the loop\nwill not iterate any further, is the more direct reason to justify\nthis change, I think, and it would help readers of \"git log\" to say\nso, instead of forcing them to infer \"are conflicts\" imply \"hence\nloop will stop\".\n\nIs this a pure clean-up, or will there be behaviour change?  I do\nnot think there is with this patch alone, but does this change make\nfuture steps easier to understand or something?\n\nIOW, the proposed log message may explain why this is not a wrong\nchange to make, but it is unclear why this is a good change we want\nto have in this part of the series.\n\nThanks.\n\n> diff --git a/sequencer.c b/sequencer.c\n> index d2c7698c48c..5073ec5902b 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4639,6 +4639,10 @@ static int pick_commits(struct repository *r,\n>  \tif (read_and_refresh_cache(r, opts))\n>  \t\treturn -1;\n>  \n> +\tunlink(rebase_path_message());\n> +\tunlink(rebase_path_stopped_sha());\n> +\tunlink(rebase_path_amend());\n> +\n>  \twhile (todo_list->current < todo_list->nr) {\n>  \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n>  \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n> @@ -4662,10 +4666,7 @@ static int pick_commits(struct repository *r,\n>  \t\t\t\t\t\ttodo_list->total_nr,\n>  \t\t\t\t\t\topts->verbose ? \"\\n\" : \"\\r\");\n>  \t\t\t}\n> -\t\t\tunlink(rebase_path_message());\n>  \t\t\tunlink(rebase_path_author_script());\n> -\t\t\tunlink(rebase_path_stopped_sha());\n> -\t\t\tunlink(rebase_path_amend());\n>  \t\t\tunlink(git_path_merge_head(r));\n>  \t\t\tunlink(git_path_auto_merge(r));\n>  \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n"},{"id":"475797","messageId":"xmqqwn25jbzw.fsf@gitster.g","threadId":"59416","inReplyTo":"227aea031b588977f22f3f97faee981d79ade05c.1682089074.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/6] rebase -i: remove patch file after conflict resolution","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-21T19:01:23Z","receivedAt":"2023-04-21T19:01:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> When rebase stops for the user to resolve conflicts it writes a patch\n> for the conflicting commit to .git/rebase-merge/patch. This file\n> should be deleted when the rebase continues.\n\nCould you describe the reason why this file \"should\" be deleted a\nbit better?  Once the user edits the files in the working tree and\ntell \"git rebase\" with the \"--continue\" option that they finished\nhelping the command, and the command creates a commit out of the\nresolution left by the user in the working tree and in the index,\nthe patch may no longer is needed, so I can understand if this were\n\"this file can be deleted\"---in other words, again, this explains\nwhy such a change would not be a wrong change that hurts the users,\nbut it does not explain why we want such a change very well.  Is\nthere a reason why a left-over patch file is a bad thing (perhaps\ncausing end-user confusion upon seeing such a patch that apparently\nis for a much earlier step in the rebase in progress?  If so, that\nmight be a good justification to say we \"should\").\n\n> As the path is now used\n> in two different places rebase_path_patch() is added and used to\n> obtain the path for the patch.\n\nOK.\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  sequencer.c | 9 +++++++--\n>  1 file changed, 7 insertions(+), 2 deletions(-)\n\nThe patch text itself looks good in the sense that it correctly\nimplements what the proposed log message claims it \"should\".\n\nThanks.\n"},{"id":"475798","messageId":"CAPig+cT8fccACXvOfd7hFnogy1x7hisb1K-BY+1aVJd2VUXHwQ@mail.gmail.com","threadId":"59416","inReplyTo":"31bb644e769a085bd2db97cdb5aae78729efc52d.1682089075.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/6] sequencer: factor out part of pick_commits()","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-04-21T19:12:58Z","receivedAt":"2023-04-21T19:13:15Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Apr 21, 2023 at 11:11 AM Phillip Wood via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> This is simplifies a change in a later commit. If a pick fails we now\n\ns/This is/This/\n\n> return the error at then end of the loop body rather than returning\n\ns/then end/the end/\n\n> early but there is no change in behavior.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n"},{"id":"475814","messageId":"CAPig+cQ2z2jq05S1QxboBT-51gVSqsJsWTfGKRN6PPYxW=ZzmA@mail.gmail.com","threadId":"59416","inReplyTo":"9356d14b09a468d8ef2884cd7d76e59ec5c16691.1682089075.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/6] rebase --continue: refuse to commit after failed command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-04-21T19:14:49Z","receivedAt":"2023-04-21T19:15:26Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Apr 21, 2023 at 11:12 AM Phillip Wood via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n> If a commit cannot be picked because it would overwrite an untracked\n> file then \"git rebase --continue\" should refuse to commit any staged\n> changes as the commit was not picked. Do this by using the existing\n> check for a missing author script in run_git_commit() which prevents\n> \"rebase --continue\" from committing staged changes after failed exec\n> commands.\n>\n> When fast-forwarding it is not necessary to write the author script as\n> we're reusing an existing commit, not creating a new one. If a\n> fast-forwarded commit is modified by an \"edit\" or \"reword\" command then\n> the modification is committed with \"git commit --amend\" which reuses the\n> author of the commit being amended so the author script is not needed.\n> baf8ec8d3a (rebase -r: don't write .git/MERGE_MSG when fast-forwarding,\n> 2021-08-20) changed run_git_commit() to allow a missing author script\n> when rewording a commit. This changes extends that to allow a missing\n\ns/changes/change/\n\n> author script whenever the commit is being amended.\n>\n> If we're not fast-forwarding then we must remove the author script if\n> the pick fails.\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n"},{"id":"475815","messageId":"xmqqpm7xjam2.fsf@gitster.g","threadId":"59416","inReplyTo":"31bb644e769a085bd2db97cdb5aae78729efc52d.1682089075.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/6] sequencer: factor out part of pick_commits()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-21T19:31:17Z","receivedAt":"2023-04-21T19:31:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This is simplifies a change in a later commit. If a pick fails we now\n> return the error at then end of the loop body rather than returning\n> early but there is no change in behavior.\n\nThe new pick_one_commit() function is pretty much verbatim copy/move\nfrom inside the \"any command below SQUASH\" block, and in the\noriginal code, the block returned with an error whenever res is not\n0, with one exception that TODO_EDIT would return with 0 if there is\nno error (but still with a patch).\n\nThe new code that calls pick_one_commit() helper lets this exception\ncase to return from the function in the \"any command below SQUASH\"\nblock, but everything else falls through *and* eventually at the end\nof the outer block there is\n\n\tif (res)\n\t\treturn res;\n\nthat makes us return from the function.\n\nBut there are now a few other things done after the if/else if/else\ncascade, namely\n\n * there is an extra \"if (reschedule)\" and \"else if (rebase-i) etc\"\n   logic.\n\n * the todo_list->current counter is incremented\n\nare done BEFORE that \"if res is not zero return\".  I am not sure we\ncan safely claim \"there is no change in behaviour\".  \n\nAm I missing something?\n\nThanks.\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  sequencer.c | 129 ++++++++++++++++++++++++++++------------------------\n>  1 file changed, 69 insertions(+), 60 deletions(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index c4a548f2c98..2d463818dd1 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4628,6 +4628,72 @@ N_(\"Could not execute the todo command\\n\"\n>  \"    git rebase --edit-todo\\n\"\n>  \"    git rebase --continue\\n\");\n>  \n> +static int pick_one_commit(struct repository *r,\n> +\t\t\t   struct todo_list *todo_list,\n> +\t\t\t   struct replay_opts *opts,\n> +\t\t\t   int *check_todo)\n> +{\n> +\tint res;\n> +\tstruct todo_item *item = todo_list->items + todo_list->current;\n> +\tconst char *arg = todo_item_get_arg(todo_list, item);\n> +\tif (is_rebase_i(opts))\n> +\t\topts->reflog_message = reflog_message(\n> +\t\t\topts, command_to_string(item->command), NULL);\n> +\n> +\tres = do_pick_commit(r, item, opts, is_final_fixup(todo_list),\n> +\t\t\t     check_todo);\n> +\tif (is_rebase_i(opts) && res < 0) {\n> +\t\t/* Reschedule */\n> +\t\tadvise(_(rescheduled_advice),\n> +\t\t       get_item_line_length(todo_list, todo_list->current),\n> +\t\t       get_item_line(todo_list, todo_list->current));\n> +\t\ttodo_list->current--;\n> +\t\tif (save_todo(todo_list, opts))\n> +\t\t\treturn -1;\n> +\t}\n> +\tif (item->command == TODO_EDIT) {\n> +\t\tstruct commit *commit = item->commit;\n> +\t\tif (!res) {\n> +\t\t\tif (!opts->verbose)\n> +\t\t\t\tterm_clear_line();\n> +\t\t\tfprintf(stderr, _(\"Stopped at %s...  %.*s\\n\"),\n> +\t\t\t\tshort_commit_name(commit), item->arg_len, arg);\n> +\t\t}\n> +\t\treturn error_with_patch(r, commit,\n> +\t\t\t\t\targ, item->arg_len, opts, res, !res);\n> +\t}\n> +\tif (is_rebase_i(opts) && !res)\n> +\t\trecord_in_rewritten(&item->commit->object.oid,\n> +\t\t\t\t    peek_command(todo_list, 1));\n> +\tif (res && is_fixup(item->command)) {\n> +\t\tif (res == 1)\n> +\t\t\tintend_to_amend();\n> +\t\treturn error_failed_squash(r, item->commit, opts,\n> +\t\t\t\t\t   item->arg_len, arg);\n> +\t} else if (res && is_rebase_i(opts) && item->commit) {\n> +\t\tint to_amend = 0;\n> +\t\tstruct object_id oid;\n> +\n> +\t\t/*\n> +\t\t * If we are rewording and have either\n> +\t\t * fast-forwarded already, or are about to\n> +\t\t * create a new root commit, we want to amend,\n> +\t\t * otherwise we do not.\n> +\t\t */\n> +\t\tif (item->command == TODO_REWORD &&\n> +\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n> +\t\t    (oideq(&item->commit->object.oid, &oid) ||\n> +\t\t     (opts->have_squash_onto &&\n> +\t\t      oideq(&opts->squash_onto, &oid))))\n> +\t\t\tto_amend = 1;\n> +\n> +\t\treturn res | error_with_patch(r, item->commit,\n> +\t\t\t\t\t      arg, item->arg_len, opts,\n> +\t\t\t\t\t      res, to_amend);\n> +\t}\n> +\treturn res;\n> +}\n> +\n>  static int pick_commits(struct repository *r,\n>  \t\t\tstruct todo_list *todo_list,\n>  \t\t\tstruct replay_opts *opts)\n> @@ -4683,66 +4749,9 @@ static int pick_commits(struct repository *r,\n>  \t\t\t}\n>  \t\t}\n>  \t\tif (item->command <= TODO_SQUASH) {\n> -\t\t\tif (is_rebase_i(opts))\n> -\t\t\t\topts->reflog_message = reflog_message(opts,\n> -\t\t\t\t      command_to_string(item->command), NULL);\n> -\n> -\t\t\tres = do_pick_commit(r, item, opts,\n> -\t\t\t\t\t     is_final_fixup(todo_list),\n> -\t\t\t\t\t     &check_todo);\n> -\t\t\tif (is_rebase_i(opts) && res < 0) {\n> -\t\t\t\t/* Reschedule */\n> -\t\t\t\tadvise(_(rescheduled_advice),\n> -\t\t\t\t       get_item_line_length(todo_list,\n> -\t\t\t\t\t\t\t    todo_list->current),\n> -\t\t\t\t       get_item_line(todo_list,\n> -\t\t\t\t\t\t     todo_list->current));\n> -\t\t\t\ttodo_list->current--;\n> -\t\t\t\tif (save_todo(todo_list, opts))\n> -\t\t\t\t\treturn -1;\n> -\t\t\t}\n> -\t\t\tif (item->command == TODO_EDIT) {\n> -\t\t\t\tstruct commit *commit = item->commit;\n> -\t\t\t\tif (!res) {\n> -\t\t\t\t\tif (!opts->verbose)\n> -\t\t\t\t\t\tterm_clear_line();\n> -\t\t\t\t\tfprintf(stderr,\n> -\t\t\t\t\t\t_(\"Stopped at %s...  %.*s\\n\"),\n> -\t\t\t\t\t\tshort_commit_name(commit),\n> -\t\t\t\t\t\titem->arg_len, arg);\n> -\t\t\t\t}\n> -\t\t\t\treturn error_with_patch(r, commit,\n> -\t\t\t\t\targ, item->arg_len, opts, res, !res);\n> -\t\t\t}\n> -\t\t\tif (is_rebase_i(opts) && !res)\n> -\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n> -\t\t\t\t\tpeek_command(todo_list, 1));\n> -\t\t\tif (res && is_fixup(item->command)) {\n> -\t\t\t\tif (res == 1)\n> -\t\t\t\t\tintend_to_amend();\n> -\t\t\t\treturn error_failed_squash(r, item->commit, opts,\n> -\t\t\t\t\titem->arg_len, arg);\n> -\t\t\t} else if (res && is_rebase_i(opts) && item->commit) {\n> -\t\t\t\tint to_amend = 0;\n> -\t\t\t\tstruct object_id oid;\n> -\n> -\t\t\t\t/*\n> -\t\t\t\t * If we are rewording and have either\n> -\t\t\t\t * fast-forwarded already, or are about to\n> -\t\t\t\t * create a new root commit, we want to amend,\n> -\t\t\t\t * otherwise we do not.\n> -\t\t\t\t */\n> -\t\t\t\tif (item->command == TODO_REWORD &&\n> -\t\t\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n> -\t\t\t\t    (oideq(&item->commit->object.oid, &oid) ||\n> -\t\t\t\t     (opts->have_squash_onto &&\n> -\t\t\t\t      oideq(&opts->squash_onto, &oid))))\n> -\t\t\t\t\tto_amend = 1;\n> -\n> -\t\t\t\treturn res | error_with_patch(r, item->commit,\n> -\t\t\t\t\t\targ, item->arg_len, opts,\n> -\t\t\t\t\t\tres, to_amend);\n> -\t\t\t}\n> +\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n> +\t\t\tif (!res && item->command == TODO_EDIT)\n> +\t\t\t\treturn 0;\n>  \t\t} else if (item->command == TODO_EXEC) {\n>  \t\t\tchar *end_of_arg = (char *)(arg + item->arg_len);\n>  \t\t\tint saved = *end_of_arg;\n"},{"id":"475816","messageId":"049a792d-e015-3583-452d-923b9aee4a72@gmail.com","threadId":"59416","inReplyTo":"xmqqpm7xjam2.fsf@gitster.g","subject":"Re: [PATCH v2 3/6] sequencer: factor out part of pick_commits()","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-04-21T20:00:47Z","receivedAt":"2023-04-21T20:01:00Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Junio\n\nOn 21/04/2023 20:31, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> This is simplifies a change in a later commit. If a pick fails we now\n>> return the error at then end of the loop body rather than returning\n>> early but there is no change in behavior.\n> \n> The new pick_one_commit() function is pretty much verbatim copy/move\n> from inside the \"any command below SQUASH\" block, and in the\n> original code, the block returned with an error whenever res is not\n> 0, with one exception that TODO_EDIT would return with 0 if there is\n> no error (but still with a patch).\n> \n> The new code that calls pick_one_commit() helper lets this exception\n> case to return from the function in the \"any command below SQUASH\"\n> block, but everything else falls through *and* eventually at the end\n> of the outer block there is\n> \n> \tif (res)\n> \t\treturn res;\n> \n> that makes us return from the function.\n> \n> But there are now a few other things done after the if/else if/else\n> cascade, namely\n> \n>   * there is an extra \"if (reschedule)\" and \"else if (rebase-i) etc\"\n>     logic.\n\nThere are two blocks that might be entered. One guarded by \"if \n(reschedule)\" - this is not entered because reschedlue is always zero \nwhen picking a commit. The other is guarded by \"else if \n(is_rebase_i(opts) && check_todo && !res)\" and so will not be entered \nwhen we want to return an error because \"res\" is non-zero in that case.\n\nBest Wishes\n\nPhillip\n>   * the todo_list->current counter is incremented\n> \n> are done BEFORE that \"if res is not zero return\".  I am not sure we\n> can safely claim \"there is no change in behaviour\".\n> \n> Am I missing something?\n> \n> Thanks.\n> \n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   sequencer.c | 129 ++++++++++++++++++++++++++++------------------------\n>>   1 file changed, 69 insertions(+), 60 deletions(-)\n>>\n>> diff --git a/sequencer.c b/sequencer.c\n>> index c4a548f2c98..2d463818dd1 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -4628,6 +4628,72 @@ N_(\"Could not execute the todo command\\n\"\n>>   \"    git rebase --edit-todo\\n\"\n>>   \"    git rebase --continue\\n\");\n>>   \n>> +static int pick_one_commit(struct repository *r,\n>> +\t\t\t   struct todo_list *todo_list,\n>> +\t\t\t   struct replay_opts *opts,\n>> +\t\t\t   int *check_todo)\n>> +{\n>> +\tint res;\n>> +\tstruct todo_item *item = todo_list->items + todo_list->current;\n>> +\tconst char *arg = todo_item_get_arg(todo_list, item);\n>> +\tif (is_rebase_i(opts))\n>> +\t\topts->reflog_message = reflog_message(\n>> +\t\t\topts, command_to_string(item->command), NULL);\n>> +\n>> +\tres = do_pick_commit(r, item, opts, is_final_fixup(todo_list),\n>> +\t\t\t     check_todo);\n>> +\tif (is_rebase_i(opts) && res < 0) {\n>> +\t\t/* Reschedule */\n>> +\t\tadvise(_(rescheduled_advice),\n>> +\t\t       get_item_line_length(todo_list, todo_list->current),\n>> +\t\t       get_item_line(todo_list, todo_list->current));\n>> +\t\ttodo_list->current--;\n>> +\t\tif (save_todo(todo_list, opts))\n>> +\t\t\treturn -1;\n>> +\t}\n>> +\tif (item->command == TODO_EDIT) {\n>> +\t\tstruct commit *commit = item->commit;\n>> +\t\tif (!res) {\n>> +\t\t\tif (!opts->verbose)\n>> +\t\t\t\tterm_clear_line();\n>> +\t\t\tfprintf(stderr, _(\"Stopped at %s...  %.*s\\n\"),\n>> +\t\t\t\tshort_commit_name(commit), item->arg_len, arg);\n>> +\t\t}\n>> +\t\treturn error_with_patch(r, commit,\n>> +\t\t\t\t\targ, item->arg_len, opts, res, !res);\n>> +\t}\n>> +\tif (is_rebase_i(opts) && !res)\n>> +\t\trecord_in_rewritten(&item->commit->object.oid,\n>> +\t\t\t\t    peek_command(todo_list, 1));\n>> +\tif (res && is_fixup(item->command)) {\n>> +\t\tif (res == 1)\n>> +\t\t\tintend_to_amend();\n>> +\t\treturn error_failed_squash(r, item->commit, opts,\n>> +\t\t\t\t\t   item->arg_len, arg);\n>> +\t} else if (res && is_rebase_i(opts) && item->commit) {\n>> +\t\tint to_amend = 0;\n>> +\t\tstruct object_id oid;\n>> +\n>> +\t\t/*\n>> +\t\t * If we are rewording and have either\n>> +\t\t * fast-forwarded already, or are about to\n>> +\t\t * create a new root commit, we want to amend,\n>> +\t\t * otherwise we do not.\n>> +\t\t */\n>> +\t\tif (item->command == TODO_REWORD &&\n>> +\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n>> +\t\t    (oideq(&item->commit->object.oid, &oid) ||\n>> +\t\t     (opts->have_squash_onto &&\n>> +\t\t      oideq(&opts->squash_onto, &oid))))\n>> +\t\t\tto_amend = 1;\n>> +\n>> +\t\treturn res | error_with_patch(r, item->commit,\n>> +\t\t\t\t\t      arg, item->arg_len, opts,\n>> +\t\t\t\t\t      res, to_amend);\n>> +\t}\n>> +\treturn res;\n>> +}\n>> +\n>>   static int pick_commits(struct repository *r,\n>>   \t\t\tstruct todo_list *todo_list,\n>>   \t\t\tstruct replay_opts *opts)\n>> @@ -4683,66 +4749,9 @@ static int pick_commits(struct repository *r,\n>>   \t\t\t}\n>>   \t\t}\n>>   \t\tif (item->command <= TODO_SQUASH) {\n>> -\t\t\tif (is_rebase_i(opts))\n>> -\t\t\t\topts->reflog_message = reflog_message(opts,\n>> -\t\t\t\t      command_to_string(item->command), NULL);\n>> -\n>> -\t\t\tres = do_pick_commit(r, item, opts,\n>> -\t\t\t\t\t     is_final_fixup(todo_list),\n>> -\t\t\t\t\t     &check_todo);\n>> -\t\t\tif (is_rebase_i(opts) && res < 0) {\n>> -\t\t\t\t/* Reschedule */\n>> -\t\t\t\tadvise(_(rescheduled_advice),\n>> -\t\t\t\t       get_item_line_length(todo_list,\n>> -\t\t\t\t\t\t\t    todo_list->current),\n>> -\t\t\t\t       get_item_line(todo_list,\n>> -\t\t\t\t\t\t     todo_list->current));\n>> -\t\t\t\ttodo_list->current--;\n>> -\t\t\t\tif (save_todo(todo_list, opts))\n>> -\t\t\t\t\treturn -1;\n>> -\t\t\t}\n>> -\t\t\tif (item->command == TODO_EDIT) {\n>> -\t\t\t\tstruct commit *commit = item->commit;\n>> -\t\t\t\tif (!res) {\n>> -\t\t\t\t\tif (!opts->verbose)\n>> -\t\t\t\t\t\tterm_clear_line();\n>> -\t\t\t\t\tfprintf(stderr,\n>> -\t\t\t\t\t\t_(\"Stopped at %s...  %.*s\\n\"),\n>> -\t\t\t\t\t\tshort_commit_name(commit),\n>> -\t\t\t\t\t\titem->arg_len, arg);\n>> -\t\t\t\t}\n>> -\t\t\t\treturn error_with_patch(r, commit,\n>> -\t\t\t\t\targ, item->arg_len, opts, res, !res);\n>> -\t\t\t}\n>> -\t\t\tif (is_rebase_i(opts) && !res)\n>> -\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n>> -\t\t\t\t\tpeek_command(todo_list, 1));\n>> -\t\t\tif (res && is_fixup(item->command)) {\n>> -\t\t\t\tif (res == 1)\n>> -\t\t\t\t\tintend_to_amend();\n>> -\t\t\t\treturn error_failed_squash(r, item->commit, opts,\n>> -\t\t\t\t\titem->arg_len, arg);\n>> -\t\t\t} else if (res && is_rebase_i(opts) && item->commit) {\n>> -\t\t\t\tint to_amend = 0;\n>> -\t\t\t\tstruct object_id oid;\n>> -\n>> -\t\t\t\t/*\n>> -\t\t\t\t * If we are rewording and have either\n>> -\t\t\t\t * fast-forwarded already, or are about to\n>> -\t\t\t\t * create a new root commit, we want to amend,\n>> -\t\t\t\t * otherwise we do not.\n>> -\t\t\t\t */\n>> -\t\t\t\tif (item->command == TODO_REWORD &&\n>> -\t\t\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n>> -\t\t\t\t    (oideq(&item->commit->object.oid, &oid) ||\n>> -\t\t\t\t     (opts->have_squash_onto &&\n>> -\t\t\t\t      oideq(&opts->squash_onto, &oid))))\n>> -\t\t\t\t\tto_amend = 1;\n>> -\n>> -\t\t\t\treturn res | error_with_patch(r, item->commit,\n>> -\t\t\t\t\t\targ, item->arg_len, opts,\n>> -\t\t\t\t\t\tres, to_amend);\n>> -\t\t\t}\n>> +\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n>> +\t\t\tif (!res && item->command == TODO_EDIT)\n>> +\t\t\t\treturn 0;\n>>   \t\t} else if (item->command == TODO_EXEC) {\n>>   \t\t\tchar *end_of_arg = (char *)(arg + item->arg_len);\n>>   \t\t\tint saved = *end_of_arg;\n"},{"id":"475817","messageId":"xmqqsfcthrpb.fsf@gitster.g","threadId":"59416","inReplyTo":"9356d14b09a468d8ef2884cd7d76e59ec5c16691.1682089075.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/6] rebase --continue: refuse to commit after failed command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-21T21:05:04Z","receivedAt":"2023-04-21T21:05:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> If a commit cannot be picked because it would overwrite an untracked\n> file then \"git rebase --continue\" should refuse to commit any staged\n> changes as the commit was not picked.\n\nIt makes perfect sense to refuse blindly committing.  But this makes\nme wonder what the procedure for the user to recover.  In the simplest\ncase, the untracked file may be expendable and the user would wish to\neasily redo the step after removing it?  Like\n\n\t$ git rebase\n\t... stops with \"merging will overwrite untracked 'foo'\" ...\n\t$ git rebase --continue\n\t... refuses thanks to the fix in this step ...\n\t$ rm foo\n\t... now what?  \"git rebase --redo\"?  \"git rebase --continue\"?\n\t\n> Do this by using the existing\n> check for a missing author script in run_git_commit() which prevents\n> \"rebase --continue\" from committing staged changes after failed exec\n> commands.\n\nDepending on the recovery procedure, this may or may not be a wise\ndesign decision, even though it may be the quickest way to implement\nit.  If the recovery procedure involves redoing the failed step from\nscratch (i.e. \"rm foo && git reset --hard && git rebase\" would try\nto restart by replaying the failed step anew), then loss of author\nscript file has no downside.  If we want to salvage as much as what\nwas done in the initial attempt, maybe not.\n\n> When fast-forwarding it is not necessary to write the author script as\n\nI'd prefer a comma before \"it is not\" here.\n\n> we're reusing an existing commit, not creating a new one. If a\n> fast-forwarded commit is modified by an \"edit\" or \"reword\" command then\n> the modification is committed with \"git commit --amend\" which reuses the\n> author of the commit being amended so the author script is not needed.\n\nOK.\n\n> baf8ec8d3a (rebase -r: don't write .git/MERGE_MSG when fast-forwarding,\n> 2021-08-20) changed run_git_commit() to allow a missing author script\n> when rewording a commit. This changes extends that to allow a missing\n\nIt is unclear to me what \"This changes extends\" refers to.  Could\nyou rephrase?\n\n> author script whenever the commit is being amended.\n>\n> If we're not fast-forwarding then we must remove the author script if\n> the pick fails.\n\nThe changes described in these three paragraphs are about efforts\nthat were made needed only because the approach chosen to stop\n\"continue\" from continuing in this situation happens to be to remove\nthe author script.  If it were done differently (perhaps by adding\nanother flag file that \"continue\" pays attention to), none of them\nmay be necessary?\n\n> @@ -4141,6 +4140,7 @@ static int do_merge(struct repository *r,\n>  \tif (ret < 0) {\n>  \t\terror(_(\"could not even attempt to merge '%.*s'\"),\n>  \t\t      merge_arg_len, arg);\n> +\t\tunlink(rebase_path_author_script());\n>  \t\tgoto leave_merge;\n>  \t}\n\nI agree that this is the right location to add new code to stop\n\"continue\" from moving forward.  I do not know if the \"unlink the\nauthor script\" is the right choice of such a new code, though.\n\nComing back to my first point, perhaps adding an advice() after this\nerror() would help end users who see this error to learn what to do\nto move forward?\n\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index ff0afad63e2..c1fe55dc2c1 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1288,6 +1288,12 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>  \ttest_must_fail git rebase --continue &&\n>  \ttest_cmp_rev HEAD F &&\n>  \trm file6 &&\n\nBefore the pre-context is an attempt to run \"git rebase -i\" to\nreplay a commit that adds file6, stop the sequence by marking a step\nas \"edit\" to take control back.  Then we create file6 manually in\nthe working tree and make sure that \"git rebase --continue\" fails in\nthe pre-context we can see here.\n\nThe recovery I asked about earlier is done with \"rm file6\" in this\ncase.\n\n> +\ttest_path_is_missing .git/rebase-merge/author-script &&\n\nThis is testing the implementation.  The failed \"continue\" would\nhave removed the file thanks to the change we saw earlier.\n\n> +\techo changed >file1 &&\n> +\tgit add file1 &&\n> +\ttest_must_fail git rebase --continue 2>err &&\n\nThen we make some edit, and try to \"--continue\".  Why should this\nfail?  Is it because the earlier \"rebase --continue\" that failed\ndid not replay the original commit due to untracked file and the\nuser needs to redo the step in its entirety before the working tree\nbecomes ready to take any further changes?\n\n> +\tgrep \"error: you have staged changes in your working tree\" err &&\n> +\tgit reset --hard HEAD &&\n\nAnd this \"reset --hard\" is another thing in the recovery procedure\nthe user needs to take (the other one being the removal of file6 we\nhave seen earlier).  After that, \"rebase --continue\" will replay the\nstep that was interrupted by the untracked file6 that was in the\nworking tree.  OK.\n\n>  \tgit rebase --continue &&\n>  \ttest_cmp_rev HEAD I\n>  '\n\nWe of course do not want to become overly \"helpful\" and run \"reset\n--hard\" ourselves when we issue the \"could not even attempt to\nmerge\" message, but when we step back and see what the user wanted\nto do, this is still not entirely satisfactory, is it?\n\nMy understanding of what the user wanted to do (and let's pretend\nthat creation of file6 in the middle was merely for test writer's\nconvenience and in the real scenario of what the user wanted to do,\nthere was the file left untracked from the beginning before the\nrebase started) is to redo the A---F---I chain, making a bit of\nchange to F when it is recreated, and then replay I on top of the\nresult of tweaked F.  But instead of allowing them to edit F by\ndoing some modification to file1, we ended up forcing the user to\ndiscard the edit made to file1 with \"reset --hard\", and \"continue\"\nreplayed I on top of the replayed F that \"edit\" did not have a\nchance to modify.\n\nOf course, the user can now go back to A and replay F and I on top,\nessentially redoing the \"rebase -i\" with FAKE_LINES=\"edit 1 2\" and\nthis time, because untracked file6 is gone, it may work better and\nF may allow to be tweaked.  But then it does not look any better\nthan saying \"git rebase --abort\" before doing the final \"continue\",\nwhich will also allow the user to redo the whole thing from scratch\nagain.\n\nSo, I dunno.\n\n"},{"id":"475819","messageId":"xmqqleilhqxd.fsf@gitster.g","threadId":"59416","inReplyTo":"049a792d-e015-3583-452d-923b9aee4a72@gmail.com","subject":"Re: [PATCH v2 3/6] sequencer: factor out part of pick_commits()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-04-21T21:21:50Z","receivedAt":"2023-04-21T21:21:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> There are two blocks that might be entered. One guarded by \"if\n> (reschedule)\" - this is not entered because reschedlue is always zero\n> when picking a commit. The other is guarded by \"else if\n> (is_rebase_i(opts) && check_todo && !res)\" and so will not be entered\n> when we want to return an error because \"res\" is non-zero in that\n> case.\n\nPerhaps the proposed log message can be updated to mention\nthese to save time from \"git log\" readers?\n\n>>   * the todo_list->current counter is incremented\n\nThe todo_list->current counter gets incremented before leaving the\nfunction now, but all three callers of pick_commits() immediately\ncall todo_list_release(), the value, whether it is incremented or\nnot, is discarded without getting looked at.  \n\nSo it is not a noop, but the difference in behaviour does not become\nexternally observable?\n\nThanks.\n"},{"id":"476186","messageId":"5d5413a2-d3fe-4a5b-25e5-d0132034c11f@gmail.com","threadId":"59416","inReplyTo":"xmqq8relkv52.fsf@gitster.g","subject":"Re: [PATCH v2 1/6] rebase -i: move unlink() calls","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-04-27T10:15:39Z","receivedAt":"2023-04-27T10:15:50Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 21/04/2023 18:22, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> At the start of each iteration the loop that picks commits removes\n>> state files from the previous pick. However some of these are only\n>> written if there are conflicts so only need to be removed before\n>> starting the loop, not in each iteration.\n> \n> I do not doubt your reasoning is correct, but could you explain this\n> a bit better?\n> \n> I think the reason why others, e.g. author-script, need to be\n> removed on every iteration is because the previous iteration that\n> called do_pick_commit() can come back successfully after calling\n> write_author_script(), and we would want to clear the deck before\n> going into the next iteration, so I can guess that you meant by \"if\n> there are conflicts\" that the loop will not iterate to the next step\n> after conflicts happened (and these files like \"amend\" and\n> \"stopped-sha\" may have been written)?  The latter, i.e. the loop\n> will not iterate any further, is the more direct reason to justify\n> this change, I think, and it would help readers of \"git log\" to say\n> so, instead of forcing them to infer \"are conflicts\" imply \"hence\n> loop will stop\".\n\nYes, that's right. I'll expand the commit message when I re-roll.\n\n> Is this a pure clean-up, or will there be behaviour change?  I do\n> not think there is with this patch alone, but does this change make\n> future steps easier to understand or something?\n\nIt is a pure cleanup. I noticed it when adding the unlink() call in the \nnext patch, it doesn't really have anything to do with the subject of \nthis series, but I felt it was worth doing as a preparation for the next \npatch.\n\nBest Wishes\n\nPhillip\n\n> IOW, the proposed log message may explain why this is not a wrong\n> change to make, but it is unclear why this is a good change we want\n> to have in this part of the series.\n> \n> Thanks.\n> \n>> diff --git a/sequencer.c b/sequencer.c\n>> index d2c7698c48c..5073ec5902b 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -4639,6 +4639,10 @@ static int pick_commits(struct repository *r,\n>>   \tif (read_and_refresh_cache(r, opts))\n>>   \t\treturn -1;\n>>   \n>> +\tunlink(rebase_path_message());\n>> +\tunlink(rebase_path_stopped_sha());\n>> +\tunlink(rebase_path_amend());\n>> +\n>>   \twhile (todo_list->current < todo_list->nr) {\n>>   \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n>>   \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n>> @@ -4662,10 +4666,7 @@ static int pick_commits(struct repository *r,\n>>   \t\t\t\t\t\ttodo_list->total_nr,\n>>   \t\t\t\t\t\topts->verbose ? \"\\n\" : \"\\r\");\n>>   \t\t\t}\n>> -\t\t\tunlink(rebase_path_message());\n>>   \t\t\tunlink(rebase_path_author_script());\n>> -\t\t\tunlink(rebase_path_stopped_sha());\n>> -\t\t\tunlink(rebase_path_amend());\n>>   \t\t\tunlink(git_path_merge_head(r));\n>>   \t\t\tunlink(git_path_auto_merge(r));\n>>   \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n\n"},{"id":"476187","messageId":"1fd54422-b66a-c2e4-7cd7-934ea01190ad@gmail.com","threadId":"59416","inReplyTo":"xmqqwn25jbzw.fsf@gitster.g","subject":"Re: [PATCH v2 2/6] rebase -i: remove patch file after conflict resolution","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-04-27T10:17:15Z","receivedAt":"2023-04-27T10:17:22Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 21/04/2023 20:01, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> When rebase stops for the user to resolve conflicts it writes a patch\n>> for the conflicting commit to .git/rebase-merge/patch. This file\n>> should be deleted when the rebase continues.\n> \n> Could you describe the reason why this file \"should\" be deleted a\n> bit better?  Once the user edits the files in the working tree and\n> tell \"git rebase\" with the \"--continue\" option that they finished\n> helping the command, and the command creates a commit out of the\n> resolution left by the user in the working tree and in the index,\n> the patch may no longer is needed, so I can understand if this were\n> \"this file can be deleted\"---in other words, again, this explains\n> why such a change would not be a wrong change that hurts the users,\n> but it does not explain why we want such a change very well.  Is\n> there a reason why a left-over patch file is a bad thing (perhaps\n> causing end-user confusion upon seeing such a patch that apparently\n> is for a much earlier step in the rebase in progress?  If so, that\n> might be a good justification to say we \"should\").\n\nYes that's the reason - we don't want a stale patch when we stop for \nanother reason, I'll improve the commit message.\n\nBest Wishes\n\nPhillip\n\n>> As the path is now used\n>> in two different places rebase_path_patch() is added and used to\n>> obtain the path for the patch.\n> \n> OK.\n> \n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   sequencer.c | 9 +++++++--\n>>   1 file changed, 7 insertions(+), 2 deletions(-)\n> \n> The patch text itself looks good in the sense that it correctly\n> implements what the proposed log message claims it \"should\".\n> \n> Thanks.\n\n"},{"id":"478660","messageId":"kl6lv8fgr347.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/6] rebase -i: impove handling of failed commands","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-21T20:07:20Z","receivedAt":"2023-06-21T20:07:26Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Hi Phillip!\n\nWe picked up this series during Review Club. You can browse the notes at\n\n  https://docs.google.com/document/d/14L8BAumGTpsXpjDY8VzZ4rRtpAjuGrFSRqn3stCuS_w/edit?pli=1\n\nbut we'll post any substantial feedback back to the mailing list anyway.\n\nFirstly, I have to acknowledge that this series appears to be geared\ntowards to reviewers who are already familiar with the rebase machinery\nand don't require a lot of context on the changes. None of the Review\nClub attendees were familiar with it, so we had trouble following along\ncertain patches, but we might not have been the intended audience\nanyway.\n\nI can leave comments from that perspective, i.e. what would have been\nuseful from _if_ this were written for folks who weren't already\nfamiliar with the rebase code, which could be useful if we were trying\nto train people to reviewer sequencer.c. However, I don't think this\nwarrants substantial changes if the series is already clear to the\nintended audience.\n\n\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series fixes several bugs in the way we handle a commit cannot be\n> picked because it would overwrite an untracked file.\n>\n>  * after a failed pick \"git rebase --continue\" will happily commit any\n>    staged changes even though no commit was picked.\n\nThis sounds like a safer default for most users, but I worry that there\nare seasoned users relying on the old behavior.\n\n>  * the commit of the failed pick is recorded as rewritten even though no\n>    commit was picked.\n\nSounds like a reasonable fix.\n\n>  * the \"done\" file used by \"git status\" to show the recently executed\n>    commands contains an incorrect entry.\n\nThis also sounds reasonable, but from Johannes' upthread response, this\nsounds like this isn't universally agreed upon.\n\nPerhaps the underlying issue is that the behavior of \"git rebase\n--continue\", and the todo/done lists is underspecified (in public and\ninternal documentation), so we end up having to reestablish what the\n'correct' behavior is (which will probably end up being just a matter of\npersonal taste). This isn't strictly necessary for the series, but it\nwould be nice for us to establish what the correct behavior _should be_\n(even if we aren't there yet) and use that to guide future fixes.\n"},{"id":"478661","messageId":"kl6lsfakr2sr.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"227aea031b588977f22f3f97faee981d79ade05c.1682089074.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/6] rebase -i: remove patch file after conflict resolution","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-21T20:14:12Z","receivedAt":"2023-06-21T20:14:18Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> @@ -3490,7 +3495,6 @@ static int make_patch(struct repository *r,\n>  \t\treturn -1;\n>  \tres |= write_rebase_head(&commit->object.oid);\n>  \n> -\tstrbuf_addf(&buf, \"%s/patch\", get_dir(opts));\n>  \tmemset(&log_tree_opt, 0, sizeof(log_tree_opt));\n>  \trepo_init_revisions(r, &log_tree_opt, NULL);\n>  \tlog_tree_opt.abbrev = 0;\n\nI was checking to see if we could remove buf or whether we are reusing\nit for unrelated reasons (which is a common Git-ism). We can't remove it\nbecause we reuse it, however...\n\n> @@ -3498,7 +3502,7 @@ static int make_patch(struct repository *r,\n>  \tlog_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;\n>  \tlog_tree_opt.disable_stdin = 1;\n>  \tlog_tree_opt.no_commit_id = 1;\n> -\tlog_tree_opt.diffopt.file = fopen(buf.buf, \"w\");\n> +\tlog_tree_opt.diffopt.file = fopen(rebase_path_patch(), \"w\");\n>  \tlog_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;\n>  \tif (!log_tree_opt.diffopt.file)\n>  \t\tres |= error_errno(_(\"could not open '%s'\"), buf.buf);\n\nthis buf.buf was supposed to be the value we populated earlier - this\nshould be rebase_path_patch() instead.\n\nAs an aside, I have a mild distaste the Git-ism of reusing \"struct\nstrbuf buf\" - using a variable for just a single purpose and naming it\nas such makes these sorts of errors much easier to spot. That isn't\nsomething we need to fix here, I'm just venting a little :)\n"},{"id":"478663","messageId":"kl6lpm5or1tg.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"9356d14b09a468d8ef2884cd7d76e59ec5c16691.1682089075.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/6] rebase --continue: refuse to commit after failed command","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-21T20:35:23Z","receivedAt":"2023-06-21T20:35:29Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> If a commit cannot be picked because it would overwrite an untracked\n> file then \"git rebase --continue\" should refuse to commit any staged\n> changes as the commit was not picked. Do this by using the existing\n> check for a missing author script in run_git_commit() which prevents\n> \"rebase --continue\" from committing staged changes after failed exec\n> commands.\n\nFor someone unfamiliar with \"git rebase\" code, I think it is easy enough\nto gather that \"rebase --continue\" will refuse to accept staged changes\nif the author script is missing, so we are reusing that mechanism to\nachieve our desired effect. It's not obvious whether this might have\nunintended consequences (Are we reusing something unrelated for an\nunintended purpose?) or what alternatives exist (Is sequencer.c so\ncomplex that there isn't another way to do this?). It would have been\nhelpful for me to see how these considerations factored into your\ndecision.\n\n> When fast-forwarding it is not necessary to write the author script as\n> we're reusing an existing commit, not creating a new one. If a\n> fast-forwarded commit is modified by an \"edit\" or \"reword\" command then\n> the modification is committed with \"git commit --amend\" which reuses the\n> author of the commit being amended so the author script is not needed.\n> baf8ec8d3a (rebase -r: don't write .git/MERGE_MSG when fast-forwarding,\n> 2021-08-20) changed run_git_commit() to allow a missing author script\n> when rewording a commit. This changes extends that to allow a missing\n> author script whenever the commit is being amended.\n\nAs I understand it, the author script can now be missing in other\ncircumstances, so we have to adjust the rest of the machinery to handle\nthat case? If so, this seems to suggest that there are some unintended\nconsequences.\n\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index ff0afad63e2..c1fe55dc2c1 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1288,6 +1288,12 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>  \ttest_must_fail git rebase --continue &&\n>  \ttest_cmp_rev HEAD F &&\n>  \trm file6 &&\n> +\ttest_path_is_missing .git/rebase-merge/author-script &&\n\nChecking that the path is missing seems like testing implementation\ndetails. If so, I would prefer to remove this assertion here and\nelsewhere.\n\n> +\techo changed >file1 &&\n> +\tgit add file1 &&\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"error: you have staged changes in your working tree\" err &&\n> +\tgit reset --hard HEAD &&\n\nThis seems reasonable.\n"},{"id":"478664","messageId":"kl6lmt0sr16k.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"f8e64c1b631116367e6e68fcfde711b507a03a94.1682089075.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 5/6] rebase: fix rewritten list for failed pick","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-21T20:49:07Z","receivedAt":"2023-06-21T20:49:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> When rebasing commands are moved from the todo list in \"git-rebase-todo\"\n> to the \"done\" file just before they are executed. This means that if a\n> command fails because it would overwrite an untracked file it has to be\n> added back into the todo list before the rebase stops for the user to\n> fix the problem. Unfortunately the way this is done results in the\n> failed pick being recorded as rewritten.\n\nI could not make the connection from the described problem to the\nproposed solution. In particular, I couldn't tell what about \"the way\nthis is done\" that causes the incorrect behavior (e.g. are we failing to\nclean up something? are we writing the wrong set of metadata?).\n\n> Fix this by not calling error_with_patch() for failed commands.\n\nSo unfortunately , I wasn't sure how this solution would fix the\nproblem, and I didn't dive too deeply into this patch.\n\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index c1fe55dc2c1..a657167befd 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1289,6 +1289,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>  \ttest_cmp_rev HEAD F &&\n>  \trm file6 &&\n>  \ttest_path_is_missing .git/rebase-merge/author-script &&\n> +\ttest_path_is_missing .git/rebase-merge/patch &&\n> +\ttest_path_is_missing .git/MERGE_MSG &&\n> +\ttest_path_is_missing .git/rebase-merge/message &&\n> +\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n\nThis also seems to be testing implementation details, and if so, it\nwould be worth removing them.\n"},{"id":"478666","messageId":"kl6ljzvwr0px.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"a836b049b900fa9d7c03ed5426a28b5cc754d4c5.1682089075.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 6/6] rebase -i: fix adding failed command to the todo list","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-21T20:59:06Z","receivedAt":"2023-06-21T20:59:12Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> When rebasing commands are moved from the todo list in \"git-rebase-todo\"\n> to the \"done\" file (which is used by \"git status\" to show the recently\n> executed commands) just before they are executed. This means that if a\n> command fails because it would overwrite an untracked file it has to be\n> added back into the todo list before the rebase stops for the user to\n> fix the problem.\n>\n> Unfortunately when a failed command is added back into the todo list\n> the command preceding it is erroneously appended to the \"done\" file.\n> This means that when rebase stops after \"pick B\" fails the \"done\"\n> file contains\n>\n> \tpick A\n> \tpick B\n> \tpick A\n>\n> instead of\n>\n> \tpick A\n> \tpick B\n\nIt's very interesting that an _earlier_ line would get appended on the\n_other_ side, I'd expect that even if lines get duplicated, their\nrelative order would be preserved. I was not able to trace the sequence\nof events that got us here, so I could not make a connection from that\nto the solution. I didn't dig deeply into this patch either, so I will\nrefrain from commenting on the solution.\n\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index a657167befd..653c19bc9c8 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1276,18 +1276,23 @@ test_expect_success 'todo count' '\n>  '\n>  \n>  test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n> -\tgit checkout --force branch2 &&\n> +\tgit checkout --force A &&\n>  \tgit clean -f &&\n> +\tcat >todo <<-EOF &&\n> +\texec >file2\n> +\tpick $(git rev-parse B) B\n> +\tpick $(git rev-parse C) C\n> +\tpick $(git rev-parse D) D\n> +\texec cat .git/rebase-merge/done >actual\n> +\tEOF\n>  \t(\n> -\t\tset_fake_editor &&\n> -\t\tFAKE_LINES=\"edit 1 2\" git rebase -i A\n> +\t\tset_replace_editor todo &&\n> +\t\ttest_must_fail git rebase -i A\n>  \t) &&\n> -\ttest_cmp_rev HEAD F &&\n> -\ttest_path_is_missing file6 &&\n> -\t>file6 &&\n> -\ttest_must_fail git rebase --continue &&\n> -\ttest_cmp_rev HEAD F &&\n> -\trm file6 &&\n> +\ttest_cmp_rev HEAD B &&\n> +\thead -n3 todo >expect &&\n> +\ttest_cmp expect .git/rebase-merge/done &&\n> +\trm file2 &&\n\nI didn't look into how the test works, but I confirmed that it tests the\nexact scenario described in the commit message.\n\n"},{"id":"479514","messageId":"55dd6194-25e5-1a66-9c39-27cb19bfbb3c@gmail.com","threadId":"59416","inReplyTo":"kl6lsfakr2sr.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 2/6] rebase -i: remove patch file after conflict resolution","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-07-14T10:08:26Z","receivedAt":"2023-07-14T10:08:33Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 21/06/2023 21:14, Glen Choo wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> @@ -3490,7 +3495,6 @@ static int make_patch(struct repository *r,\n>>   \t\treturn -1;\n>>   \tres |= write_rebase_head(&commit->object.oid);\n>>   \n>> -\tstrbuf_addf(&buf, \"%s/patch\", get_dir(opts));\n>>   \tmemset(&log_tree_opt, 0, sizeof(log_tree_opt));\n>>   \trepo_init_revisions(r, &log_tree_opt, NULL);\n>>   \tlog_tree_opt.abbrev = 0;\n> \n> I was checking to see if we could remove buf or whether we are reusing\n> it for unrelated reasons (which is a common Git-ism). We can't remove it\n> because we reuse it, however...\n\nI had a look at that and we're using it to construct a path that we \nshould obtain by calling rebase_path_message() - I'll add a fix when I \nre-roll.\n\n>> @@ -3498,7 +3502,7 @@ static int make_patch(struct repository *r,\n>>   \tlog_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;\n>>   \tlog_tree_opt.disable_stdin = 1;\n>>   \tlog_tree_opt.no_commit_id = 1;\n>> -\tlog_tree_opt.diffopt.file = fopen(buf.buf, \"w\");\n>> +\tlog_tree_opt.diffopt.file = fopen(rebase_path_patch(), \"w\");\n>>   \tlog_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;\n>>   \tif (!log_tree_opt.diffopt.file)\n>>   \t\tres |= error_errno(_(\"could not open '%s'\"), buf.buf);\n> \n> this buf.buf was supposed to be the value we populated earlier - this\n> should be rebase_path_patch() instead.\n\nOh, well spotted, thanks for pointing that out.\n\n> As an aside, I have a mild distaste the Git-ism of reusing \"struct\n> strbuf buf\" - using a variable for just a single purpose and naming it\n> as such makes these sorts of errors much easier to spot. That isn't\n> something we need to fix here, I'm just venting a little :)\n\nI agree it can get confusing. We occasionally forget to call \nstrbuf_reset() before reusing the buffer (my first contribution to git \nfixed such a case in 4ab867b8fc8 (rebase -i: fix reflog message, \n2017-05-18)), or forget to remove a call to reset the buffer that is \nno-longer necessary when refactoring. However it does save quite a few \ncalls to malloc()/free() in the rebase/sequencer code.\n\nBest Wishes\n\nPhillip\n"},{"id":"479523","messageId":"xmqq4jm6qvvd.fsf@gitster.g","threadId":"59416","inReplyTo":"55dd6194-25e5-1a66-9c39-27cb19bfbb3c@gmail.com","subject":"Re: [PATCH v2 2/6] rebase -i: remove patch file after conflict resolution","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-07-14T16:51:50Z","receivedAt":"2023-07-14T16:51:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 21/06/2023 21:14, Glen Choo wrote:\n>> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> \n>>> @@ -3490,7 +3495,6 @@ static int make_patch(struct repository *r,\n>>>   \t\treturn -1;\n>>>   \tres |= write_rebase_head(&commit->object.oid);\n>>>   -\tstrbuf_addf(&buf, \"%s/patch\", get_dir(opts));\n>>>   \tmemset(&log_tree_opt, 0, sizeof(log_tree_opt));\n>>>   \trepo_init_revisions(r, &log_tree_opt, NULL);\n>>>   \tlog_tree_opt.abbrev = 0;\n>> I was checking to see if we could remove buf or whether we are\n>> reusing\n>> it for unrelated reasons (which is a common Git-ism). We can't remove it\n>> because we reuse it, however...\n>\n> I had a look at that and we're using it to construct a path that we\n> should obtain by calling rebase_path_message() - I'll add a fix when I\n> re-roll.\n\nWow, a patch from April commented in June and responded in July ;-).\n\nI'll salvage the topic from the \"will discard\" bin and mark it again\nas \"Expecting a reroll\" in my draft of the next \"What's cooking\"\nreport.\n\nThanks.\n"},{"id":"479576","messageId":"b2812b85-b92e-89a4-e3e9-2c952d53c3ff@gmail.com","threadId":"59416","inReplyTo":"xmqq4jm6qvvd.fsf@gitster.g","subject":"Re: [PATCH v2 2/6] rebase -i: remove patch file after conflict resolution","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-07-17T15:39:02Z","receivedAt":"2023-07-17T15:39:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 14/07/2023 17:51, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> On 21/06/2023 21:14, Glen Choo wrote:\n>>> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>>\n>>>> @@ -3490,7 +3495,6 @@ static int make_patch(struct repository *r,\n>>>>    \t\treturn -1;\n>>>>    \tres |= write_rebase_head(&commit->object.oid);\n>>>>    -\tstrbuf_addf(&buf, \"%s/patch\", get_dir(opts));\n>>>>    \tmemset(&log_tree_opt, 0, sizeof(log_tree_opt));\n>>>>    \trepo_init_revisions(r, &log_tree_opt, NULL);\n>>>>    \tlog_tree_opt.abbrev = 0;\n>>> I was checking to see if we could remove buf or whether we are\n>>> reusing\n>>> it for unrelated reasons (which is a common Git-ism). We can't remove it\n>>> because we reuse it, however...\n>>\n>> I had a look at that and we're using it to construct a path that we\n>> should obtain by calling rebase_path_message() - I'll add a fix when I\n>> re-roll.\n> \n> Wow, a patch from April commented in June and responded in July ;-).\n> \n> I'll salvage the topic from the \"will discard\" bin and mark it again\n> as \"Expecting a reroll\" in my draft of the next \"What's cooking\"\n> report.\n\nThanks, sorry it has taken so long, I've been struggling to find time to \nwork on this but hopefully will have a new version ready in the next \ncouple of weeks\n\nBest Wishes\n\nPhillip\n\n> Thanks.\n"},{"id":"479840","messageId":"fdb605bf-938d-6135-b341-6cf20600abaf@gmail.com","threadId":"59416","inReplyTo":"kl6lmt0sr16k.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 5/6] rebase: fix rewritten list for failed pick","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-07-25T15:42:08Z","receivedAt":"2023-07-25T15:42:25Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Glen\n\nOn 21/06/2023 21:49, Glen Choo wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> When rebasing commands are moved from the todo list in \"git-rebase-todo\"\n>> to the \"done\" file just before they are executed. This means that if a\n>> command fails because it would overwrite an untracked file it has to be\n>> added back into the todo list before the rebase stops for the user to\n>> fix the problem. Unfortunately the way this is done results in the\n>> failed pick being recorded as rewritten.\n> \n> I could not make the connection from the described problem to the\n> proposed solution. In particular, I couldn't tell what about \"the way\n> this is done\" that causes the incorrect behavior (e.g. are we failing to\n> clean up something? are we writing the wrong set of metadata?).\n\nYes, on reflection that first paragraph is not very helpful. I've \nupdated it to\n\ngit rebase keeps a list that maps the OID of each commit before\nit was rebased to the OID of the equivalent commit after the rebase.\nThis list is used to drive the \"post-rewrite\" hook that is called at the\nend of a successful rebase. When a rebase stops for the user to resolve\nmerge conflicts the OID of the commit being picked is written to\n\".git/rebase-merge/stopped-sha1\" and when the rebase is continued that\nOID is added to the list of rewritten commits. Unfortunately when a\ncommit cannot be picked because it would overwrite an untracked file we\nstill write the \"stopped-sha1\" file and so when the rebase is continued\nthe commit is added into the list of rewritten commits even though it\nhas not been picked yet.\n\nHopefully that is more helpful\n\n>> Fix this by not calling error_with_patch() for failed commands.\n> \n> So unfortunately , I wasn't sure how this solution would fix the\n> problem, and I didn't dive too deeply into this patch.\n> \n>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n>> index c1fe55dc2c1..a657167befd 100755\n>> --- a/t/t3404-rebase-interactive.sh\n>> +++ b/t/t3404-rebase-interactive.sh\n>> @@ -1289,6 +1289,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>>   \ttest_cmp_rev HEAD F &&\n>>   \trm file6 &&\n>>   \ttest_path_is_missing .git/rebase-merge/author-script &&\n>> +\ttest_path_is_missing .git/rebase-merge/patch &&\n>> +\ttest_path_is_missing .git/MERGE_MSG &&\n>> +\ttest_path_is_missing .git/rebase-merge/message &&\n>> +\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n> \n> This also seems to be testing implementation details, and if so, it\n> would be worth removing them.\n\nWith the exception of the \"patch\" file which exists solely for the \nbenefit of the user this is testing an invariant of the implementation \nwhich isn't ideal. I'm worried that removing these checks will mask some \nsubtle regression in the future. I think it is unlikely that the names \nof these files will change in the future as we try to avoid changes that \nwould cause a rebase to fail if git is upgraded while it has stopped for \nthe user to resolve conflicts. I did think about whether we could add \nsome BUG() statements to sequencer.c instead. Unfortunately I don't \nthink it is that easy for the sequencer to know when these files should \nbe missing without relying on the logic that we are tying to test.\n\nBest Wishes\n\nPhillip\n"},{"id":"479842","messageId":"kl6lo7k0ym57.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"fdb605bf-938d-6135-b341-6cf20600abaf@gmail.com","subject":"Re: [PATCH v2 5/6] rebase: fix rewritten list for failed pick","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-25T16:46:28Z","receivedAt":"2023-07-25T16:48:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>>> When rebasing commands are moved from the todo list in \"git-rebase-todo\"\n>>> to the \"done\" file just before they are executed. This means that if a\n>>> command fails because it would overwrite an untracked file it has to be\n>>> added back into the todo list before the rebase stops for the user to\n>>> fix the problem. Unfortunately the way this is done results in the\n>>> failed pick being recorded as rewritten.\n>> \n>> I could not make the connection from the described problem to the\n>> proposed solution. In particular, I couldn't tell what about \"the way\n>> this is done\" that causes the incorrect behavior (e.g. are we failing to\n>> clean up something? are we writing the wrong set of metadata?).\n>\n> Yes, on reflection that first paragraph is not very helpful. I've \n> updated it to\n>\n> git rebase keeps a list that maps the OID of each commit before\n> it was rebased to the OID of the equivalent commit after the rebase.\n> This list is used to drive the \"post-rewrite\" hook that is called at the\n> end of a successful rebase. When a rebase stops for the user to resolve\n> merge conflicts the OID of the commit being picked is written to\n> \".git/rebase-merge/stopped-sha1\" and when the rebase is continued that\n> OID is added to the list of rewritten commits. Unfortunately when a\n> commit cannot be picked because it would overwrite an untracked file we\n> still write the \"stopped-sha1\" file and so when the rebase is continued\n> the commit is added into the list of rewritten commits even though it\n> has not been picked yet.\n>\n> Hopefully that is more helpful\n\nAh, yes that is much easier to visualise and understand. Thanks so much.\n\n>>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n>>> index c1fe55dc2c1..a657167befd 100755\n>>> --- a/t/t3404-rebase-interactive.sh\n>>> +++ b/t/t3404-rebase-interactive.sh\n>>> @@ -1289,6 +1289,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>>>   \ttest_cmp_rev HEAD F &&\n>>>   \trm file6 &&\n>>>   \ttest_path_is_missing .git/rebase-merge/author-script &&\n>>> +\ttest_path_is_missing .git/rebase-merge/patch &&\n>>> +\ttest_path_is_missing .git/MERGE_MSG &&\n>>> +\ttest_path_is_missing .git/rebase-merge/message &&\n>>> +\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n>> \n>> This also seems to be testing implementation details, and if so, it\n>> would be worth removing them.\n>\n> With the exception of the \"patch\" file which exists solely for the \n> benefit of the user this is testing an invariant of the implementation \n> which isn't ideal. I'm worried that removing these checks will mask some \n> subtle regression in the future. I think it is unlikely that the names \n> of these files will change in the future as we try to avoid changes that \n> would cause a rebase to fail if git is upgraded while it has stopped for \n> the user to resolve conflicts. I did think about whether we could add \n> some BUG() statements to sequencer.c instead. Unfortunately I don't \n> think it is that easy for the sequencer to know when these files should \n> be missing without relying on the logic that we are tying to test.\n\nUnfortunately, it's been a while since I reviewed this patch, so forgive\nme if I'm rusty. So you're saying that this test is about checking\ninvariants that we want to preserve between Git versions. I think that's\na reasonable goal - I am slightly skeptical of whether we should be\ndoing that ad-hoc like this, but I don't feel strongly about it.\n\nIIRC, there was an earlier patch would be different from an where we\ntested that author-script is missing, but what we really want is for the\npick to stop. Is the same thing happening here? E.g. is 'testing for\nmissing stopped-sha' a stand-in for 'testing that the rewritten list is\ncorrect'? If so, it would be nice to test that specifically, but if\nthat's infeasible, a clarifying comment will probably suffice.\n"},{"id":"479896","messageId":"a3526864-dd3a-f95c-72e6-44995a9a460f@gmail.com","threadId":"59416","inReplyTo":"kl6lo7k0ym57.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 5/6] rebase: fix rewritten list for failed pick","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-07-26T13:08:22Z","receivedAt":"2023-07-26T13:08:37Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Glen\n\nOn 25/07/2023 17:46, Glen Choo wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n>>>> index c1fe55dc2c1..a657167befd 100755\n>>>> --- a/t/t3404-rebase-interactive.sh\n>>>> +++ b/t/t3404-rebase-interactive.sh\n>>>> @@ -1289,6 +1289,10 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>>>>    \ttest_cmp_rev HEAD F &&\n>>>>    \trm file6 &&\n>>>>    \ttest_path_is_missing .git/rebase-merge/author-script &&\n>>>> +\ttest_path_is_missing .git/rebase-merge/patch &&\n>>>> +\ttest_path_is_missing .git/MERGE_MSG &&\n>>>> +\ttest_path_is_missing .git/rebase-merge/message &&\n>>>> +\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n>>>\n>>> This also seems to be testing implementation details, and if so, it\n>>> would be worth removing them.\n>>\n>> With the exception of the \"patch\" file which exists solely for the\n>> benefit of the user this is testing an invariant of the implementation\n>> which isn't ideal. I'm worried that removing these checks will mask some\n>> subtle regression in the future. I think it is unlikely that the names\n>> of these files will change in the future as we try to avoid changes that\n>> would cause a rebase to fail if git is upgraded while it has stopped for\n>> the user to resolve conflicts. I did think about whether we could add\n>> some BUG() statements to sequencer.c instead. Unfortunately I don't\n>> think it is that easy for the sequencer to know when these files should\n>> be missing without relying on the logic that we are tying to test.\n> \n> Unfortunately, it's been a while since I reviewed this patch, so forgive\n> me if I'm rusty. So you're saying that this test is about checking\n> invariants that we want to preserve between Git versions.\n\nNot really. One of the reasons why testing the implementation rather \nthan the user observable behavior is a bad idea is that when the \nimplementation is changed the test is likely to start failing or keep \npassing without checking anything useful. I was trying to say that in \nthis case we're unlikely to change this aspect of the implementation \nbecause it would be tricky to do so without inconveniencing users who \nupgrade git while rebase is stopped for a conflict resolution and so it \nis unlikely that this test will be affected by future changes to the \nimplementation.\n\n> IIRC, there was an earlier patch would be different from an where we\n> tested that author-script is missing, but what we really want is for the\n> pick to stop. Is the same thing happening here? E.g. is 'testing for\n> missing stopped-sha' a stand-in for 'testing that the rewritten list is\n> correct'? If so, it would be nice to test that specifically, but if\n> that's infeasible, a clarifying comment will probably suffice.\n\nYes this patch adds a test to t5407-post-rewrite-hook.sh to do that but \nit only checks a failing \"pick\" command. The reason I think it is useful \nto add these test_path_is_missing checks is that they are checking \nfailing \"squash\" and \"merge\" commands as well. Maybe I should just bite \nthe bullet see how tricky it is to extend the post-rewrite-hook test to \ncover those cases as well.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"479909","messageId":"kl6lfs5azhqd.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"a3526864-dd3a-f95c-72e6-44995a9a460f@gmail.com","subject":"Re: [PATCH v2 5/6] rebase: fix rewritten list for failed pick","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-26T17:48:42Z","receivedAt":"2023-07-26T17:48:55Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> Unfortunately, it's been a while since I reviewed this patch, so forgive\n>> me if I'm rusty. So you're saying that this test is about checking\n>> invariants that we want to preserve between Git versions.\n>\n> Not really. One of the reasons why testing the implementation rather \n> than the user observable behavior is a bad idea is that when the \n> implementation is changed the test is likely to start failing or keep \n> passing without checking anything useful. I was trying to say that in \n> this case we're unlikely to change this aspect of the implementation \n> because it would be tricky to do so without inconveniencing users who \n> upgrade git while rebase is stopped for a conflict resolution and so it \n> is unlikely that this test will be affected by future changes to the \n> implementation.\n\nAh, I see the difference. I think that's it's fair to assume that the\nnames of the files will be fairly stable, though this series has made it\nclear to me that what each file does and when it is written is quite\nunder-documented, and I wouldn't be surprised to see some of that change\nif we start to try to explain the inner workings to ourselves.\n\n> Yes this patch adds a test to t5407-post-rewrite-hook.sh to do that but \n> it only checks a failing \"pick\" command. The reason I think it is useful \n> to add these test_path_is_missing checks is that they are checking \n> failing \"squash\" and \"merge\" commands as well. Maybe I should just bite \n> the bullet see how tricky it is to extend the post-rewrite-hook test to \n> cover those cases as well.\n\nYes, that would probably be a good idea. Maybe if we combined them into\na test helper that checks all of \"pick\", \"squash\" and \"merge\", which\nalso has the added benefit of being able to hide implementation details\nin case we decide to change them.\n"},{"id":"479965","messageId":"fd7ffcfd-288c-7f17-565d-7633ab507d51@gmail.com","threadId":"59416","inReplyTo":"kl6lfs5azhqd.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 5/6] rebase: fix rewritten list for failed pick","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-07-28T13:19:40Z","receivedAt":"2023-07-28T13:19:46Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Glen\n\nOn 26/07/2023 18:48, Glen Choo wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>>> Unfortunately, it's been a while since I reviewed this patch, so forgive\n>>> me if I'm rusty. So you're saying that this test is about checking\n>>> invariants that we want to preserve between Git versions.\n>>\n>> Not really. One of the reasons why testing the implementation rather\n>> than the user observable behavior is a bad idea is that when the\n>> implementation is changed the test is likely to start failing or keep\n>> passing without checking anything useful. I was trying to say that in\n>> this case we're unlikely to change this aspect of the implementation\n>> because it would be tricky to do so without inconveniencing users who\n>> upgrade git while rebase is stopped for a conflict resolution and so it\n>> is unlikely that this test will be affected by future changes to the\n>> implementation.\n> \n> Ah, I see the difference. I think that's it's fair to assume that the\n> names of the files will be fairly stable, though this series has made it\n> clear to me that what each file does and when it is written is quite\n> under-documented,\n\nThat is certainly true\n\n> and I wouldn't be surprised to see some of that change\n> if we start to try to explain the inner workings to ourselves.\n\nI agree. In the end I've removed the state file checks from the tests in \nfavor of adding explicit checks that we refuse to commit staged changes \nand adding more cases to the test for the \"post-rewrite\" hook. I think \nit would probably be useful to add some assertions to the sequencer in a \nfuture series. We can assert things like \"if this state file exists then \nso should these other ones\" and \"if this state file does not exist then \nthese others should not\" without relying on the logic in the sequencer. \nThe sequencer assertions wouldn't know if the message file should exist \nbut know what other files should exist if it does and the tests for \ncommitting staged changes then effectively check if the message file \nshould exist.\n\nThanks for your comments on this series, I'll send a re-roll next week\n\nBest Wishes\n\nPhillip\n\n>> Yes this patch adds a test to t5407-post-rewrite-hook.sh to do that but\n>> it only checks a failing \"pick\" command. The reason I think it is useful\n>> to add these test_path_is_missing checks is that they are checking\n>> failing \"squash\" and \"merge\" commands as well. Maybe I should just bite\n>> the bullet see how tricky it is to extend the post-rewrite-hook test to\n>> cover those cases as well.\n> \n> Yes, that would probably be a good idea. Maybe if we combined them into\n> a test helper that checks all of \"pick\", \"squash\" and \"merge\", which\n> also has the added benefit of being able to hide implementation details\n> in case we decide to change them.\n\n"},{"id":"480042","messageId":"e2a758eb4a5df0fab189b3dd235a1651a0c10342.1690903412.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v3 2/7] rebase -i: remove patch file after conflict resolution","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:27Z","receivedAt":"2023-08-01T15:23:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen a rebase stops for the user to resolve conflicts it writes a patch\nfor the conflicting commit to .git/rebase-merge/patch. This file has\nbeen written since the introduction of \"git-rebase-interactive.sh\" in\n1b1dce4bae7 (Teach rebase an interactive mode, 2007-06-25). I assume the\nidea was to enable the user inspect the conflicting commit in the same\nway as they could for the patch based rebase. This file should be\ndeleted when the rebase continues as if the rebase stops for a failed\n\"exec\" command or a \"break\" command it is confusing to the user if there\nis a stale patch lying around from an unrelated command. As the path is\nnow used in two different places rebase_path_patch() is added and used\nto obtain the path for the patch.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                | 13 +++++++++----\n t/t3418-rebase-continue.sh | 18 ++++++++++++++++++\n 2 files changed, 27 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex de66bda9d5b..70b0a7023b0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -138,6 +138,11 @@ static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n  * the commit object name of the corresponding patch.\n  */\n static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n+/*\n+ * When we stop for the user to resolve conflicts this file contains\n+ * the patch of the commit that is being picked.\n+ */\n+static GIT_PATH_FUNC(rebase_path_patch, \"rebase-merge/patch\")\n /*\n  * For the post-rewrite hook, we make a list of rewritten commits and\n  * their new sha1s.  The rewritten-pending list keeps the sha1s of\n@@ -3507,7 +3512,6 @@ static int make_patch(struct repository *r,\n \t\treturn -1;\n \tres |= write_rebase_head(&commit->object.oid);\n \n-\tstrbuf_addf(&buf, \"%s/patch\", get_dir(opts));\n \tmemset(&log_tree_opt, 0, sizeof(log_tree_opt));\n \trepo_init_revisions(r, &log_tree_opt, NULL);\n \tlog_tree_opt.abbrev = 0;\n@@ -3515,15 +3519,15 @@ static int make_patch(struct repository *r,\n \tlog_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;\n \tlog_tree_opt.disable_stdin = 1;\n \tlog_tree_opt.no_commit_id = 1;\n-\tlog_tree_opt.diffopt.file = fopen(buf.buf, \"w\");\n+\tlog_tree_opt.diffopt.file = fopen(rebase_path_patch(), \"w\");\n \tlog_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;\n \tif (!log_tree_opt.diffopt.file)\n-\t\tres |= error_errno(_(\"could not open '%s'\"), buf.buf);\n+\t\tres |= error_errno(_(\"could not open '%s'\"),\n+\t\t\t\t   rebase_path_patch());\n \telse {\n \t\tres |= log_tree_commit(&log_tree_opt, commit);\n \t\tfclose(log_tree_opt.diffopt.file);\n \t}\n-\tstrbuf_reset(&buf);\n \n \tstrbuf_addf(&buf, \"%s/message\", get_dir(opts));\n \tif (!file_exists(buf.buf)) {\n@@ -4659,6 +4663,7 @@ static int pick_commits(struct repository *r,\n \tunlink(rebase_path_message());\n \tunlink(rebase_path_stopped_sha());\n \tunlink(rebase_path_amend());\n+\tunlink(rebase_path_patch());\n \n \twhile (todo_list->current < todo_list->nr) {\n \t\tstruct todo_item *item = todo_list->items + todo_list->current;\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 2d0789e554b..261e7cd754c 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -244,6 +244,24 @@ test_expect_success 'the todo command \"break\" works' '\n \ttest_path_is_file execed\n '\n \n+test_expect_success 'patch file is removed before break command' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\tcat >todo <<-\\EOF &&\n+\tpick commit-new-file-F2-on-topic-branch\n+\tbreak\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i --onto commit-new-file-F2 HEAD\n+\t) &&\n+\ttest_path_is_file .git/rebase-merge/patch &&\n+\techo 22>F2 &&\n+\tgit add F2 &&\n+\tgit rebase --continue &&\n+\ttest_path_is_missing .git/rebase-merge/patch\n+'\n+\n test_expect_success '--reschedule-failed-exec' '\n \ttest_when_finished \"git rebase --abort\" &&\n \ttest_must_fail git rebase -x false --reschedule-failed-exec HEAD^ &&\n-- \ngitgitgadget\n\n"},{"id":"480043","messageId":"1ab1ad2ef07687c25c1d346b5b7b26f38bafe5b9.1690903412.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v3 1/7] rebase -i: move unlink() calls","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:26Z","receivedAt":"2023-08-01T15:23:50Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAt the start of each iteration the loop that picks commits removes\nstate files from the previous pick. However some of these are only\nwritten if there are conflicts and so we break out of the loop after\nwriting them. Therefore they only need to be removed when the rebase\ncontinues, not in each iteration.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex cc9821ece2c..de66bda9d5b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4656,6 +4656,10 @@ static int pick_commits(struct repository *r,\n \tif (read_and_refresh_cache(r, opts))\n \t\treturn -1;\n \n+\tunlink(rebase_path_message());\n+\tunlink(rebase_path_stopped_sha());\n+\tunlink(rebase_path_amend());\n+\n \twhile (todo_list->current < todo_list->nr) {\n \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n@@ -4679,10 +4683,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\t\t\ttodo_list->total_nr,\n \t\t\t\t\t\topts->verbose ? \"\\n\" : \"\\r\");\n \t\t\t}\n-\t\t\tunlink(rebase_path_message());\n \t\t\tunlink(rebase_path_author_script());\n-\t\t\tunlink(rebase_path_stopped_sha());\n-\t\t\tunlink(rebase_path_amend());\n \t\t\tunlink(git_path_merge_head(r));\n \t\t\tunlink(git_path_auto_merge(r));\n \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n-- \ngitgitgadget\n\n"},{"id":"480044","messageId":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v2.git.1682089074.gitgitgadget@gmail.com","subject":"[PATCH v3 0/7] rebase -i: impove handling of failed commands","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:25Z","receivedAt":"2023-08-01T15:23:51Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"This series fixes several bugs in the way we handle a commit cannot be\npicked because it would overwrite an untracked file.\n\n * after a failed pick \"git rebase --continue\" will happily commit any\n   staged changes even though no commit was picked.\n\n * the commit of the failed pick is recorded as rewritten even though no\n   commit was picked.\n\n * the \"done\" file used by \"git status\" to show the recently executed\n   commands contains an incorrect entry.\n\nThanks to Eric, Glen and Junio for their comments on v2. Here are the\nchanges since v2:\n\nPatch 1 - Reworded the commit message.\n\nPatch 2 - Reworded the commit message, added a test and fixed error message\npointed out by Glen.\n\nPatch 3 - New cleanup.\n\nPatch 4 - Reworded the commit message, now only increments\ntodo_list->current if there is no error.\n\nPatch 5 - Swapped with next patch. Reworded the commit message, stopped\ntesting implementation (suggested by Glen). Expanded post-rewrite hook test.\n\nPatch 6 - Reworded the commit message, now uses the message file rather than\nthe author script to check if \"rebase --continue\" should commit staged\nchanges. Junio suggested using a separate file for this but I think that\nwould end up being more involved as we'd need to be careful about creating\nand removing it.\n\nPatch 7 - Reworded the commit message.\n\nThanks for the comments on V1, this series has now grown somewhat.\nPreviously I was worried that refactoring would change the behavior, but\nhaving thought about it the current behavior is wrong and should be changed.\n\nChanges since V1:\n\nRebased onto master to avoid a conflict with\nab/remove-implicit-use-of-the-repository\n\n * Patches 1-3 are new preparatory changes\n * Patches 4 & 5 are new and fix the first two issues listed above.\n * Patch 6 is the old patch 1 which has been rebased and the commit message\n   reworded. It fixes the last issues listed above.\n\nPhillip Wood (7):\n  rebase -i: move unlink() calls\n  rebase -i: remove patch file after conflict resolution\n  sequencer: use rebase_path_message()\n  sequencer: factor out part of pick_commits()\n  rebase: fix rewritten list for failed pick\n  rebase --continue: refuse to commit after failed command\n  rebase -i: fix adding failed command to the todo list\n\n sequencer.c                   | 179 ++++++++++++++++++----------------\n t/t3404-rebase-interactive.sh |  53 +++++++---\n t/t3418-rebase-continue.sh    |  18 ++++\n t/t3430-rebase-merges.sh      |  30 ++++--\n t/t5407-post-rewrite-hook.sh  |  48 +++++++++\n 5 files changed, 225 insertions(+), 103 deletions(-)\n\n\nbase-commit: a80be152923a46f04a06bade7bcc72870e46ca09\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1492%2Fphillipwood%2Frebase-dont-write-done-when-rescheduling-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1492/phillipwood/rebase-dont-write-done-when-rescheduling-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1492\n\nRange-diff vs v2:\n\n 1:  3dfb2c6903b ! 1:  1ab1ad2ef07 rebase -i: move unlink() calls\n     @@ Commit message\n      \n          At the start of each iteration the loop that picks commits removes\n          state files from the previous pick. However some of these are only\n     -    written if there are conflicts so only need to be removed before\n     -    starting the loop, not in each iteration.\n     +    written if there are conflicts and so we break out of the loop after\n     +    writing them. Therefore they only need to be removed when the rebase\n     +    continues, not in each iteration.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n 2:  227aea031b5 ! 2:  e2a758eb4a5 rebase -i: remove patch file after conflict resolution\n     @@ Metadata\n       ## Commit message ##\n          rebase -i: remove patch file after conflict resolution\n      \n     -    When rebase stops for the user to resolve conflicts it writes a patch\n     -    for the conflicting commit to .git/rebase-merge/patch. This file\n     -    should be deleted when the rebase continues. As the path is now used\n     -    in two different places rebase_path_patch() is added and used to\n     -    obtain the path for the patch.\n     +    When a rebase stops for the user to resolve conflicts it writes a patch\n     +    for the conflicting commit to .git/rebase-merge/patch. This file has\n     +    been written since the introduction of \"git-rebase-interactive.sh\" in\n     +    1b1dce4bae7 (Teach rebase an interactive mode, 2007-06-25). I assume the\n     +    idea was to enable the user inspect the conflicting commit in the same\n     +    way as they could for the patch based rebase. This file should be\n     +    deleted when the rebase continues as if the rebase stops for a failed\n     +    \"exec\" command or a \"break\" command it is confusing to the user if there\n     +    is a stale patch lying around from an unrelated command. As the path is\n     +    now used in two different places rebase_path_patch() is added and used\n     +    to obtain the path for the patch.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n     @@ sequencer.c: static int make_patch(struct repository *r,\n      +\tlog_tree_opt.diffopt.file = fopen(rebase_path_patch(), \"w\");\n       \tlog_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;\n       \tif (!log_tree_opt.diffopt.file)\n     - \t\tres |= error_errno(_(\"could not open '%s'\"), buf.buf);\n     +-\t\tres |= error_errno(_(\"could not open '%s'\"), buf.buf);\n     ++\t\tres |= error_errno(_(\"could not open '%s'\"),\n     ++\t\t\t\t   rebase_path_patch());\n     + \telse {\n     + \t\tres |= log_tree_commit(&log_tree_opt, commit);\n     + \t\tfclose(log_tree_opt.diffopt.file);\n     + \t}\n     +-\tstrbuf_reset(&buf);\n     + \n     + \tstrbuf_addf(&buf, \"%s/message\", get_dir(opts));\n     + \tif (!file_exists(buf.buf)) {\n      @@ sequencer.c: static int pick_commits(struct repository *r,\n       \tunlink(rebase_path_message());\n       \tunlink(rebase_path_stopped_sha());\n     @@ sequencer.c: static int pick_commits(struct repository *r,\n       \n       \twhile (todo_list->current < todo_list->nr) {\n       \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n     +\n     + ## t/t3418-rebase-continue.sh ##\n     +@@ t/t3418-rebase-continue.sh: test_expect_success 'the todo command \"break\" works' '\n     + \ttest_path_is_file execed\n     + '\n     + \n     ++test_expect_success 'patch file is removed before break command' '\n     ++\ttest_when_finished \"git rebase --abort\" &&\n     ++\tcat >todo <<-\\EOF &&\n     ++\tpick commit-new-file-F2-on-topic-branch\n     ++\tbreak\n     ++\tEOF\n     ++\n     ++\t(\n     ++\t\tset_replace_editor todo &&\n     ++\t\ttest_must_fail git rebase -i --onto commit-new-file-F2 HEAD\n     ++\t) &&\n     ++\ttest_path_is_file .git/rebase-merge/patch &&\n     ++\techo 22>F2 &&\n     ++\tgit add F2 &&\n     ++\tgit rebase --continue &&\n     ++\ttest_path_is_missing .git/rebase-merge/patch\n     ++'\n     ++\n     + test_expect_success '--reschedule-failed-exec' '\n     + \ttest_when_finished \"git rebase --abort\" &&\n     + \ttest_must_fail git rebase -x false --reschedule-failed-exec HEAD^ &&\n -:  ----------- > 3:  8f6c0e40567 sequencer: use rebase_path_message()\n 3:  31bb644e769 ! 4:  a1fad70f4b9 sequencer: factor out part of pick_commits()\n     @@ Metadata\n       ## Commit message ##\n          sequencer: factor out part of pick_commits()\n      \n     -    This is simplifies a change in a later commit. If a pick fails we now\n     -    return the error at then end of the loop body rather than returning\n     -    early but there is no change in behavior.\n     +    This simplifies the next commit. If a pick fails we now return the error\n     +    at the end of the loop body rather than returning early, a successful\n     +    \"edit\" command continues to return early. There are three things to\n     +    check to ensure that removing the early return for an error does not\n     +    change the behavior of the code:\n     +\n     +    (1) We could enter the block guarded by \"if (reschedule)\". This block\n     +        is not entered because \"reschedlue\" is always zero when picking a\n     +        commit.\n     +\n     +    (2) We could enter the block guarded by\n     +        \"else if (is_rebase_i(opts) &&  check_todo && !res)\". This block is\n     +        not entered when returning an error because \"res\" is non-zero in\n     +        that case.\n     +\n     +    (3) todo_list->current could be incremented before returning. That is\n     +        avoided by moving the increment which is of course a potential\n     +        change in behavior itself. The move is safe because none of the\n     +        callers look at todo_list after this function returns. Moving the\n     +        increment makes it clear we only want to advance the current item\n     +        if the command was successful.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n     @@ sequencer.c: static int pick_commits(struct repository *r,\n       \t\t} else if (item->command == TODO_EXEC) {\n       \t\t\tchar *end_of_arg = (char *)(arg + item->arg_len);\n       \t\t\tint saved = *end_of_arg;\n     +@@ sequencer.c: static int pick_commits(struct repository *r,\n     + \t\t\treturn -1;\n     + \t\t}\n     + \n     +-\t\ttodo_list->current++;\n     + \t\tif (res)\n     + \t\t\treturn res;\n     ++\n     ++\t\ttodo_list->current++;\n     + \t}\n     + \n     + \tif (is_rebase_i(opts)) {\n 5:  f8e64c1b631 ! 5:  df401945866 rebase: fix rewritten list for failed pick\n     @@ Metadata\n       ## Commit message ##\n          rebase: fix rewritten list for failed pick\n      \n     -    When rebasing commands are moved from the todo list in \"git-rebase-todo\"\n     -    to the \"done\" file just before they are executed. This means that if a\n     -    command fails because it would overwrite an untracked file it has to be\n     -    added back into the todo list before the rebase stops for the user to\n     -    fix the problem. Unfortunately the way this is done results in the\n     -    failed pick being recorded as rewritten.\n     +    git rebase keeps a list that maps the OID of each commit before it was\n     +    rebased to the OID of the equivalent commit after the rebase.  This list\n     +    is used to drive the \"post-rewrite\" hook that is called at the end of a\n     +    successful rebase. When a rebase stops for the user to resolve merge\n     +    conflicts the OID of the commit being picked is written to\n     +    \".git/rebase-merge/stopped-sha\". Then when the rebase is continued that\n     +    OID is added to the list of rewritten commits. Unfortunately if a commit\n     +    cannot be picked because it would overwrite an untracked file we still\n     +    write the \"stopped-sha1\" file. This means that when the rebase is\n     +    continued the commit is added into the list of rewritten commits even\n     +    though it has not been picked yet.\n      \n          Fix this by not calling error_with_patch() for failed commands. The pick\n          has failed so there is nothing to commit and therefore we do not want to\n     -    set up the message file for committing staged changes when the rebase\n     +    set up the state files for committing staged changes when the rebase\n          continues. This change means we no-longer write a patch for the failed\n          command or display the error message printed by error_with_patch(). As\n     -    the command has failed the patch isn't really useful in that case and\n     -    REBASE_HEAD is still written so the user can inspect the commit\n     -    associated with the failed command. Unless the user has disabled it we\n     -    print an advice message that is more helpful than the message from\n     -    error_with_patch(). If the advice is disabled the user will still see\n     -    the messages from the merge machinery detailing the problem.\n     +    the command has failed the patch isn't really useful and in any case the\n     +    user can inspect the commit associated with the failed command by\n     +    inspecting REBASE_HEAD. Unless the user has disabled it we already print\n     +    an advice message that is more helpful than the message from\n     +    error_with_patch() which the user will still see. Even if the advice is\n     +    disabled the user will see the messages from the merge machinery\n     +    detailing the problem.\n      \n          To simplify writing REBASE_HEAD in this case pick_one_commit() is\n     -    modified to avoid duplicating the code that adds the failed command back\n     -    into the todo list.\n     +    modified to avoid duplicating the code that adds the failed command\n     +    back into the todo list.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## sequencer.c ##\n      @@ sequencer.c: static int do_merge(struct repository *r,\n     + \tif (ret < 0) {\n       \t\terror(_(\"could not even attempt to merge '%.*s'\"),\n       \t\t      merge_arg_len, arg);\n     - \t\tunlink(rebase_path_author_script());\n      +\t\tunlink(git_path_merge_msg(r));\n       \t\tgoto leave_merge;\n       \t}\n     @@ sequencer.c: static int pick_commits(struct repository *r,\n      \n       ## t/t3404-rebase-interactive.sh ##\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n     + \t>file6 &&\n     + \ttest_must_fail git rebase --continue &&\n       \ttest_cmp_rev HEAD F &&\n     ++\ttest_cmp_rev REBASE_HEAD I &&\n       \trm file6 &&\n     - \ttest_path_is_missing .git/rebase-merge/author-script &&\n      +\ttest_path_is_missing .git/rebase-merge/patch &&\n     -+\ttest_path_is_missing .git/MERGE_MSG &&\n     -+\ttest_path_is_missing .git/rebase-merge/message &&\n     -+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n     - \techo changed >file1 &&\n     - \tgit add file1 &&\n     - \ttest_must_fail git rebase --continue 2>err &&\n     + \tgit rebase --continue &&\n     + \ttest_cmp_rev HEAD I\n     + '\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n     + \t>file6 &&\n     + \ttest_must_fail git rebase --continue &&\n       \ttest_cmp_rev HEAD F &&\n     ++\ttest_cmp_rev REBASE_HEAD I &&\n       \trm file6 &&\n     - \ttest_path_is_missing .git/rebase-merge/author-script &&\n      +\ttest_path_is_missing .git/rebase-merge/patch &&\n     -+\ttest_path_is_missing .git/MERGE_MSG &&\n     -+\ttest_path_is_missing .git/rebase-merge/message &&\n     -+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n       \tgit rebase --continue &&\n       \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n       \tgit reset --hard original-branch2\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n     + \t>file6 &&\n     + \ttest_must_fail git rebase --continue &&\n       \ttest $(git cat-file commit HEAD | sed -ne \\$p) = F &&\n     ++\ttest_cmp_rev REBASE_HEAD I &&\n       \trm file6 &&\n     - \ttest_path_is_missing .git/rebase-merge/author-script &&\n      +\ttest_path_is_missing .git/rebase-merge/patch &&\n     -+\ttest_path_is_missing .git/MERGE_MSG &&\n     -+\ttest_path_is_missing .git/rebase-merge/message &&\n     -+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n       \tgit rebase --continue &&\n       \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n       '\n      \n       ## t/t3430-rebase-merges.sh ##\n      @@ t/t3430-rebase-merges.sh: test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n     + \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n     + \ttest_tick &&\n       \ttest_must_fail git rebase -ir HEAD &&\n     ++\ttest_cmp_rev REBASE_HEAD H^0 &&\n       \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n       \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n      -\ttest_path_is_file .git/rebase-merge/patch &&\n      +\ttest_path_is_missing .git/rebase-merge/patch &&\n     - \ttest_path_is_missing .git/rebase-merge/author-script &&\n     -+\ttest_path_is_missing .git/MERGE_MSG &&\n     -+\ttest_path_is_missing .git/rebase-merge/message &&\n     -+\ttest_path_is_missing .git/rebase-merge/stopped-sha &&\n       \n       \t: fail because of merge conflict &&\n      -\trm G.t .git/rebase-merge/patch &&\n       \tgit reset --hard conflicting-G &&\n       \ttest_must_fail git rebase --continue &&\n       \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n     - \ttest_path_is_file .git/rebase-merge/patch &&\n     --\ttest_path_is_file .git/rebase-merge/author-script\n     -+\ttest_path_is_file .git/rebase-merge/author-script &&\n     -+\ttest_path_is_file .git/MERGE_MSG &&\n     -+\ttest_path_is_file .git/rebase-merge/message &&\n     -+\ttest_path_is_file .git/rebase-merge/stopped-sha\n     - '\n     - \n     - test_expect_success 'failed `merge <branch>` does not crash' '\n      \n       ## t/t5407-post-rewrite-hook.sh ##\n     +@@ t/t5407-post-rewrite-hook.sh: test_expect_success 'setup' '\n     + \tgit checkout A^0 &&\n     + \ttest_commit E bar E &&\n     + \ttest_commit F foo F &&\n     ++\tgit checkout B &&\n     ++\tgit merge E &&\n     ++\tgit tag merge-E &&\n     ++\ttest_commit G G &&\n     ++\ttest_commit H H &&\n     ++\ttest_commit I I &&\n     + \tgit checkout main &&\n     + \n     + \ttest_hook --setup post-rewrite <<-EOF\n      @@ t/t5407-post-rewrite-hook.sh: test_fail_interactive_rebase () {\n       \t)\n       }\n       \n      +test_expect_success 'git rebase with failed pick' '\n     -+\ttest_fail_interactive_rebase \"exec_>bar pick 1\" --onto C A E &&\n     ++\tclear_hook_input &&\n     ++\tcat >todo <<-\\EOF &&\n     ++\texec >bar\n     ++\tmerge -C merge-E E\n     ++\texec >G\n     ++\tpick G\n     ++\texec >H 2>I\n     ++\tpick H\n     ++\tfixup I\n     ++\tEOF\n     ++\n     ++\t(\n     ++\t\tset_replace_editor todo &&\n     ++\t\ttest_must_fail git rebase -i D D 2>err\n     ++\t) &&\n     ++\tgrep \"would be overwritten\" err &&\n      +\trm bar &&\n     ++\n     ++\ttest_must_fail git rebase --continue 2>err &&\n     ++\tgrep \"would be overwritten\" err &&\n     ++\trm G &&\n     ++\n     ++\ttest_must_fail git rebase --continue 2>err &&\n     ++\tgrep \"would be overwritten\" err &&\n     ++\trm H &&\n     ++\n     ++\ttest_must_fail git rebase --continue 2>err &&\n     ++\tgrep \"would be overwritten\" err &&\n     ++\trm I &&\n     ++\n      +\tgit rebase --continue &&\n      +\techo rebase >expected.args &&\n      +\tcat >expected.data <<-EOF &&\n     -+\t$(git rev-parse E) $(git rev-parse HEAD)\n     ++\t$(git rev-parse merge-E) $(git rev-parse HEAD~2)\n     ++\t$(git rev-parse G) $(git rev-parse HEAD~1)\n     ++\t$(git rev-parse H) $(git rev-parse HEAD)\n     ++\t$(git rev-parse I) $(git rev-parse HEAD)\n      +\tEOF\n      +\tverify_hook_input\n      +'\n 4:  9356d14b09a ! 6:  2ed7cbe5fff rebase --continue: refuse to commit after failed command\n     @@ Commit message\n      \n          If a commit cannot be picked because it would overwrite an untracked\n          file then \"git rebase --continue\" should refuse to commit any staged\n     -    changes as the commit was not picked. Do this by using the existing\n     -    check for a missing author script in run_git_commit() which prevents\n     -    \"rebase --continue\" from committing staged changes after failed exec\n     -    commands.\n     +    changes as the commit was not picked. This is implemented by refusing to\n     +    commit if the message file is missing. The message file is chosen for\n     +    this check because it is only written when \"git rebase\" stops for the\n     +    user to resolve merge conflicts.\n      \n     -    When fast-forwarding it is not necessary to write the author script as\n     -    we're reusing an existing commit, not creating a new one. If a\n     -    fast-forwarded commit is modified by an \"edit\" or \"reword\" command then\n     -    the modification is committed with \"git commit --amend\" which reuses the\n     -    author of the commit being amended so the author script is not needed.\n     -    baf8ec8d3a (rebase -r: don't write .git/MERGE_MSG when fast-forwarding,\n     -    2021-08-20) changed run_git_commit() to allow a missing author script\n     -    when rewording a commit. This changes extends that to allow a missing\n     -    author script whenever the commit is being amended.\n     +    Existing commands that refuse to commit staged changes when continuing\n     +    such as a failed \"exec\" rely on checking for the absence of the author\n     +    script in run_git_commit(). This prevents the staged changes from being\n     +    committed but prints\n      \n     -    If we're not fast-forwarding then we must remove the author script if\n     -    the pick fails.\n     +        error: could not open '.git/rebase-merge/author-script' for\n     +        reading\n     +\n     +    before the message about not being able to commit. This is confusing to\n     +    users and so checking for the message file instead improves the user\n     +    experience. The existing test for refusing to commit after a failed exec\n     +    is updated to check that we do not print the error message about a\n     +    missing author script anymore.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## sequencer.c ##\n     -@@ sequencer.c: static int run_git_commit(const char *defmsg,\n     +@@ sequencer.c: static int commit_staged_changes(struct repository *r,\n       \n     - \tif (is_rebase_i(opts) &&\n     - \t    ((opts->committer_date_is_author_date && !opts->ignore_date) ||\n     --\t     !(!defmsg && (flags & AMEND_MSG))) &&\n     -+\t     !(flags & AMEND_MSG)) &&\n     - \t    read_env_script(&cmd.env)) {\n     - \t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n     + \tis_clean = !has_uncommitted_changes(r, 0);\n       \n     -@@ sequencer.c: static int do_pick_commit(struct repository *r,\n     - \tif (opts->allow_ff && !is_fixup(command) &&\n     - \t    ((parent && oideq(&parent->object.oid, &head)) ||\n     - \t     (!parent && unborn))) {\n     --\t\tif (is_rebase_i(opts))\n     --\t\t\twrite_author_script(msg.message);\n     - \t\tres = fast_forward_to(r, &commit->object.oid, &head, unborn,\n     - \t\t\topts);\n     - \t\tif (res || command != TODO_REWORD)\n     -@@ sequencer.c: static int do_pick_commit(struct repository *r,\n     - \t\t command == TODO_REVERT) {\n     - \t\tres = do_recursive_merge(r, base, next, base_label, next_label,\n     - \t\t\t\t\t &head, &msgbuf, opts);\n     --\t\tif (res < 0)\n     -+\t\tif (res < 0) {\n     -+\t\t\tunlink(rebase_path_author_script());\n     - \t\t\tgoto leave;\n     --\n     -+\t\t}\n     - \t\tres |= write_message(msgbuf.buf, msgbuf.len,\n     - \t\t\t\t     git_path_merge_msg(r), 0);\n     - \t} else {\n     -@@ sequencer.c: static int do_merge(struct repository *r,\n     - \tif (ret < 0) {\n     - \t\terror(_(\"could not even attempt to merge '%.*s'\"),\n     - \t\t      merge_arg_len, arg);\n     -+\t\tunlink(rebase_path_author_script());\n     - \t\tgoto leave_merge;\n     - \t}\n     - \t/*\n     ++\tif (!is_clean && !file_exists(rebase_path_message())) {\n     ++\t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n     ++\n     ++\t\treturn error(_(staged_changes_advice), gpg_opt, gpg_opt);\n     ++\t}\n     + \tif (file_exists(rebase_path_amend())) {\n     + \t\tstruct strbuf rev = STRBUF_INIT;\n     + \t\tstruct object_id head, to_amend;\n      \n       ## t/t3404-rebase-interactive.sh ##\n     +@@ t/t3404-rebase-interactive.sh: test_expect_success 'clean error after failed \"exec\"' '\n     + \techo \"edited again\" > file7 &&\n     + \tgit add file7 &&\n     + \ttest_must_fail git rebase --continue 2>error &&\n     +-\ttest_i18ngrep \"you have staged changes in your working tree\" error\n     ++\ttest_i18ngrep \"you have staged changes in your working tree\" error &&\n     ++\ttest_i18ngrep ! \"could not open.*for reading\" error\n     + '\n     + \n     + test_expect_success 'rebase a detached HEAD' '\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n     - \ttest_must_fail git rebase --continue &&\n     - \ttest_cmp_rev HEAD F &&\n     + \ttest_cmp_rev REBASE_HEAD I &&\n       \trm file6 &&\n     -+\ttest_path_is_missing .git/rebase-merge/author-script &&\n     + \ttest_path_is_missing .git/rebase-merge/patch &&\n      +\techo changed >file1 &&\n      +\tgit add file1 &&\n      +\ttest_must_fail git rebase --continue 2>err &&\n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overw\n       \ttest_cmp_rev HEAD I\n       '\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n     - \ttest_must_fail git rebase --continue &&\n     - \ttest_cmp_rev HEAD F &&\n     + \ttest_cmp_rev REBASE_HEAD I &&\n       \trm file6 &&\n     -+\ttest_path_is_missing .git/rebase-merge/author-script &&\n     + \ttest_path_is_missing .git/rebase-merge/patch &&\n     ++\techo changed >file1 &&\n     ++\tgit add file1 &&\n     ++\ttest_must_fail git rebase --continue 2>err &&\n     ++\tgrep \"error: you have staged changes in your working tree\" err &&\n     ++\tgit reset --hard HEAD &&\n       \tgit rebase --continue &&\n       \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n       \tgit reset --hard original-branch2\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n     - \ttest_must_fail git rebase --continue &&\n     - \ttest $(git cat-file commit HEAD | sed -ne \\$p) = F &&\n     + \ttest_cmp_rev REBASE_HEAD I &&\n       \trm file6 &&\n     -+\ttest_path_is_missing .git/rebase-merge/author-script &&\n     + \ttest_path_is_missing .git/rebase-merge/patch &&\n     ++\techo changed >file1 &&\n     ++\tgit add file1 &&\n     ++\ttest_must_fail git rebase --continue 2>err &&\n     ++\tgrep \"error: you have staged changes in your working tree\" err &&\n     ++\tgit reset --hard HEAD &&\n       \tgit rebase --continue &&\n       \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n       '\n     @@ t/t3430-rebase-merges.sh\n      @@ t/t3430-rebase-merges.sh: test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n       \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n       \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n     - \ttest_path_is_file .git/rebase-merge/patch &&\n     -+\ttest_path_is_missing .git/rebase-merge/author-script &&\n     + \ttest_path_is_missing .git/rebase-merge/patch &&\n     ++\techo changed >file1 &&\n     ++\tgit add file1 &&\n     ++\ttest_must_fail git rebase --continue 2>err &&\n     ++\tgrep \"error: you have staged changes in your working tree\" err &&\n       \n       \t: fail because of merge conflict &&\n     - \trm G.t .git/rebase-merge/patch &&\n       \tgit reset --hard conflicting-G &&\n     - \ttest_must_fail git rebase --continue &&\n     - \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n     --\ttest_path_is_file .git/rebase-merge/patch\n     -+\ttest_path_is_file .git/rebase-merge/patch &&\n     -+\ttest_path_is_file .git/rebase-merge/author-script\n     - '\n     - \n     - test_expect_success 'failed `merge <branch>` does not crash' '\n 6:  a836b049b90 ! 7:  bbe0afde512 rebase -i: fix adding failed command to the todo list\n     @@ Commit message\n          added back into the todo list before the rebase stops for the user to\n          fix the problem.\n      \n     -    Unfortunately when a failed command is added back into the todo list\n     -    the command preceding it is erroneously appended to the \"done\" file.\n     -    This means that when rebase stops after \"pick B\" fails the \"done\"\n     -    file contains\n     +    Unfortunately when a failed command is added back into the todo list the\n     +    command preceding it is erroneously appended to the \"done\" file.  This\n     +    means that when rebase stops after \"pick B\" fails the \"done\" file\n     +    contains\n      \n                  pick A\n                  pick B\n     @@ Commit message\n                  pick A\n                  pick B\n      \n     -    Fix this by not updating the \"done\" file when adding a failed command\n     +    This happens because save_todo() updates the \"done\" file with the\n     +    previous command whenever \"git-rebase-todo\" is updated. When we add the\n     +    failed pick back into \"git-rebase-todo\" we do not want to update\n     +    \"done\". Fix this by adding a \"reschedule\" parameter to save_todo() which\n     +    prevents the \"done\" file from being updated when adding a failed command\n          back into the \"git-rebase-todo\" file. A couple of the existing tests are\n          modified to improve their coverage as none of them trigger this bug or\n          check the \"done\" file.\n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'todo count' '\n      -\t>file6 &&\n      -\ttest_must_fail git rebase --continue &&\n      -\ttest_cmp_rev HEAD F &&\n     +-\ttest_cmp_rev REBASE_HEAD I &&\n      -\trm file6 &&\n      +\ttest_cmp_rev HEAD B &&\n     ++\ttest_cmp_rev REBASE_HEAD C &&\n      +\thead -n3 todo >expect &&\n      +\ttest_cmp expect .git/rebase-merge/done &&\n      +\trm file2 &&\n     - \ttest_path_is_missing .git/rebase-merge/author-script &&\n       \ttest_path_is_missing .git/rebase-merge/patch &&\n     - \ttest_path_is_missing .git/MERGE_MSG &&\n     + \techo changed >file1 &&\n     + \tgit add file1 &&\n      @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n       \tgrep \"error: you have staged changes in your working tree\" err &&\n       \tgit reset --hard HEAD &&\n\n-- \ngitgitgadget\n"},{"id":"480045","messageId":"8f6c0e4056742951ce84acbdb07b0518f7607b2a.1690903412.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v3 3/7] sequencer: use rebase_path_message()","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:28Z","receivedAt":"2023-08-01T15:23:54Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nRather than constructing the path in a struct strbuf use the ready\nmade function to get the path name instead. This was the last\nremaining use of the strbuf so remove it as well.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 7 ++-----\n 1 file changed, 2 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 70b0a7023b0..dbddd19b2c2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3501,7 +3501,6 @@ static int make_patch(struct repository *r,\n \t\t      struct commit *commit,\n \t\t      struct replay_opts *opts)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n \tstruct rev_info log_tree_opt;\n \tconst char *subject;\n \tchar hex[GIT_MAX_HEXSZ + 1];\n@@ -3529,18 +3528,16 @@ static int make_patch(struct repository *r,\n \t\tfclose(log_tree_opt.diffopt.file);\n \t}\n \n-\tstrbuf_addf(&buf, \"%s/message\", get_dir(opts));\n-\tif (!file_exists(buf.buf)) {\n+\tif (!file_exists(rebase_path_message())) {\n \t\tconst char *encoding = get_commit_output_encoding();\n \t\tconst char *commit_buffer = repo_logmsg_reencode(r,\n \t\t\t\t\t\t\t\t commit, NULL,\n \t\t\t\t\t\t\t\t encoding);\n \t\tfind_commit_subject(commit_buffer, &subject);\n-\t\tres |= write_message(subject, strlen(subject), buf.buf, 1);\n+\t\tres |= write_message(subject, strlen(subject), rebase_path_message(), 1);\n \t\trepo_unuse_commit_buffer(r, commit,\n \t\t\t\t\t commit_buffer);\n \t}\n-\tstrbuf_release(&buf);\n \trelease_revisions(&log_tree_opt);\n \n \treturn res;\n-- \ngitgitgadget\n\n"},{"id":"480046","messageId":"a1fad70f4b920252d84b5b5578b1b9b0a7bba7ca.1690903412.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v3 4/7] sequencer: factor out part of pick_commits()","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:29Z","receivedAt":"2023-08-01T15:23:55Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis simplifies the next commit. If a pick fails we now return the error\nat the end of the loop body rather than returning early, a successful\n\"edit\" command continues to return early. There are three things to\ncheck to ensure that removing the early return for an error does not\nchange the behavior of the code:\n\n(1) We could enter the block guarded by \"if (reschedule)\". This block\n    is not entered because \"reschedlue\" is always zero when picking a\n    commit.\n\n(2) We could enter the block guarded by\n    \"else if (is_rebase_i(opts) &&  check_todo && !res)\". This block is\n    not entered when returning an error because \"res\" is non-zero in\n    that case.\n\n(3) todo_list->current could be incremented before returning. That is\n    avoided by moving the increment which is of course a potential\n    change in behavior itself. The move is safe because none of the\n    callers look at todo_list after this function returns. Moving the\n    increment makes it clear we only want to advance the current item\n    if the command was successful.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 132 ++++++++++++++++++++++++++++------------------------\n 1 file changed, 71 insertions(+), 61 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex dbddd19b2c2..62277e7bcc1 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4642,6 +4642,72 @@ N_(\"Could not execute the todo command\\n\"\n \"    git rebase --edit-todo\\n\"\n \"    git rebase --continue\\n\");\n \n+static int pick_one_commit(struct repository *r,\n+\t\t\t   struct todo_list *todo_list,\n+\t\t\t   struct replay_opts *opts,\n+\t\t\t   int *check_todo)\n+{\n+\tint res;\n+\tstruct todo_item *item = todo_list->items + todo_list->current;\n+\tconst char *arg = todo_item_get_arg(todo_list, item);\n+\tif (is_rebase_i(opts))\n+\t\topts->reflog_message = reflog_message(\n+\t\t\topts, command_to_string(item->command), NULL);\n+\n+\tres = do_pick_commit(r, item, opts, is_final_fixup(todo_list),\n+\t\t\t     check_todo);\n+\tif (is_rebase_i(opts) && res < 0) {\n+\t\t/* Reschedule */\n+\t\tadvise(_(rescheduled_advice),\n+\t\t       get_item_line_length(todo_list, todo_list->current),\n+\t\t       get_item_line(todo_list, todo_list->current));\n+\t\ttodo_list->current--;\n+\t\tif (save_todo(todo_list, opts))\n+\t\t\treturn -1;\n+\t}\n+\tif (item->command == TODO_EDIT) {\n+\t\tstruct commit *commit = item->commit;\n+\t\tif (!res) {\n+\t\t\tif (!opts->verbose)\n+\t\t\t\tterm_clear_line();\n+\t\t\tfprintf(stderr, _(\"Stopped at %s...  %.*s\\n\"),\n+\t\t\t\tshort_commit_name(commit), item->arg_len, arg);\n+\t\t}\n+\t\treturn error_with_patch(r, commit,\n+\t\t\t\t\targ, item->arg_len, opts, res, !res);\n+\t}\n+\tif (is_rebase_i(opts) && !res)\n+\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t    peek_command(todo_list, 1));\n+\tif (res && is_fixup(item->command)) {\n+\t\tif (res == 1)\n+\t\t\tintend_to_amend();\n+\t\treturn error_failed_squash(r, item->commit, opts,\n+\t\t\t\t\t   item->arg_len, arg);\n+\t} else if (res && is_rebase_i(opts) && item->commit) {\n+\t\tint to_amend = 0;\n+\t\tstruct object_id oid;\n+\n+\t\t/*\n+\t\t * If we are rewording and have either\n+\t\t * fast-forwarded already, or are about to\n+\t\t * create a new root commit, we want to amend,\n+\t\t * otherwise we do not.\n+\t\t */\n+\t\tif (item->command == TODO_REWORD &&\n+\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n+\t\t    (oideq(&item->commit->object.oid, &oid) ||\n+\t\t     (opts->have_squash_onto &&\n+\t\t      oideq(&opts->squash_onto, &oid))))\n+\t\t\tto_amend = 1;\n+\n+\t\treturn res | error_with_patch(r, item->commit,\n+\t\t\t\t\t      arg, item->arg_len, opts,\n+\t\t\t\t\t      res, to_amend);\n+\t}\n+\treturn res;\n+}\n+\n static int pick_commits(struct repository *r,\n \t\t\tstruct todo_list *todo_list,\n \t\t\tstruct replay_opts *opts)\n@@ -4697,66 +4763,9 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n-\t\t\tif (is_rebase_i(opts))\n-\t\t\t\topts->reflog_message = reflog_message(opts,\n-\t\t\t\t      command_to_string(item->command), NULL);\n-\n-\t\t\tres = do_pick_commit(r, item, opts,\n-\t\t\t\t\t     is_final_fixup(todo_list),\n-\t\t\t\t\t     &check_todo);\n-\t\t\tif (is_rebase_i(opts) && res < 0) {\n-\t\t\t\t/* Reschedule */\n-\t\t\t\tadvise(_(rescheduled_advice),\n-\t\t\t\t       get_item_line_length(todo_list,\n-\t\t\t\t\t\t\t    todo_list->current),\n-\t\t\t\t       get_item_line(todo_list,\n-\t\t\t\t\t\t     todo_list->current));\n-\t\t\t\ttodo_list->current--;\n-\t\t\t\tif (save_todo(todo_list, opts))\n-\t\t\t\t\treturn -1;\n-\t\t\t}\n-\t\t\tif (item->command == TODO_EDIT) {\n-\t\t\t\tstruct commit *commit = item->commit;\n-\t\t\t\tif (!res) {\n-\t\t\t\t\tif (!opts->verbose)\n-\t\t\t\t\t\tterm_clear_line();\n-\t\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\t_(\"Stopped at %s...  %.*s\\n\"),\n-\t\t\t\t\t\tshort_commit_name(commit),\n-\t\t\t\t\t\titem->arg_len, arg);\n-\t\t\t\t}\n-\t\t\t\treturn error_with_patch(r, commit,\n-\t\t\t\t\targ, item->arg_len, opts, res, !res);\n-\t\t\t}\n-\t\t\tif (is_rebase_i(opts) && !res)\n-\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n-\t\t\t\t\tpeek_command(todo_list, 1));\n-\t\t\tif (res && is_fixup(item->command)) {\n-\t\t\t\tif (res == 1)\n-\t\t\t\t\tintend_to_amend();\n-\t\t\t\treturn error_failed_squash(r, item->commit, opts,\n-\t\t\t\t\titem->arg_len, arg);\n-\t\t\t} else if (res && is_rebase_i(opts) && item->commit) {\n-\t\t\t\tint to_amend = 0;\n-\t\t\t\tstruct object_id oid;\n-\n-\t\t\t\t/*\n-\t\t\t\t * If we are rewording and have either\n-\t\t\t\t * fast-forwarded already, or are about to\n-\t\t\t\t * create a new root commit, we want to amend,\n-\t\t\t\t * otherwise we do not.\n-\t\t\t\t */\n-\t\t\t\tif (item->command == TODO_REWORD &&\n-\t\t\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n-\t\t\t\t    (oideq(&item->commit->object.oid, &oid) ||\n-\t\t\t\t     (opts->have_squash_onto &&\n-\t\t\t\t      oideq(&opts->squash_onto, &oid))))\n-\t\t\t\t\tto_amend = 1;\n-\n-\t\t\t\treturn res | error_with_patch(r, item->commit,\n-\t\t\t\t\t\targ, item->arg_len, opts,\n-\t\t\t\t\t\tres, to_amend);\n-\t\t\t}\n+\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n+\t\t\tif (!res && item->command == TODO_EDIT)\n+\t\t\t\treturn 0;\n \t\t} else if (item->command == TODO_EXEC) {\n \t\t\tchar *end_of_arg = (char *)(arg + item->arg_len);\n \t\t\tint saved = *end_of_arg;\n@@ -4817,9 +4826,10 @@ static int pick_commits(struct repository *r,\n \t\t\treturn -1;\n \t\t}\n \n-\t\ttodo_list->current++;\n \t\tif (res)\n \t\t\treturn res;\n+\n+\t\ttodo_list->current++;\n \t}\n \n \tif (is_rebase_i(opts)) {\n-- \ngitgitgadget\n\n"},{"id":"480047","messageId":"df4019458665eccf2b16cdf1d6c1061186a62711.1690903412.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v3 5/7] rebase: fix rewritten list for failed pick","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:30Z","receivedAt":"2023-08-01T15:23:57Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\ngit rebase keeps a list that maps the OID of each commit before it was\nrebased to the OID of the equivalent commit after the rebase.  This list\nis used to drive the \"post-rewrite\" hook that is called at the end of a\nsuccessful rebase. When a rebase stops for the user to resolve merge\nconflicts the OID of the commit being picked is written to\n\".git/rebase-merge/stopped-sha\". Then when the rebase is continued that\nOID is added to the list of rewritten commits. Unfortunately if a commit\ncannot be picked because it would overwrite an untracked file we still\nwrite the \"stopped-sha1\" file. This means that when the rebase is\ncontinued the commit is added into the list of rewritten commits even\nthough it has not been picked yet.\n\nFix this by not calling error_with_patch() for failed commands. The pick\nhas failed so there is nothing to commit and therefore we do not want to\nset up the state files for committing staged changes when the rebase\ncontinues. This change means we no-longer write a patch for the failed\ncommand or display the error message printed by error_with_patch(). As\nthe command has failed the patch isn't really useful and in any case the\nuser can inspect the commit associated with the failed command by\ninspecting REBASE_HEAD. Unless the user has disabled it we already print\nan advice message that is more helpful than the message from\nerror_with_patch() which the user will still see. Even if the advice is\ndisabled the user will see the messages from the merge machinery\ndetailing the problem.\n\nTo simplify writing REBASE_HEAD in this case pick_one_commit() is\nmodified to avoid duplicating the code that adds the failed command\nback into the todo list.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   | 19 +++++---------\n t/t3404-rebase-interactive.sh |  6 +++++\n t/t3430-rebase-merges.sh      |  4 +--\n t/t5407-post-rewrite-hook.sh  | 48 +++++++++++++++++++++++++++++++++++\n 4 files changed, 63 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 62277e7bcc1..e25abfd2fb4 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4155,6 +4155,7 @@ static int do_merge(struct repository *r,\n \tif (ret < 0) {\n \t\terror(_(\"could not even attempt to merge '%.*s'\"),\n \t\t      merge_arg_len, arg);\n+\t\tunlink(git_path_merge_msg(r));\n \t\tgoto leave_merge;\n \t}\n \t/*\n@@ -4645,7 +4646,7 @@ N_(\"Could not execute the todo command\\n\"\n static int pick_one_commit(struct repository *r,\n \t\t\t   struct todo_list *todo_list,\n \t\t\t   struct replay_opts *opts,\n-\t\t\t   int *check_todo)\n+\t\t\t   int *check_todo, int* reschedule)\n {\n \tint res;\n \tstruct todo_item *item = todo_list->items + todo_list->current;\n@@ -4658,12 +4659,8 @@ static int pick_one_commit(struct repository *r,\n \t\t\t     check_todo);\n \tif (is_rebase_i(opts) && res < 0) {\n \t\t/* Reschedule */\n-\t\tadvise(_(rescheduled_advice),\n-\t\t       get_item_line_length(todo_list, todo_list->current),\n-\t\t       get_item_line(todo_list, todo_list->current));\n-\t\ttodo_list->current--;\n-\t\tif (save_todo(todo_list, opts))\n-\t\t\treturn -1;\n+\t\t*reschedule = 1;\n+\t\treturn -1;\n \t}\n \tif (item->command == TODO_EDIT) {\n \t\tstruct commit *commit = item->commit;\n@@ -4763,7 +4760,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n-\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n+\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo,\n+\t\t\t\t\t      &reschedule);\n \t\t\tif (!res && item->command == TODO_EDIT)\n \t\t\t\treturn 0;\n \t\t} else if (item->command == TODO_EXEC) {\n@@ -4817,10 +4815,7 @@ static int pick_commits(struct repository *r,\n \t\t\tif (save_todo(todo_list, opts))\n \t\t\t\treturn -1;\n \t\t\tif (item->commit)\n-\t\t\t\treturn error_with_patch(r,\n-\t\t\t\t\t\t\titem->commit,\n-\t\t\t\t\t\t\targ, item->arg_len,\n-\t\t\t\t\t\t\topts, res, 0);\n+\t\t\t\twrite_rebase_head(&item->commit->object.oid);\n \t\t} else if (is_rebase_i(opts) && check_todo && !res &&\n \t\t\t   reread_todo_if_changed(r, todo_list, opts)) {\n \t\t\treturn -1;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ff0afad63e2..6d3788c588b 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1287,7 +1287,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \t>file6 &&\n \ttest_must_fail git rebase --continue &&\n \ttest_cmp_rev HEAD F &&\n+\ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \tgit rebase --continue &&\n \ttest_cmp_rev HEAD I\n '\n@@ -1305,7 +1307,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n \t>file6 &&\n \ttest_must_fail git rebase --continue &&\n \ttest_cmp_rev HEAD F &&\n+\ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n \tgit reset --hard original-branch2\n@@ -1323,7 +1327,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n \t>file6 &&\n \ttest_must_fail git rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = F &&\n+\ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 96ae0edf1e1..4938ebb1c17 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -165,12 +165,12 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n \ttest_tick &&\n \ttest_must_fail git rebase -ir HEAD &&\n+\ttest_cmp_rev REBASE_HEAD H^0 &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n-\ttest_path_is_file .git/rebase-merge/patch &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \n \t: fail because of merge conflict &&\n-\trm G.t .git/rebase-merge/patch &&\n \tgit reset --hard conflicting-G &&\n \ttest_must_fail git rebase --continue &&\n \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 5f3ff051ca2..ad7f8c6f002 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -17,6 +17,12 @@ test_expect_success 'setup' '\n \tgit checkout A^0 &&\n \ttest_commit E bar E &&\n \ttest_commit F foo F &&\n+\tgit checkout B &&\n+\tgit merge E &&\n+\tgit tag merge-E &&\n+\ttest_commit G G &&\n+\ttest_commit H H &&\n+\ttest_commit I I &&\n \tgit checkout main &&\n \n \ttest_hook --setup post-rewrite <<-EOF\n@@ -173,6 +179,48 @@ test_fail_interactive_rebase () {\n \t)\n }\n \n+test_expect_success 'git rebase with failed pick' '\n+\tclear_hook_input &&\n+\tcat >todo <<-\\EOF &&\n+\texec >bar\n+\tmerge -C merge-E E\n+\texec >G\n+\tpick G\n+\texec >H 2>I\n+\tpick H\n+\tfixup I\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i D D 2>err\n+\t) &&\n+\tgrep \"would be overwritten\" err &&\n+\trm bar &&\n+\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"would be overwritten\" err &&\n+\trm G &&\n+\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"would be overwritten\" err &&\n+\trm H &&\n+\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"would be overwritten\" err &&\n+\trm I &&\n+\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<-EOF &&\n+\t$(git rev-parse merge-E) $(git rev-parse HEAD~2)\n+\t$(git rev-parse G) $(git rev-parse HEAD~1)\n+\t$(git rev-parse H) $(git rev-parse HEAD)\n+\t$(git rev-parse I) $(git rev-parse HEAD)\n+\tEOF\n+\tverify_hook_input\n+'\n+\n test_expect_success 'git rebase -i (unchanged)' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-- \ngitgitgadget\n\n"},{"id":"480048","messageId":"2ed7cbe5fff2d104a1246783909c6c4b75c559f2.1690903412.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v3 6/7] rebase --continue: refuse to commit after failed command","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:31Z","receivedAt":"2023-08-01T15:23:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf a commit cannot be picked because it would overwrite an untracked\nfile then \"git rebase --continue\" should refuse to commit any staged\nchanges as the commit was not picked. This is implemented by refusing to\ncommit if the message file is missing. The message file is chosen for\nthis check because it is only written when \"git rebase\" stops for the\nuser to resolve merge conflicts.\n\nExisting commands that refuse to commit staged changes when continuing\nsuch as a failed \"exec\" rely on checking for the absence of the author\nscript in run_git_commit(). This prevents the staged changes from being\ncommitted but prints\n\n    error: could not open '.git/rebase-merge/author-script' for\n    reading\n\nbefore the message about not being able to commit. This is confusing to\nusers and so checking for the message file instead improves the user\nexperience. The existing test for refusing to commit after a failed exec\nis updated to check that we do not print the error message about a\nmissing author script anymore.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   |  5 +++++\n t/t3404-rebase-interactive.sh | 18 +++++++++++++++++-\n t/t3430-rebase-merges.sh      |  4 ++++\n 3 files changed, 26 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e25abfd2fb4..a90b015e79c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4977,6 +4977,11 @@ static int commit_staged_changes(struct repository *r,\n \n \tis_clean = !has_uncommitted_changes(r, 0);\n \n+\tif (!is_clean && !file_exists(rebase_path_message())) {\n+\t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n+\n+\t\treturn error(_(staged_changes_advice), gpg_opt, gpg_opt);\n+\t}\n \tif (file_exists(rebase_path_amend())) {\n \t\tstruct strbuf rev = STRBUF_INIT;\n \t\tstruct object_id head, to_amend;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 6d3788c588b..a8ad398956a 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -604,7 +604,8 @@ test_expect_success 'clean error after failed \"exec\"' '\n \techo \"edited again\" > file7 &&\n \tgit add file7 &&\n \ttest_must_fail git rebase --continue 2>error &&\n-\ttest_i18ngrep \"you have staged changes in your working tree\" error\n+\ttest_i18ngrep \"you have staged changes in your working tree\" error &&\n+\ttest_i18ngrep ! \"could not open.*for reading\" error\n '\n \n test_expect_success 'rebase a detached HEAD' '\n@@ -1290,6 +1291,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n+\tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n \ttest_cmp_rev HEAD I\n '\n@@ -1310,6 +1316,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n \ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n+\tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n \tgit reset --hard original-branch2\n@@ -1330,6 +1341,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n \ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n+\tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 4938ebb1c17..804ff819782 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -169,6 +169,10 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n \n \t: fail because of merge conflict &&\n \tgit reset --hard conflicting-G &&\n-- \ngitgitgadget\n\n"},{"id":"480049","messageId":"bbe0afde512c52bc818fd1d10d10b3af400246ba.1690903412.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v3 7/7] rebase -i: fix adding failed command to the todo list","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-01T15:23:32Z","receivedAt":"2023-08-01T15:24:00Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen rebasing commands are moved from the todo list in \"git-rebase-todo\"\nto the \"done\" file (which is used by \"git status\" to show the recently\nexecuted commands) just before they are executed. This means that if a\ncommand fails because it would overwrite an untracked file it has to be\nadded back into the todo list before the rebase stops for the user to\nfix the problem.\n\nUnfortunately when a failed command is added back into the todo list the\ncommand preceding it is erroneously appended to the \"done\" file.  This\nmeans that when rebase stops after \"pick B\" fails the \"done\" file\ncontains\n\n\tpick A\n\tpick B\n\tpick A\n\ninstead of\n\n\tpick A\n\tpick B\n\nThis happens because save_todo() updates the \"done\" file with the\nprevious command whenever \"git-rebase-todo\" is updated. When we add the\nfailed pick back into \"git-rebase-todo\" we do not want to update\n\"done\". Fix this by adding a \"reschedule\" parameter to save_todo() which\nprevents the \"done\" file from being updated when adding a failed command\nback into the \"git-rebase-todo\" file. A couple of the existing tests are\nmodified to improve their coverage as none of them trigger this bug or\ncheck the \"done\" file.\n\nReported-by: Stefan Haller <lists@haller-berlin.de>\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   | 12 ++++++------\n t/t3404-rebase-interactive.sh | 29 ++++++++++++++++++-----------\n t/t3430-rebase-merges.sh      | 22 ++++++++++++++++------\n 3 files changed, 40 insertions(+), 23 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex a90b015e79c..4976d159745 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3397,7 +3397,8 @@ give_advice:\n \treturn -1;\n }\n \n-static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n+static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,\n+\t\t     int reschedule)\n {\n \tstruct lock_file todo_lock = LOCK_INIT;\n \tconst char *todo_path = get_todo_path(opts);\n@@ -3407,7 +3408,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \t * rebase -i writes \"git-rebase-todo\" without the currently executing\n \t * command, appending it to \"done\" instead.\n \t */\n-\tif (is_rebase_i(opts))\n+\tif (is_rebase_i(opts) && !reschedule)\n \t\tnext++;\n \n \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n@@ -3420,7 +3421,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tif (commit_lock_file(&todo_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n \n-\tif (is_rebase_i(opts) && next > 0) {\n+\tif (is_rebase_i(opts) && !reschedule && next > 0) {\n \t\tconst char *done = rebase_path_done();\n \t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n \t\tint ret = 0;\n@@ -4730,7 +4731,7 @@ static int pick_commits(struct repository *r,\n \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n \t\tint check_todo = 0;\n \n-\t\tif (save_todo(todo_list, opts))\n+\t\tif (save_todo(todo_list, opts, reschedule))\n \t\t\treturn -1;\n \t\tif (is_rebase_i(opts)) {\n \t\t\tif (item->command != TODO_COMMENT) {\n@@ -4811,8 +4812,7 @@ static int pick_commits(struct repository *r,\n \t\t\t       get_item_line_length(todo_list,\n \t\t\t\t\t\t    todo_list->current),\n \t\t\t       get_item_line(todo_list, todo_list->current));\n-\t\t\ttodo_list->current--;\n-\t\t\tif (save_todo(todo_list, opts))\n+\t\t\tif (save_todo(todo_list, opts, reschedule))\n \t\t\t\treturn -1;\n \t\t\tif (item->commit)\n \t\t\t\twrite_rebase_head(&item->commit->object.oid);\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex a8ad398956a..71da9c465a1 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1277,19 +1277,24 @@ test_expect_success 'todo count' '\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n-\tgit checkout --force branch2 &&\n+\tgit checkout --force A &&\n \tgit clean -f &&\n+\tcat >todo <<-EOF &&\n+\texec >file2\n+\tpick $(git rev-parse B) B\n+\tpick $(git rev-parse C) C\n+\tpick $(git rev-parse D) D\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \t(\n-\t\tset_fake_editor &&\n-\t\tFAKE_LINES=\"edit 1 2\" git rebase -i A\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i A\n \t) &&\n-\ttest_cmp_rev HEAD F &&\n-\ttest_path_is_missing file6 &&\n-\t>file6 &&\n-\ttest_must_fail git rebase --continue &&\n-\ttest_cmp_rev HEAD F &&\n-\ttest_cmp_rev REBASE_HEAD I &&\n-\trm file6 &&\n+\ttest_cmp_rev HEAD B &&\n+\ttest_cmp_rev REBASE_HEAD C &&\n+\thead -n3 todo >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm file2 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n \techo changed >file1 &&\n \tgit add file1 &&\n@@ -1297,7 +1302,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \tgrep \"error: you have staged changes in your working tree\" err &&\n \tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n-\ttest_cmp_rev HEAD I\n+\ttest_cmp_rev HEAD D &&\n+\ttail -n3 todo >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (squash)' '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 804ff819782..0b0877b9846 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -128,14 +128,24 @@ test_expect_success 'generate correct todo list' '\n '\n \n test_expect_success '`reset` refuses to overwrite untracked files' '\n-\tgit checkout -b refuse-to-reset &&\n+\tgit checkout B &&\n \ttest_commit dont-overwrite-untracked &&\n-\tgit checkout @{-1} &&\n-\t: >dont-overwrite-untracked.t &&\n-\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n+\tcat >script-from-scratch <<-EOF &&\n+\texec >dont-overwrite-untracked.t\n+\tpick $(git rev-parse B) B\n+\treset refs/tags/dont-overwrite-untracked\n+\tpick $(git rev-parse C) C\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n-\ttest_must_fail git rebase -ir HEAD &&\n-\tgit rebase --abort\n+\ttest_must_fail git rebase -ir A &&\n+\ttest_cmp_rev HEAD B &&\n+\thead -n3 script-from-scratch >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm dont-overwrite-untracked.t &&\n+\tgit rebase --continue &&\n+\ttail -n3 script-from-scratch >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success '`reset` rejects trees' '\n-- \ngitgitgadget\n"},{"id":"480052","messageId":"xmqqa5vad6ea.fsf@gitster.g","threadId":"59416","inReplyTo":"1ab1ad2ef07687c25c1d346b5b7b26f38bafe5b9.1690903412.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/7] rebase -i: move unlink() calls","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-01T17:22:37Z","receivedAt":"2023-08-01T17:23:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> At the start of each iteration the loop that picks commits removes\n> state files from the previous pick. However some of these are only\n> written if there are conflicts and so we break out of the loop after\n> writing them. Therefore they only need to be removed when the rebase\n> continues, not in each iteration.\n\n\"They only need to be removed before the loop\" assumes that the SOLE\npurpose of the removal is to give the next iteration a clean slate\nto work with, but is that really the case?  The original unlink's is\nfollowed by \"if TODO_BREAK, break out of the loop\", presumably to\ngive control back to the end-user.  So three files that were not\navailable to the user after \"break\" are now suddenly visible to\nthem.\n\nPerhaps that is the effect the series wanted to have.  Or it could\nbe an unintended side effect that may be a regression.  Or perhaps\nthe visibility of these three files (but not others?) is considered\nan implementation detail no users should ever depend on.\n\nIt is hard to tell from the above description and the patch text\nwhich is the case.  Care to enlighten?\n\nThanks.\n\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  sequencer.c | 7 ++++---\n>  1 file changed, 4 insertions(+), 3 deletions(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index cc9821ece2c..de66bda9d5b 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4656,6 +4656,10 @@ static int pick_commits(struct repository *r,\n>  \tif (read_and_refresh_cache(r, opts))\n>  \t\treturn -1;\n>  \n> +\tunlink(rebase_path_message());\n> +\tunlink(rebase_path_stopped_sha());\n> +\tunlink(rebase_path_amend());\n> +\n>  \twhile (todo_list->current < todo_list->nr) {\n>  \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n>  \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n> @@ -4679,10 +4683,7 @@ static int pick_commits(struct repository *r,\n>  \t\t\t\t\t\ttodo_list->total_nr,\n>  \t\t\t\t\t\topts->verbose ? \"\\n\" : \"\\r\");\n>  \t\t\t}\n> -\t\t\tunlink(rebase_path_message());\n>  \t\t\tunlink(rebase_path_author_script());\n> -\t\t\tunlink(rebase_path_stopped_sha());\n> -\t\t\tunlink(rebase_path_amend());\n>  \t\t\tunlink(git_path_merge_head(r));\n>  \t\t\tunlink(git_path_auto_merge(r));\n>  \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n"},{"id":"480053","messageId":"xmqq5y5yd6d7.fsf@gitster.g","threadId":"59416","inReplyTo":"e2a758eb4a5df0fab189b3dd235a1651a0c10342.1690903412.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 2/7] rebase -i: remove patch file after conflict resolution","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-01T17:23:16Z","receivedAt":"2023-08-01T17:24:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> When a rebase stops for the user to resolve conflicts it writes a patch\n> for the conflicting commit to .git/rebase-merge/patch. This file has\n> been written since the introduction of \"git-rebase-interactive.sh\" in\n> 1b1dce4bae7 (Teach rebase an interactive mode, 2007-06-25). I assume the\n> idea was to enable the user inspect the conflicting commit in the same\n> way as they could for the patch based rebase. This file should be\n> deleted when the rebase continues as if the rebase stops for a failed\n> \"exec\" command or a \"break\" command it is confusing to the user if there\n> is a stale patch lying around from an unrelated command.\n\nUnlike the previous step, this describes the change in end-user\nobservable behaviour *and* asserts that it is an intended change.\n\nVery good.\n\n> As the path is\n> now used in two different places rebase_path_patch() is added and used\n> to obtain the path for the patch.\n\nThe get_dir() function gives different paths, between \"rebase-merge\"\n(for \"rebase -i\") and \"sequencer\" (for everything else), and that is\nthe parent directory of \"/patch\" output make_patch() uses.\n\nerror_with_patch() is the only caller of make_patch(), and\nerror_with_patch() is called by\n\n error_failed_squash() - called from pick_commits()\n pick_commits() - when TODO_EDIT stops the sequence, or\n\t\t  a non fix-up insn failed when is_rebase_i(), or\n\t\t  a merge insn failed, or\n\t\t  a reschedule happened.\n\nAre we sure that it is the right thing to do to hardcode\n\"rebase-merge/patch\"?  Unless \"rebase -i\" is the only thing that\ncalls pick_commits() and reaches error_with_patch() to cause\nmake_patch() to be called, this changes the behaviour for cases the\ntests added by this patch do not cover, doesn't it?\n\nI would feel safer if this change is accompanied by something like\n\ndiff --git i/sequencer.c w/sequencer.c\nindex cc9821ece2..a5ec8538fa 100644\n--- i/sequencer.c\n+++ w/sequencer.c\n@@ -3502,6 +3502,9 @@ static int make_patch(struct repository *r,\n \tchar hex[GIT_MAX_HEXSZ + 1];\n \tint res = 0;\n \n+\tif (!is_rebase_i(opts))\n+\t\tBUG(\"make-patch should only be triggered during rebase -i\");\n+\n \toid_to_hex_r(hex, &commit->object.oid);\n \tif (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)\n \t\treturn -1;\n\nto make sure that future changes to \"git cherry-pick A..B\" that\nmakes it easier to edit .git/sequencer/todo and tweak \"pick\" into\n\"edit\" (aka \"git cherry-pick -i\") would not happen unless the author\nof such a change considerts its ramifications first.\n\nAlternatively, we could still introduce a handy path function, but\ncall it sequencer_path_patch() that does get_dir() + \"/patch\", i.e.\nreturn different paths honoring is_rebase_i(), to make sure we will\nbehave the same way as before.  That might be safer.\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  sequencer.c                | 13 +++++++++----\n>  t/t3418-rebase-continue.sh | 18 ++++++++++++++++++\n>  2 files changed, 27 insertions(+), 4 deletions(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index de66bda9d5b..70b0a7023b0 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4659,6 +4663,7 @@ static int pick_commits(struct repository *r,\n>  \tunlink(rebase_path_message());\n>  \tunlink(rebase_path_stopped_sha());\n>  \tunlink(rebase_path_amend());\n> +\tunlink(rebase_path_patch());\n>  \n>  \twhile (todo_list->current < todo_list->nr) {\n>  \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n\nOther hunks are about \"get_dir() + /patch\" -> \"rebase_path_patch()\",\nbut this hunk is about the intended behaviour change.  We clear the\nleftover patch file from the previous round before we enter the loop\nto process new insn from the list, which makes sense.\n\n> diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n> index 2d0789e554b..261e7cd754c 100755\n> --- a/t/t3418-rebase-continue.sh\n> +++ b/t/t3418-rebase-continue.sh\n> @@ -244,6 +244,24 @@ test_expect_success 'the todo command \"break\" works' '\n>  \ttest_path_is_file execed\n>  '\n>  \n> +test_expect_success 'patch file is removed before break command' '\n> +\ttest_when_finished \"git rebase --abort\" &&\n> +\tcat >todo <<-\\EOF &&\n> +\tpick commit-new-file-F2-on-topic-branch\n> +\tbreak\n> +\tEOF\n> +\n> +\t(\n> +\t\tset_replace_editor todo &&\n> +\t\ttest_must_fail git rebase -i --onto commit-new-file-F2 HEAD\n> +\t) &&\n> +\ttest_path_is_file .git/rebase-merge/patch &&\n> +\techo 22>F2 &&\n> +\tgit add F2 &&\n> +\tgit rebase --continue &&\n> +\ttest_path_is_missing .git/rebase-merge/patch\n> +'\n> +\n>  test_expect_success '--reschedule-failed-exec' '\n>  \ttest_when_finished \"git rebase --abort\" &&\n>  \ttest_must_fail git rebase -x false --reschedule-failed-exec HEAD^ &&\n"},{"id":"480055","messageId":"xmqq4jlid6cx.fsf@gitster.g","threadId":"59416","inReplyTo":"8f6c0e4056742951ce84acbdb07b0518f7607b2a.1690903412.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/7] sequencer: use rebase_path_message()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-01T17:23:26Z","receivedAt":"2023-08-01T17:24:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> Rather than constructing the path in a struct strbuf use the ready\n> made function to get the path name instead. This was the last\n> remaining use of the strbuf so remove it as well.\n\nThe same comment about \"get_dir() vs hardcoded rebase-merge\" applies\nhere, I think.  And the same (1) assertion to ensure that we are in\n\"rebase -i\" when make_patch() is called should give us a sufficient\nsafety valve, or (2) instead of hardcoding rebase_path_message(),\ncall it sequencer_path_message() and switch at runtime behaving the\nsame way as get_dir(opts) based version, would also work.\n\nThanks.\n\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  sequencer.c | 7 ++-----\n>  1 file changed, 2 insertions(+), 5 deletions(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index 70b0a7023b0..dbddd19b2c2 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -3501,7 +3501,6 @@ static int make_patch(struct repository *r,\n>  \t\t      struct commit *commit,\n>  \t\t      struct replay_opts *opts)\n>  {\n> -\tstruct strbuf buf = STRBUF_INIT;\n>  \tstruct rev_info log_tree_opt;\n>  \tconst char *subject;\n>  \tchar hex[GIT_MAX_HEXSZ + 1];\n> @@ -3529,18 +3528,16 @@ static int make_patch(struct repository *r,\n>  \t\tfclose(log_tree_opt.diffopt.file);\n>  \t}\n>  \n> -\tstrbuf_addf(&buf, \"%s/message\", get_dir(opts));\n> -\tif (!file_exists(buf.buf)) {\n> +\tif (!file_exists(rebase_path_message())) {\n>  \t\tconst char *encoding = get_commit_output_encoding();\n>  \t\tconst char *commit_buffer = repo_logmsg_reencode(r,\n>  \t\t\t\t\t\t\t\t commit, NULL,\n>  \t\t\t\t\t\t\t\t encoding);\n>  \t\tfind_commit_subject(commit_buffer, &subject);\n> -\t\tres |= write_message(subject, strlen(subject), buf.buf, 1);\n> +\t\tres |= write_message(subject, strlen(subject), rebase_path_message(), 1);\n>  \t\trepo_unuse_commit_buffer(r, commit,\n>  \t\t\t\t\t commit_buffer);\n>  \t}\n> -\tstrbuf_release(&buf);\n>  \trelease_revisions(&log_tree_opt);\n>  \n>  \treturn res;\n"},{"id":"480074","messageId":"48fefaf0-6a31-0518-3424-73d93343f1d8@gmail.com","threadId":"59416","inReplyTo":"xmqqa5vad6ea.fsf@gitster.g","subject":"Re: [PATCH v3 1/7] rebase -i: move unlink() calls","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-08-01T18:42:15Z","receivedAt":"2023-08-01T18:42:51Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 01/08/2023 18:22, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> At the start of each iteration the loop that picks commits removes\n>> state files from the previous pick. However some of these are only\n>> written if there are conflicts and so we break out of the loop after\n>> writing them. Therefore they only need to be removed when the rebase\n>> continues, not in each iteration.\n> \n> \"They only need to be removed before the loop\" assumes that the SOLE\n> purpose of the removal is to give the next iteration a clean slate\n> to work with, but is that really the case?  The original unlink's is\n> followed by \"if TODO_BREAK, break out of the loop\", presumably to\n> give control back to the end-user.  So three files that were not\n> available to the user after \"break\" are now suddenly visible to\n> them.\n\nThe files will never exist when the \"if TODO_BREAK\" is executed because \nwe've removed them before entering the loop and as I tried and seemly \nfailed to explain in the  commit message they are only created when \nwe're about to break out of the loop.\n\nBest Wishes\n\nPhillip\n\n> Perhaps that is the effect the series wanted to have.  Or it could\n> be an unintended side effect that may be a regression.  Or perhaps\n> the visibility of these three files (but not others?) is considered\n> an implementation detail no users should ever depend on.\n> \n> It is hard to tell from the above description and the patch text\n> which is the case.  Care to enlighten?\n> \n> Thanks.\n> \n> \n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   sequencer.c | 7 ++++---\n>>   1 file changed, 4 insertions(+), 3 deletions(-)\n>>\n>> diff --git a/sequencer.c b/sequencer.c\n>> index cc9821ece2c..de66bda9d5b 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -4656,6 +4656,10 @@ static int pick_commits(struct repository *r,\n>>   \tif (read_and_refresh_cache(r, opts))\n>>   \t\treturn -1;\n>>   \n>> +\tunlink(rebase_path_message());\n>> +\tunlink(rebase_path_stopped_sha());\n>> +\tunlink(rebase_path_amend());\n>> +\n>>   \twhile (todo_list->current < todo_list->nr) {\n>>   \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n>>   \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n>> @@ -4679,10 +4683,7 @@ static int pick_commits(struct repository *r,\n>>   \t\t\t\t\t\ttodo_list->total_nr,\n>>   \t\t\t\t\t\topts->verbose ? \"\\n\" : \"\\r\");\n>>   \t\t\t}\n>> -\t\t\tunlink(rebase_path_message());\n>>   \t\t\tunlink(rebase_path_author_script());\n>> -\t\t\tunlink(rebase_path_stopped_sha());\n>> -\t\t\tunlink(rebase_path_amend());\n>>   \t\t\tunlink(git_path_merge_head(r));\n>>   \t\t\tunlink(git_path_auto_merge(r));\n>>   \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n"},{"id":"480076","messageId":"8588c7d5-6ba1-7916-4131-bcf388452254@gmail.com","threadId":"59416","inReplyTo":"xmqq5y5yd6d7.fsf@gitster.g","subject":"Re: [PATCH v3 2/7] rebase -i: remove patch file after conflict resolution","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-08-01T18:47:08Z","receivedAt":"2023-08-01T18:47:46Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 01/08/2023 18:23, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> When a rebase stops for the user to resolve conflicts it writes a patch\n>> for the conflicting commit to .git/rebase-merge/patch. This file has\n>> been written since the introduction of \"git-rebase-interactive.sh\" in\n>> 1b1dce4bae7 (Teach rebase an interactive mode, 2007-06-25). I assume the\n>> idea was to enable the user inspect the conflicting commit in the same\n>> way as they could for the patch based rebase. This file should be\n>> deleted when the rebase continues as if the rebase stops for a failed\n>> \"exec\" command or a \"break\" command it is confusing to the user if there\n>> is a stale patch lying around from an unrelated command.\n> \n> Unlike the previous step, this describes the change in end-user\n> observable behaviour *and* asserts that it is an intended change.\n\nThere is no intended change in the observable behavior in the previous \npatch.\n\n> Very good.\n> \n>> As the path is\n>> now used in two different places rebase_path_patch() is added and used\n>> to obtain the path for the patch.\n> \n> The get_dir() function gives different paths, between \"rebase-merge\"\n> (for \"rebase -i\") and \"sequencer\" (for everything else), and that is\n> the parent directory of \"/patch\" output make_patch() uses.\n\nGood point - the patch file is only ever created by \"rebase -i\". I'll \nadd the assertion you suggest below.\n\nBest Wishes\n\nPhillip\n\n> error_with_patch() is the only caller of make_patch(), and\n> error_with_patch() is called by\n> \n>   error_failed_squash() - called from pick_commits()\n>   pick_commits() - when TODO_EDIT stops the sequence, or\n> \t\t  a non fix-up insn failed when is_rebase_i(), or\n> \t\t  a merge insn failed, or\n> \t\t  a reschedule happened.\n> \n> Are we sure that it is the right thing to do to hardcode\n> \"rebase-merge/patch\"?  Unless \"rebase -i\" is the only thing that\n> calls pick_commits() and reaches error_with_patch() to cause\n> make_patch() to be called, this changes the behaviour for cases the\n> tests added by this patch do not cover, doesn't it?\n> \n> I would feel safer if this change is accompanied by something like\n >\n> diff --git i/sequencer.c w/sequencer.c\n> index cc9821ece2..a5ec8538fa 100644\n> --- i/sequencer.c\n> +++ w/sequencer.c\n> @@ -3502,6 +3502,9 @@ static int make_patch(struct repository *r,\n>   \tchar hex[GIT_MAX_HEXSZ + 1];\n>   \tint res = 0;\n>   \n> +\tif (!is_rebase_i(opts))\n> +\t\tBUG(\"make-patch should only be triggered during rebase -i\");\n> +\n>   \toid_to_hex_r(hex, &commit->object.oid);\n>   \tif (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)\n>   \t\treturn -1;\n> \n> to make sure that future changes to \"git cherry-pick A..B\" that\n> makes it easier to edit .git/sequencer/todo and tweak \"pick\" into\n> \"edit\" (aka \"git cherry-pick -i\") would not happen unless the author\n> of such a change considerts its ramifications first.\n> \n> Alternatively, we could still introduce a handy path function, but\n> call it sequencer_path_patch() that does get_dir() + \"/patch\", i.e.\n> return different paths honoring is_rebase_i(), to make sure we will\n> behave the same way as before.  That might be safer.\n> \n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   sequencer.c                | 13 +++++++++----\n>>   t/t3418-rebase-continue.sh | 18 ++++++++++++++++++\n>>   2 files changed, 27 insertions(+), 4 deletions(-)\n>>\n>> diff --git a/sequencer.c b/sequencer.c\n>> index de66bda9d5b..70b0a7023b0 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -4659,6 +4663,7 @@ static int pick_commits(struct repository *r,\n>>   \tunlink(rebase_path_message());\n>>   \tunlink(rebase_path_stopped_sha());\n>>   \tunlink(rebase_path_amend());\n>> +\tunlink(rebase_path_patch());\n>>   \n>>   \twhile (todo_list->current < todo_list->nr) {\n>>   \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n> \n> Other hunks are about \"get_dir() + /patch\" -> \"rebase_path_patch()\",\n> but this hunk is about the intended behaviour change.  We clear the\n> leftover patch file from the previous round before we enter the loop\n> to process new insn from the list, which makes sense.\n> \n>> diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\n>> index 2d0789e554b..261e7cd754c 100755\n>> --- a/t/t3418-rebase-continue.sh\n>> +++ b/t/t3418-rebase-continue.sh\n>> @@ -244,6 +244,24 @@ test_expect_success 'the todo command \"break\" works' '\n>>   \ttest_path_is_file execed\n>>   '\n>>   \n>> +test_expect_success 'patch file is removed before break command' '\n>> +\ttest_when_finished \"git rebase --abort\" &&\n>> +\tcat >todo <<-\\EOF &&\n>> +\tpick commit-new-file-F2-on-topic-branch\n>> +\tbreak\n>> +\tEOF\n>> +\n>> +\t(\n>> +\t\tset_replace_editor todo &&\n>> +\t\ttest_must_fail git rebase -i --onto commit-new-file-F2 HEAD\n>> +\t) &&\n>> +\ttest_path_is_file .git/rebase-merge/patch &&\n>> +\techo 22>F2 &&\n>> +\tgit add F2 &&\n>> +\tgit rebase --continue &&\n>> +\ttest_path_is_missing .git/rebase-merge/patch\n>> +'\n>> +\n>>   test_expect_success '--reschedule-failed-exec' '\n>>   \ttest_when_finished \"git rebase --abort\" &&\n>>   \ttest_must_fail git rebase -x false --reschedule-failed-exec HEAD^ &&\n"},{"id":"480077","messageId":"619e458b-218b-a790-dfb4-9200e201b513@gmail.com","threadId":"59416","inReplyTo":"xmqq4jlid6cx.fsf@gitster.g","subject":"Re: [PATCH v3 3/7] sequencer: use rebase_path_message()","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-08-01T18:49:34Z","receivedAt":"2023-08-01T18:49:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 01/08/2023 18:23, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> Rather than constructing the path in a struct strbuf use the ready\n>> made function to get the path name instead. This was the last\n>> remaining use of the strbuf so remove it as well.\n> \n> The same comment about \"get_dir() vs hardcoded rebase-merge\" applies\n> here, I think.  And the same (1) assertion to ensure that we are in\n> \"rebase -i\" when make_patch() is called should give us a sufficient\n> safety valve,\n\nAgreed\n\n> or (2) instead of hardcoding rebase_path_message(),\n> call it sequencer_path_message() and switch at runtime behaving the\n> same way as get_dir(opts) based version, would also work.\n\nI think that would me misleading because cherry-pick/revert do not \ncreate that file - they rely on \"git commit\" reading .git/MERGE_MSG\n\nBest Wishes\n\nPhillip\n\n> Thanks.\n> \n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   sequencer.c | 7 ++-----\n>>   1 file changed, 2 insertions(+), 5 deletions(-)\n>>\n>> diff --git a/sequencer.c b/sequencer.c\n>> index 70b0a7023b0..dbddd19b2c2 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -3501,7 +3501,6 @@ static int make_patch(struct repository *r,\n>>   \t\t      struct commit *commit,\n>>   \t\t      struct replay_opts *opts)\n>>   {\n>> -\tstruct strbuf buf = STRBUF_INIT;\n>>   \tstruct rev_info log_tree_opt;\n>>   \tconst char *subject;\n>>   \tchar hex[GIT_MAX_HEXSZ + 1];\n>> @@ -3529,18 +3528,16 @@ static int make_patch(struct repository *r,\n>>   \t\tfclose(log_tree_opt.diffopt.file);\n>>   \t}\n>>   \n>> -\tstrbuf_addf(&buf, \"%s/message\", get_dir(opts));\n>> -\tif (!file_exists(buf.buf)) {\n>> +\tif (!file_exists(rebase_path_message())) {\n>>   \t\tconst char *encoding = get_commit_output_encoding();\n>>   \t\tconst char *commit_buffer = repo_logmsg_reencode(r,\n>>   \t\t\t\t\t\t\t\t commit, NULL,\n>>   \t\t\t\t\t\t\t\t encoding);\n>>   \t\tfind_commit_subject(commit_buffer, &subject);\n>> -\t\tres |= write_message(subject, strlen(subject), buf.buf, 1);\n>> +\t\tres |= write_message(subject, strlen(subject), rebase_path_message(), 1);\n>>   \t\trepo_unuse_commit_buffer(r, commit,\n>>   \t\t\t\t\t commit_buffer);\n>>   \t}\n>> -\tstrbuf_release(&buf);\n>>   \trelease_revisions(&log_tree_opt);\n>>   \n>>   \treturn res;\n"},{"id":"480079","messageId":"xmqq7cqeblut.fsf@gitster.g","threadId":"59416","inReplyTo":"48fefaf0-6a31-0518-3424-73d93343f1d8@gmail.com","subject":"Re: [PATCH v3 1/7] rebase -i: move unlink() calls","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-01T19:31:38Z","receivedAt":"2023-08-01T19:31:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> The files will never exist when the \"if TODO_BREAK\" is executed\n> because we've removed them before entering the loop and as I tried and\n> seemly failed to explain in the commit message they are only created\n> when we're about to break out of the loop.\n\nSpecifically, they are not created when we voluntarily leave the\nloop via TODO_BREAK.  They are created when we leave the loop via\nthe other exit paths (e.g. path_message may be created from MERGE_MSG\nin error_with_patch() but the control flow to reach error_with_patch()\nin the loop would break out of the loop without ever reaching the\nTODO_BREAK codepath).\n\nOr something like that?  I didn't follow thru the other two files.\n\nOK.  I am slow to read and understand a patch from more than 3\nmonths ago X-<; sorry for the confusion.\n\nThanks.\n\n\n\n"},{"id":"480106","messageId":"xmqqedkl6r1t.fsf@gitster.g","threadId":"59416","inReplyTo":"619e458b-218b-a790-dfb4-9200e201b513@gmail.com","subject":"Re: [PATCH v3 3/7] sequencer: use rebase_path_message()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-02T22:02:54Z","receivedAt":"2023-08-02T22:03:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 01/08/2023 18:23, Junio C Hamano wrote:\n>> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> \n>>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>>\n>>> Rather than constructing the path in a struct strbuf use the ready\n>>> made function to get the path name instead. This was the last\n>>> remaining use of the strbuf so remove it as well.\n>> The same comment about \"get_dir() vs hardcoded rebase-merge\" applies\n>> here, I think.  And the same (1) assertion to ensure that we are in\n>> \"rebase -i\" when make_patch() is called should give us a sufficient\n>> safety valve,\n>\n> Agreed\n>\n>> or (2) instead of hardcoding rebase_path_message(),\n>> call it sequencer_path_message() and switch at runtime behaving the\n>> same way as get_dir(opts) based version, would also work.\n>\n> I think that would me misleading because cherry-pick/revert do not\n> create that file - they rely on \"git commit\" reading .git/MERGE_MSG\n\nFair enough.  \n\nAbusing the MERGE_MSG this way probably came from the \"if 'commit'\npicks up whatever is left inside MERGE_MSG when it is run, reusing\nit for this operation (even though it clearly is not a 'merge')\nwould be a way to do with the least effort, even if it does not make\nsense for those who will be reading the code 3 years from now on\"\nkind of hackery.  The file probably outgrew its name and we might\nwant to rename it to a more appropriate name (it is \"we gave control\nback to the user to help us resolve a mess in the working tree, and\nhere is the message to be used when the user is done\"; the \"mess\" no\nlonger is limited to conflicts created during a \"merge\").\n\nBut it would be a major headache if end-user tools are relying on\nit, so it is not likely to happen anytime soon.\n\nSo, moving to hardcoded \"rebase-merge\", as long as we make sure\nmake_patch() will only callable by \"rebase -i\" (and not something\nlike \"cherry-pick -i\" people will wish to add in the future), I am\nfine with such a design.\n\nThanks.\n"},{"id":"480107","messageId":"xmqq8rat6qou.fsf@gitster.g","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/7] rebase -i: impove handling of failed commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-02T22:10:41Z","receivedAt":"2023-08-02T22:10:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>   sequencer: factor out part of pick_commits()\n>   rebase: fix rewritten list for failed pick\n>   rebase --continue: refuse to commit after failed command\n>   rebase -i: fix adding failed command to the todo list\n\nI'd really prefer to see the latter half of the series reviewed\n(both for the design and its implementation) by those who have more\nstake in the sequencer code than myself.\n\nI just noticed that we have a question on the last step left\nunanswered since the very initial iteration.\n\ncf. https://lore.kernel.org/git/f05deb00-1bcd-9e05-739f-6a30d6d8cf3b@gmx.de/\n\nThanks.\n"},{"id":"480119","messageId":"d20ffc1f-c3b5-0f64-d508-976098f418e0@gmail.com","threadId":"59416","inReplyTo":"f05deb00-1bcd-9e05-739f-6a30d6d8cf3b@gmx.de","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-08-03T12:56:52Z","receivedAt":"2023-08-03T12:57:12Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Dscho\n\nSorry for not replying sooner.\n\nOn 27/03/2023 08:04, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Sun, 19 Mar 2023, Phillip Wood via GitGitGadget wrote:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> As the sequencer executes todo commands it appends them to\n>> .git/rebase-merge/done. This file is used by \"git status\" to show the\n>> recently executed commands. Unfortunately when a command is rescheduled\n>> the command preceding it is erroneously appended to the \"done\" file.\n>> This means that when rebase stops after rescheduling \"pick B\" the \"done\"\n>> file contains\n>>\n>> \tpick A\n>> \tpick B\n>> \tpick A\n>>\n>> instead of\n>>\n>> \tpick A\n>> \tpick B\n>>\n>> Fix this by not updating the \"done\" file when adding a rescheduled\n>> command back into the \"todo\" file. A couple of the existing tests are\n>> modified to improve their coverage as none of them trigger this bug or\n>> check the \"done\" file.\n> \n> I am purposefully not yet focusing on the patch, as I have a concern about\n> the reasoning above.\n> \n> When a command fails that needs to be rescheduled, I actually _like_ that\n> there is a record in `done` about said command. It is very much like a\n> `pick` that failed (but was not rescheduled) and was then `--skip`ed: it\n> still shows up on `done`.\n\nWe still do that after this patch. What changes is that when \"pick B\" \nfails we don't add \"pick A\" to the \"done\" file when \"pick B\" is added \nback into \"git-rebase-todo\"\n\n> I do understand the concern that the rescheduled command now shows up in\n> both `done` and `git-rebase-todo` (which is very different from the failed\n> `pick` command that would show up _only_ in `git-rebase-todo`). So maybe\n> we can find some compromise, e.g. adding a commented-out line to `done` à\n> la:\n> \n> \t# rescheduled: pick A\n> \n> What do you think?\n\nIf a commit is rescheduled we still end up with multiple entries in the \n\"done\". In the example above if \"pick B\" fails the first time it is \nexecuted and then succeeds on the second attempt \"done\" will contain\n\n\tpick A\n\tpick B\n\tpick B\n\nIt might be nice to mark it as rescheduled as you suggest but this \nseries focuses on removing the incorrect entry from the \"done\" file, not \nde-duplicating the \"done\" entities when a command fails.\n\nBest Wishes\n\nPhillip\n\n> Ciao,\n> Johannes\n\n"},{"id":"480120","messageId":"ee2d639d-32b7-6c61-f01f-9f06b295fd99@gmail.com","threadId":"59416","inReplyTo":"xmqq8rat6qou.fsf@gitster.g","subject":"Re: [PATCH v3 0/7] rebase -i: impove handling of failed commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-08-03T13:06:04Z","receivedAt":"2023-08-03T13:09:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/08/2023 23:10, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>>    sequencer: factor out part of pick_commits()\n>>    rebase: fix rewritten list for failed pick\n>>    rebase --continue: refuse to commit after failed command\n>>    rebase -i: fix adding failed command to the todo list\n> \n> I'd really prefer to see the latter half of the series reviewed\n> (both for the design and its implementation) by those who have more\n> stake in the sequencer code than myself.\n\nThat would certainly be nice.\n\n> I just noticed that we have a question on the last step left\n> unanswered since the very initial iteration.\n> \n> cf. https://lore.kernel.org/git/f05deb00-1bcd-9e05-739f-6a30d6d8cf3b@gmx.de/\n\nThanks, I'd forgotten about that.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"480252","messageId":"kl6l4jla38wc.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/7] rebase -i: impove handling of failed commands","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-08-07T20:16:51Z","receivedAt":"2023-08-07T20:17:04Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Hi Phillip!\n\n\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series fixes several bugs in the way we handle a commit cannot be\n> picked because it would overwrite an untracked file.\n>\n>  * after a failed pick \"git rebase --continue\" will happily commit any\n>    staged changes even though no commit was picked.\n>\n>  * the commit of the failed pick is recorded as rewritten even though no\n>    commit was picked.\n>\n>  * the \"done\" file used by \"git status\" to show the recently executed\n>    commands contains an incorrect entry.\n>\n> Thanks to Eric, Glen and Junio for their comments on v2. Here are the\n> changes since v2:\n\nThanks for sending this version, and apologies for not getting to it\nsooner (I tried a few times, but it was hard to reconstruct the context\naround something as complicated as sequencer.c..). Unfortunately, I\ndon't think I will be able to chime in on subsequent rounds.\n\n> Patch 1 - Reworded the commit message.\n>\n> Patch 2 - Reworded the commit message, added a test and fixed error message\n> pointed out by Glen.\n>\n> Patch 3 - New cleanup.\n>\n> Patch 4 - Reworded the commit message, now only increments\n> todo_list->current if there is no error.\n>\n> Patch 5 - Swapped with next patch. Reworded the commit message, stopped\n> testing implementation (suggested by Glen). Expanded post-rewrite hook test.\n>\n> Patch 6 - Reworded the commit message, now uses the message file rather than\n> the author script to check if \"rebase --continue\" should commit staged\n> changes. Junio suggested using a separate file for this but I think that\n> would end up being more involved as we'd need to be careful about creating\n> and removing it.\n>\n> Patch 7 - Reworded the commit message.\n\nI found the updated commit messages much easier to understand, and the\nchange to no longer test implementation is also very welcome, so\noverall, I think this is a marked improvement over the previous version.\n\nLike Junio, I'm not familiar enough with sequencer or its 'expected\nbehavior' to feel comfortable LGTM-ing the later patches.\n"},{"id":"480353","messageId":"a3b920a8-f446-2670-f1cd-de07b28f2c7d@gmail.com","threadId":"59416","inReplyTo":"kl6l4jla38wc.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3 0/7] rebase -i: impove handling of failed commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-08-09T10:06:36Z","receivedAt":"2023-08-09T10:06:41Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 07/08/2023 21:16, Glen Choo wrote:\n> Hi Phillip!\n> \n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> This series fixes several bugs in the way we handle a commit cannot be\n>> picked because it would overwrite an untracked file.\n>>\n>>   * after a failed pick \"git rebase --continue\" will happily commit any\n>>     staged changes even though no commit was picked.\n>>\n>>   * the commit of the failed pick is recorded as rewritten even though no\n>>     commit was picked.\n>>\n>>   * the \"done\" file used by \"git status\" to show the recently executed\n>>     commands contains an incorrect entry.\n>>\n>> Thanks to Eric, Glen and Junio for their comments on v2. Here are the\n>> changes since v2:\n> \n> Thanks for sending this version, and apologies for not getting to it\n> sooner (I tried a few times, but it was hard to reconstruct the context\n> around something as complicated as sequencer.c..). Unfortunately, I\n> don't think I will be able to chime in on subsequent rounds.\n\nThanks again for you comments on the last round, they were really \nhelpful in improving the commit messages.\n\n>> Patch 1 - Reworded the commit message.\n>>\n>> Patch 2 - Reworded the commit message, added a test and fixed error message\n>> pointed out by Glen.\n>>\n>> Patch 3 - New cleanup.\n>>\n>> Patch 4 - Reworded the commit message, now only increments\n>> todo_list->current if there is no error.\n>>\n>> Patch 5 - Swapped with next patch. Reworded the commit message, stopped\n>> testing implementation (suggested by Glen). Expanded post-rewrite hook test.\n>>\n>> Patch 6 - Reworded the commit message, now uses the message file rather than\n>> the author script to check if \"rebase --continue\" should commit staged\n>> changes. Junio suggested using a separate file for this but I think that\n>> would end up being more involved as we'd need to be careful about creating\n>> and removing it.\n>>\n>> Patch 7 - Reworded the commit message.\n> \n> I found the updated commit messages much easier to understand, and the\n> change to no longer test implementation is also very welcome, so\n> overall, I think this is a marked improvement over the previous version.\n\nThanks, I'm glad the messages are easier to understand now\n\n> Like Junio, I'm not familiar enough with sequencer or its 'expected\n> behavior' to feel comfortable LGTM-ing the later patches.\n\nYes, I'm hoping Dscho will have time to take a look at them once 2.42.0 \nis out.\n\nBest Wishes\n\nPhillip\n"},{"id":"480359","messageId":"dd07a578-9a8e-062d-11f8-7c969b026cb3@gmail.com","threadId":"59416","inReplyTo":"xmqq8rat6qou.fsf@gitster.g","subject":"Re: [PATCH v3 0/7] rebase -i: impove handling of failed commands","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-08-09T13:08:06Z","receivedAt":"2023-08-09T13:08:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/08/2023 23:10, Junio C Hamano wrote:\n> \"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>>    sequencer: factor out part of pick_commits()\n>>    rebase: fix rewritten list for failed pick\n>>    rebase --continue: refuse to commit after failed command\n>>    rebase -i: fix adding failed command to the todo list\n> \n> I'd really prefer to see the latter half of the series reviewed\n> (both for the design and its implementation) by those who have more\n> stake in the sequencer code than myself.\n\nDscho do you think you will have time to look at the last four patches \nafter 2.42.0 is released?\n\nMany Thanks\n\nPhillip\n\n> I just noticed that we have a question on the last step left\n> unanswered since the very initial iteration.\n> \n> cf. https://lore.kernel.org/git/f05deb00-1bcd-9e05-739f-6a30d6d8cf3b@gmx.de/\n> \n> Thanks.\n\n"},{"id":"480947","messageId":"1718e29c-0dd8-84af-1fb4-82f01458625e@gmx.de","threadId":"59416","inReplyTo":"a1fad70f4b920252d84b5b5578b1b9b0a7bba7ca.1690903412.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 4/7] sequencer: factor out part of pick_commits()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-08-23T08:55:08Z","receivedAt":"2023-08-23T09:00:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Tue, 1 Aug 2023, Phillip Wood via GitGitGadget wrote:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> This simplifies the next commit. If a pick fails we now return the error\n> at the end of the loop body rather than returning early, a successful\n> \"edit\" command continues to return early. There are three things to\n> check to ensure that removing the early return for an error does not\n> change the behavior of the code:\n>\n> (1) We could enter the block guarded by \"if (reschedule)\". This block\n>     is not entered because \"reschedlue\" is always zero when picking a\n>     commit.\n>\n> (2) We could enter the block guarded by\n>     \"else if (is_rebase_i(opts) &&  check_todo && !res)\". This block is\n>     not entered when returning an error because \"res\" is non-zero in\n>     that case.\n>\n> (3) todo_list->current could be incremented before returning. That is\n>     avoided by moving the increment which is of course a potential\n>     change in behavior itself. The move is safe because none of the\n>     callers look at todo_list after this function returns. Moving the\n>     increment makes it clear we only want to advance the current item\n>     if the command was successful.\n\nWell argued, and it matches exactly what the patch says.\n\nFWIW I had to look at the patch locally, using\n\n$ git show --color-moved --color-moved-ws=allow-indentation-change \\\n\ta1fad70f4b920252d84b5b5578b1b9b0a7bba7ca\n\nto convince myself that the code was extracted into `pick_one_commit()` as\nverbatim as possible. There are a couple of (benign) differences: a `&`\nwas removed since `check_todo` is now a pointer, and some wrapping\ndifferences that leave the logic unchanged.\n\nSo here is my ACK.\n\nThank you,\nJohannes\n"},{"id":"480948","messageId":"03d6f4df-d175-350c-7e75-ba78be80ba4e@gmx.de","threadId":"59416","inReplyTo":"d20ffc1f-c3b5-0f64-d508-976098f418e0@gmail.com","subject":"Re: [PATCH] rebase -i: do not update \"done\" when rescheduling command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-08-23T08:54:53Z","receivedAt":"2023-08-23T09:00:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Thu, 3 Aug 2023, Phillip Wood wrote:\n\n> On 27/03/2023 08:04, Johannes Schindelin wrote:\n> >\n> > On Sun, 19 Mar 2023, Phillip Wood via GitGitGadget wrote:\n> >\n> > > From: Phillip Wood <phillip.wood@dunelm.org.uk>\n> > >\n> > > As the sequencer executes todo commands it appends them to\n> > > .git/rebase-merge/done. This file is used by \"git status\" to show the\n> > > recently executed commands. Unfortunately when a command is rescheduled\n> > > the command preceding it is erroneously appended to the \"done\" file.\n> > > This means that when rebase stops after rescheduling \"pick B\" the \"done\"\n> > > file contains\n> > >\n> > >  pick A\n> > >  pick B\n> > >  pick A\n> > >\n> > > instead of\n> > >\n> > >  pick A\n> > >  pick B\n> > >\n> > > Fix this by not updating the \"done\" file when adding a rescheduled\n> > > command back into the \"todo\" file. A couple of the existing tests are\n> > > modified to improve their coverage as none of them trigger this bug or\n> > > check the \"done\" file.\n> >\n> > I am purposefully not yet focusing on the patch, as I have a concern about\n> > the reasoning above.\n> >\n> > When a command fails that needs to be rescheduled, I actually _like_ that\n> > there is a record in `done` about said command. It is very much like a\n> > `pick` that failed (but was not rescheduled) and was then `--skip`ed: it\n> > still shows up on `done`.\n>\n> We still do that after this patch. What changes is that when \"pick B\" fails we\n> don't add \"pick A\" to the \"done\" file when \"pick B\" is added back into\n> \"git-rebase-todo\"\n>\n> > I do understand the concern that the rescheduled command now shows up in\n> > both `done` and `git-rebase-todo` (which is very different from the failed\n> > `pick` command that would show up _only_ in `git-rebase-todo`). So maybe\n> > we can find some compromise, e.g. adding a commented-out line to `done` à\n> > la:\n> >\n> >  # rescheduled: pick A\n> >\n> > What do you think?\n>\n> If a commit is rescheduled we still end up with multiple entries in the\n> \"done\". In the example above if \"pick B\" fails the first time it is executed\n> and then succeeds on the second attempt \"done\" will contain\n>\n> \tpick A\n> \tpick B\n> \tpick B\n>\n> It might be nice to mark it as rescheduled as you suggest but this series\n> focuses on removing the incorrect entry from the \"done\" file, not\n> de-duplicating the \"done\" entities when a command fails.\n\nAfter having had plenty of time to let this issue simmer in the back of my\nhead, and after reviewing the latest iteration of the patch series, I am\nno longer concerned, as it now sounds more logical to me, too, that\nrescheduled commands don't show up in `done`.\n\nThank you,\nJohannes\n"},{"id":"480949","messageId":"a5bfea5f-0d0d-f7ed-3f72-37e3db6f5b2c@gmx.de","threadId":"59416","inReplyTo":"2ed7cbe5fff2d104a1246783909c6c4b75c559f2.1690903412.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 6/7] rebase --continue: refuse to commit after failed command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-08-23T09:01:37Z","receivedAt":"2023-08-23T09:15:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Tue, 1 Aug 2023, Phillip Wood via GitGitGadget wrote:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> If a commit cannot be picked because it would overwrite an untracked\n> file then \"git rebase --continue\" should refuse to commit any staged\n> changes as the commit was not picked. This is implemented by refusing to\n> commit if the message file is missing. The message file is chosen for\n> this check because it is only written when \"git rebase\" stops for the\n> user to resolve merge conflicts.\n>\n> Existing commands that refuse to commit staged changes when continuing\n> such as a failed \"exec\" rely on checking for the absence of the author\n> script in run_git_commit(). This prevents the staged changes from being\n> committed but prints\n>\n>     error: could not open '.git/rebase-merge/author-script' for\n>     reading\n>\n> before the message about not being able to commit. This is confusing to\n> users and so checking for the message file instead improves the user\n> experience. The existing test for refusing to commit after a failed exec\n> is updated to check that we do not print the error message about a\n> missing author script anymore.\n\nI am delighted to see an improvement of the user experience!\n\nHowever, I could imagine that users would still be confused when seeing\nthe advice about staged changes, even if nothing was staged at all.\n\nCould you introduce a new advice message specifically for the case where\nuntracked files are in the way and prevent changes from being applied?\n\nThank you,\nJohannes\n\nP.S.: To save both you and me time, here is my ACK for patch 7/7\n(actually, the entire patch series, but _maybe_ you want to change\n\"impove\" -> \"improve\" in the cover letter's subject) ;-)\n\n>\n> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> ---\n>  sequencer.c                   |  5 +++++\n>  t/t3404-rebase-interactive.sh | 18 +++++++++++++++++-\n>  t/t3430-rebase-merges.sh      |  4 ++++\n>  3 files changed, 26 insertions(+), 1 deletion(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index e25abfd2fb4..a90b015e79c 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4977,6 +4977,11 @@ static int commit_staged_changes(struct repository *r,\n>\n>  \tis_clean = !has_uncommitted_changes(r, 0);\n>\n> +\tif (!is_clean && !file_exists(rebase_path_message())) {\n> +\t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n> +\n> +\t\treturn error(_(staged_changes_advice), gpg_opt, gpg_opt);\n> +\t}\n>  \tif (file_exists(rebase_path_amend())) {\n>  \t\tstruct strbuf rev = STRBUF_INIT;\n>  \t\tstruct object_id head, to_amend;\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 6d3788c588b..a8ad398956a 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -604,7 +604,8 @@ test_expect_success 'clean error after failed \"exec\"' '\n>  \techo \"edited again\" > file7 &&\n>  \tgit add file7 &&\n>  \ttest_must_fail git rebase --continue 2>error &&\n> -\ttest_i18ngrep \"you have staged changes in your working tree\" error\n> +\ttest_i18ngrep \"you have staged changes in your working tree\" error &&\n> +\ttest_i18ngrep ! \"could not open.*for reading\" error\n>  '\n>\n>  test_expect_success 'rebase a detached HEAD' '\n> @@ -1290,6 +1291,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>  \ttest_cmp_rev REBASE_HEAD I &&\n>  \trm file6 &&\n>  \ttest_path_is_missing .git/rebase-merge/patch &&\n> +\techo changed >file1 &&\n> +\tgit add file1 &&\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"error: you have staged changes in your working tree\" err &&\n> +\tgit reset --hard HEAD &&\n>  \tgit rebase --continue &&\n>  \ttest_cmp_rev HEAD I\n>  '\n> @@ -1310,6 +1316,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n>  \ttest_cmp_rev REBASE_HEAD I &&\n>  \trm file6 &&\n>  \ttest_path_is_missing .git/rebase-merge/patch &&\n> +\techo changed >file1 &&\n> +\tgit add file1 &&\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"error: you have staged changes in your working tree\" err &&\n> +\tgit reset --hard HEAD &&\n>  \tgit rebase --continue &&\n>  \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n>  \tgit reset --hard original-branch2\n> @@ -1330,6 +1341,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n>  \ttest_cmp_rev REBASE_HEAD I &&\n>  \trm file6 &&\n>  \ttest_path_is_missing .git/rebase-merge/patch &&\n> +\techo changed >file1 &&\n> +\tgit add file1 &&\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"error: you have staged changes in your working tree\" err &&\n> +\tgit reset --hard HEAD &&\n>  \tgit rebase --continue &&\n>  \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n>  '\n> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n> index 4938ebb1c17..804ff819782 100755\n> --- a/t/t3430-rebase-merges.sh\n> +++ b/t/t3430-rebase-merges.sh\n> @@ -169,6 +169,10 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n>  \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n>  \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n>  \ttest_path_is_missing .git/rebase-merge/patch &&\n> +\techo changed >file1 &&\n> +\tgit add file1 &&\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"error: you have staged changes in your working tree\" err &&\n>\n>  \t: fail because of merge conflict &&\n>  \tgit reset --hard conflicting-G &&\n> --\n> gitgitgadget\n>\n>\n"},{"id":"480951","messageId":"304553b3-3668-0b98-c6dc-3c047fd9e34c@gmx.de","threadId":"59416","inReplyTo":"df4019458665eccf2b16cdf1d6c1061186a62711.1690903412.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 5/7] rebase: fix rewritten list for failed pick","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-08-23T08:55:53Z","receivedAt":"2023-08-23T09:46:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Tue, 1 Aug 2023, Phillip Wood via GitGitGadget wrote:\n\n> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n> git rebase keeps a list that maps the OID of each commit before it was\n> rebased to the OID of the equivalent commit after the rebase.  This list\n> is used to drive the \"post-rewrite\" hook that is called at the end of a\n> successful rebase. When a rebase stops for the user to resolve merge\n> conflicts the OID of the commit being picked is written to\n> \".git/rebase-merge/stopped-sha\". Then when the rebase is continued that\n> OID is added to the list of rewritten commits. Unfortunately if a commit\n> cannot be picked because it would overwrite an untracked file we still\n> write the \"stopped-sha1\" file. This means that when the rebase is\n> continued the commit is added into the list of rewritten commits even\n> though it has not been picked yet.\n>\n> Fix this by not calling error_with_patch() for failed commands. The pick\n> has failed so there is nothing to commit and therefore we do not want to\n> set up the state files for committing staged changes when the rebase\n> continues. This change means we no-longer write a patch for the failed\n> command or display the error message printed by error_with_patch(). As\n> the command has failed the patch isn't really useful and in any case the\n> user can inspect the commit associated with the failed command by\n> inspecting REBASE_HEAD. Unless the user has disabled it we already print\n> an advice message that is more helpful than the message from\n> error_with_patch() which the user will still see. Even if the advice is\n> disabled the user will see the messages from the merge machinery\n> detailing the problem.\n>\n> To simplify writing REBASE_HEAD in this case pick_one_commit() is\n> modified to avoid duplicating the code that adds the failed command\n> back into the todo list.\n\nThis motivates the change well, and answered all but one of the questions\nI had about it, being:\n\n> diff --git a/sequencer.c b/sequencer.c\n> index 62277e7bcc1..e25abfd2fb4 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> [...]\n> @@ -4658,12 +4659,8 @@ static int pick_one_commit(struct repository *r,\n>  \t\t\t     check_todo);\n>  \tif (is_rebase_i(opts) && res < 0) {\n>  \t\t/* Reschedule */\n> -\t\tadvise(_(rescheduled_advice),\n> -\t\t       get_item_line_length(todo_list, todo_list->current),\n> -\t\t       get_item_line(todo_list, todo_list->current));\n> -\t\ttodo_list->current--;\n\nWhy is it okay to remove this decrement?\n\nHere is why: The code that calls `save_todo()` in the `if (reschedule)`\nblock of the loop of `pick_commits()` _duplicates_ the logic that is\nremoved here, including the advice and the decrementing of `current`.\n\nI had to instrument the code before and after this patch to figure this\nout, as I had missed the fact that the now-remaining code also decremented\nthe `current` attribute.\n\nSo: All is good with this patch. If you'd like to amend the commit message\naccordingly, I would not be opposed, but I could now live equally as\neasily without it.\n\n> -\t\tif (save_todo(todo_list, opts))\n> -\t\t\treturn -1;\n> +\t\t*reschedule = 1;\n> +\t\treturn -1;\n>  \t}\n>  \tif (item->command == TODO_EDIT) {\n>  \t\tstruct commit *commit = item->commit;\n\nI'd like to point out how delighted I am about this careful test case:\n\n> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n> index 96ae0edf1e1..4938ebb1c17 100755\n> --- a/t/t3430-rebase-merges.sh\n> +++ b/t/t3430-rebase-merges.sh\n> @@ -165,12 +165,12 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n>  \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n>  \ttest_tick &&\n>  \ttest_must_fail git rebase -ir HEAD &&\n> +\ttest_cmp_rev REBASE_HEAD H^0 &&\n>  \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n>  \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n> -\ttest_path_is_file .git/rebase-merge/patch &&\n> +\ttest_path_is_missing .git/rebase-merge/patch &&\n>\n>  \t: fail because of merge conflict &&\n> -\trm G.t .git/rebase-merge/patch &&\n>  \tgit reset --hard conflicting-G &&\n>  \ttest_must_fail git rebase --continue &&\n>  \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n> diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\n> index 5f3ff051ca2..ad7f8c6f002 100755\n> --- a/t/t5407-post-rewrite-hook.sh\n> +++ b/t/t5407-post-rewrite-hook.sh\n> @@ -17,6 +17,12 @@ test_expect_success 'setup' '\n>  \tgit checkout A^0 &&\n>  \ttest_commit E bar E &&\n>  \ttest_commit F foo F &&\n> +\tgit checkout B &&\n> +\tgit merge E &&\n> +\tgit tag merge-E &&\n> +\ttest_commit G G &&\n> +\ttest_commit H H &&\n> +\ttest_commit I I &&\n>  \tgit checkout main &&\n>\n>  \ttest_hook --setup post-rewrite <<-EOF\n> @@ -173,6 +179,48 @@ test_fail_interactive_rebase () {\n>  \t)\n>  }\n>\n> +test_expect_success 'git rebase with failed pick' '\n> +\tclear_hook_input &&\n> +\tcat >todo <<-\\EOF &&\n> +\texec >bar\n> +\tmerge -C merge-E E\n> +\texec >G\n> +\tpick G\n> +\texec >H 2>I\n> +\tpick H\n> +\tfixup I\n> +\tEOF\n> +\n> +\t(\n> +\t\tset_replace_editor todo &&\n> +\t\ttest_must_fail git rebase -i D D 2>err\n> +\t) &&\n> +\tgrep \"would be overwritten\" err &&\n> +\trm bar &&\n> +\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"would be overwritten\" err &&\n> +\trm G &&\n> +\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"would be overwritten\" err &&\n> +\trm H &&\n> +\n> +\ttest_must_fail git rebase --continue 2>err &&\n> +\tgrep \"would be overwritten\" err &&\n> +\trm I &&\n> +\n> +\tgit rebase --continue &&\n> +\techo rebase >expected.args &&\n> +\tcat >expected.data <<-EOF &&\n> +\t$(git rev-parse merge-E) $(git rev-parse HEAD~2)\n> +\t$(git rev-parse G) $(git rev-parse HEAD~1)\n> +\t$(git rev-parse H) $(git rev-parse HEAD)\n> +\t$(git rev-parse I) $(git rev-parse HEAD)\n> +\tEOF\n> +\tverify_hook_input\n> +'\n> +\n>  test_expect_success 'git rebase -i (unchanged)' '\n>  \tgit reset --hard D &&\n>  \tclear_hook_input &&\n\nHere is my ACK.\n\nThank you,\nJohannes\n"},{"id":"481369","messageId":"4606961d-6ead-4cad-8831-5bbded31b23b@gmail.com","threadId":"59416","inReplyTo":"304553b3-3668-0b98-c6dc-3c047fd9e34c@gmx.de","subject":"Re: [PATCH v3 5/7] rebase: fix rewritten list for failed pick","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-09-04T14:31:51Z","receivedAt":"2023-09-04T14:32:06Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Dscho\n\nOn 23/08/2023 09:55, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Tue, 1 Aug 2023, Phillip Wood via GitGitGadget wrote:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> git rebase keeps a list that maps the OID of each commit before it was\n>> rebased to the OID of the equivalent commit after the rebase.  This list\n>> is used to drive the \"post-rewrite\" hook that is called at the end of a\n>> successful rebase. When a rebase stops for the user to resolve merge\n>> conflicts the OID of the commit being picked is written to\n>> \".git/rebase-merge/stopped-sha\". Then when the rebase is continued that\n>> OID is added to the list of rewritten commits. Unfortunately if a commit\n>> cannot be picked because it would overwrite an untracked file we still\n>> write the \"stopped-sha1\" file. This means that when the rebase is\n>> continued the commit is added into the list of rewritten commits even\n>> though it has not been picked yet.\n>>\n>> Fix this by not calling error_with_patch() for failed commands. The pick\n>> has failed so there is nothing to commit and therefore we do not want to\n>> set up the state files for committing staged changes when the rebase\n>> continues. This change means we no-longer write a patch for the failed\n>> command or display the error message printed by error_with_patch(). As\n>> the command has failed the patch isn't really useful and in any case the\n>> user can inspect the commit associated with the failed command by\n>> inspecting REBASE_HEAD. Unless the user has disabled it we already print\n>> an advice message that is more helpful than the message from\n>> error_with_patch() which the user will still see. Even if the advice is\n>> disabled the user will see the messages from the merge machinery\n>> detailing the problem.\n>>\n>> To simplify writing REBASE_HEAD in this case pick_one_commit() is\n>> modified to avoid duplicating the code that adds the failed command\n>> back into the todo list.\n> \n> This motivates the change well, and answered all but one of the questions\n> I had about it, being:\n> \n>> diff --git a/sequencer.c b/sequencer.c\n>> index 62277e7bcc1..e25abfd2fb4 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> [...]\n>> @@ -4658,12 +4659,8 @@ static int pick_one_commit(struct repository *r,\n>>   \t\t\t     check_todo);\n>>   \tif (is_rebase_i(opts) && res < 0) {\n>>   \t\t/* Reschedule */\n>> -\t\tadvise(_(rescheduled_advice),\n>> -\t\t       get_item_line_length(todo_list, todo_list->current),\n>> -\t\t       get_item_line(todo_list, todo_list->current));\n>> -\t\ttodo_list->current--;\n> \n> Why is it okay to remove this decrement?\n> \n> Here is why: The code that calls `save_todo()` in the `if (reschedule)`\n> block of the loop of `pick_commits()` _duplicates_ the logic that is\n> removed here, including the advice and the decrementing of `current`.\n> \n> I had to instrument the code before and after this patch to figure this\n> out, as I had missed the fact that the now-remaining code also decremented\n> the `current` attribute.\n> \n> So: All is good with this patch. If you'd like to amend the commit message\n> accordingly, I would not be opposed, but I could now live equally as\n> easily without it.\n\nI'll try and add something to the commit message when I re-roll\n\nThanks\n\nPhillip\n\n>> -\t\tif (save_todo(todo_list, opts))\n>> -\t\t\treturn -1;\n>> +\t\t*reschedule = 1;\n>> +\t\treturn -1;\n>>   \t}\n>>   \tif (item->command == TODO_EDIT) {\n>>   \t\tstruct commit *commit = item->commit;\n> \n> I'd like to point out how delighted I am about this careful test case:\n> \n>> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n>> index 96ae0edf1e1..4938ebb1c17 100755\n>> --- a/t/t3430-rebase-merges.sh\n>> +++ b/t/t3430-rebase-merges.sh\n>> @@ -165,12 +165,12 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n>>   \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n>>   \ttest_tick &&\n>>   \ttest_must_fail git rebase -ir HEAD &&\n>> +\ttest_cmp_rev REBASE_HEAD H^0 &&\n>>   \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n>>   \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n>> -\ttest_path_is_file .git/rebase-merge/patch &&\n>> +\ttest_path_is_missing .git/rebase-merge/patch &&\n>>\n>>   \t: fail because of merge conflict &&\n>> -\trm G.t .git/rebase-merge/patch &&\n>>   \tgit reset --hard conflicting-G &&\n>>   \ttest_must_fail git rebase --continue &&\n>>   \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n>> diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\n>> index 5f3ff051ca2..ad7f8c6f002 100755\n>> --- a/t/t5407-post-rewrite-hook.sh\n>> +++ b/t/t5407-post-rewrite-hook.sh\n>> @@ -17,6 +17,12 @@ test_expect_success 'setup' '\n>>   \tgit checkout A^0 &&\n>>   \ttest_commit E bar E &&\n>>   \ttest_commit F foo F &&\n>> +\tgit checkout B &&\n>> +\tgit merge E &&\n>> +\tgit tag merge-E &&\n>> +\ttest_commit G G &&\n>> +\ttest_commit H H &&\n>> +\ttest_commit I I &&\n>>   \tgit checkout main &&\n>>\n>>   \ttest_hook --setup post-rewrite <<-EOF\n>> @@ -173,6 +179,48 @@ test_fail_interactive_rebase () {\n>>   \t)\n>>   }\n>>\n>> +test_expect_success 'git rebase with failed pick' '\n>> +\tclear_hook_input &&\n>> +\tcat >todo <<-\\EOF &&\n>> +\texec >bar\n>> +\tmerge -C merge-E E\n>> +\texec >G\n>> +\tpick G\n>> +\texec >H 2>I\n>> +\tpick H\n>> +\tfixup I\n>> +\tEOF\n>> +\n>> +\t(\n>> +\t\tset_replace_editor todo &&\n>> +\t\ttest_must_fail git rebase -i D D 2>err\n>> +\t) &&\n>> +\tgrep \"would be overwritten\" err &&\n>> +\trm bar &&\n>> +\n>> +\ttest_must_fail git rebase --continue 2>err &&\n>> +\tgrep \"would be overwritten\" err &&\n>> +\trm G &&\n>> +\n>> +\ttest_must_fail git rebase --continue 2>err &&\n>> +\tgrep \"would be overwritten\" err &&\n>> +\trm H &&\n>> +\n>> +\ttest_must_fail git rebase --continue 2>err &&\n>> +\tgrep \"would be overwritten\" err &&\n>> +\trm I &&\n>> +\n>> +\tgit rebase --continue &&\n>> +\techo rebase >expected.args &&\n>> +\tcat >expected.data <<-EOF &&\n>> +\t$(git rev-parse merge-E) $(git rev-parse HEAD~2)\n>> +\t$(git rev-parse G) $(git rev-parse HEAD~1)\n>> +\t$(git rev-parse H) $(git rev-parse HEAD)\n>> +\t$(git rev-parse I) $(git rev-parse HEAD)\n>> +\tEOF\n>> +\tverify_hook_input\n>> +'\n>> +\n>>   test_expect_success 'git rebase -i (unchanged)' '\n>>   \tgit reset --hard D &&\n>>   \tclear_hook_input &&\n> \n> Here is my ACK.\n> \n> Thank you,\n> Johannes\n"},{"id":"481370","messageId":"02c28b26-4658-43c8-b1d1-7f1e09bda609@gmail.com","threadId":"59416","inReplyTo":"a5bfea5f-0d0d-f7ed-3f72-37e3db6f5b2c@gmx.de","subject":"Re: [PATCH v3 6/7] rebase --continue: refuse to commit after failed command","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-09-04T14:37:14Z","receivedAt":"2023-09-04T14:37:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Dscho\n\nOn 23/08/2023 10:01, Johannes Schindelin wrote:\n> Hi Phillip,\n> \n> On Tue, 1 Aug 2023, Phillip Wood via GitGitGadget wrote:\n> \n>> From: Phillip Wood <phillip.wood@dunelm.org.uk>\n>>\n>> If a commit cannot be picked because it would overwrite an untracked\n>> file then \"git rebase --continue\" should refuse to commit any staged\n>> changes as the commit was not picked. This is implemented by refusing to\n>> commit if the message file is missing. The message file is chosen for\n>> this check because it is only written when \"git rebase\" stops for the\n>> user to resolve merge conflicts.\n>>\n>> Existing commands that refuse to commit staged changes when continuing\n>> such as a failed \"exec\" rely on checking for the absence of the author\n>> script in run_git_commit(). This prevents the staged changes from being\n>> committed but prints\n>>\n>>      error: could not open '.git/rebase-merge/author-script' for\n>>      reading\n>>\n>> before the message about not being able to commit. This is confusing to\n>> users and so checking for the message file instead improves the user\n>> experience. The existing test for refusing to commit after a failed exec\n>> is updated to check that we do not print the error message about a\n>> missing author script anymore.\n> \n> I am delighted to see an improvement of the user experience!\n> \n> However, I could imagine that users would still be confused when seeing\n> the advice about staged changes, even if nothing was staged at all.\n\nIf nothing is staged then this message wont trigger because is_clean \nwill be false.\n\n> Could you introduce a new advice message specifically for the case where\n> untracked files are in the way and prevent changes from being applied?\n\nWe have an advice message now that is printed when the rebase stops in \nthat case. The message here is printed when the user runs \"rebase \n--continue\" with staged changes and we're not expecting to commit \nanything because the commit couldn't be picked or we're containing from \na break command or bad exec/label/reset etc.\n\n\n> P.S.: To save both you and me time, here is my ACK for patch 7/7\n> (actually, the entire patch series, but _maybe_ you want to change\n> \"impove\" -> \"improve\" in the cover letter's subject) ;-)\n\nThanks for taking the time to read through the patches and for you \ncomments. I'll fix the typo when I re-roll\n\nBest Wishes\n\nPhillip\n\n>>\n>> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>> ---\n>>   sequencer.c                   |  5 +++++\n>>   t/t3404-rebase-interactive.sh | 18 +++++++++++++++++-\n>>   t/t3430-rebase-merges.sh      |  4 ++++\n>>   3 files changed, 26 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/sequencer.c b/sequencer.c\n>> index e25abfd2fb4..a90b015e79c 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -4977,6 +4977,11 @@ static int commit_staged_changes(struct repository *r,\n>>\n>>   \tis_clean = !has_uncommitted_changes(r, 0);\n>>\n>> +\tif (!is_clean && !file_exists(rebase_path_message())) {\n>> +\t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n>> +\n>> +\t\treturn error(_(staged_changes_advice), gpg_opt, gpg_opt);\n>> +\t}\n>>   \tif (file_exists(rebase_path_amend())) {\n>>   \t\tstruct strbuf rev = STRBUF_INIT;\n>>   \t\tstruct object_id head, to_amend;\n>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n>> index 6d3788c588b..a8ad398956a 100755\n>> --- a/t/t3404-rebase-interactive.sh\n>> +++ b/t/t3404-rebase-interactive.sh\n>> @@ -604,7 +604,8 @@ test_expect_success 'clean error after failed \"exec\"' '\n>>   \techo \"edited again\" > file7 &&\n>>   \tgit add file7 &&\n>>   \ttest_must_fail git rebase --continue 2>error &&\n>> -\ttest_i18ngrep \"you have staged changes in your working tree\" error\n>> +\ttest_i18ngrep \"you have staged changes in your working tree\" error &&\n>> +\ttest_i18ngrep ! \"could not open.*for reading\" error\n>>   '\n>>\n>>   test_expect_success 'rebase a detached HEAD' '\n>> @@ -1290,6 +1291,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n>>   \ttest_cmp_rev REBASE_HEAD I &&\n>>   \trm file6 &&\n>>   \ttest_path_is_missing .git/rebase-merge/patch &&\n>> +\techo changed >file1 &&\n>> +\tgit add file1 &&\n>> +\ttest_must_fail git rebase --continue 2>err &&\n>> +\tgrep \"error: you have staged changes in your working tree\" err &&\n>> +\tgit reset --hard HEAD &&\n>>   \tgit rebase --continue &&\n>>   \ttest_cmp_rev HEAD I\n>>   '\n>> @@ -1310,6 +1316,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n>>   \ttest_cmp_rev REBASE_HEAD I &&\n>>   \trm file6 &&\n>>   \ttest_path_is_missing .git/rebase-merge/patch &&\n>> +\techo changed >file1 &&\n>> +\tgit add file1 &&\n>> +\ttest_must_fail git rebase --continue 2>err &&\n>> +\tgrep \"error: you have staged changes in your working tree\" err &&\n>> +\tgit reset --hard HEAD &&\n>>   \tgit rebase --continue &&\n>>   \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n>>   \tgit reset --hard original-branch2\n>> @@ -1330,6 +1341,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n>>   \ttest_cmp_rev REBASE_HEAD I &&\n>>   \trm file6 &&\n>>   \ttest_path_is_missing .git/rebase-merge/patch &&\n>> +\techo changed >file1 &&\n>> +\tgit add file1 &&\n>> +\ttest_must_fail git rebase --continue 2>err &&\n>> +\tgrep \"error: you have staged changes in your working tree\" err &&\n>> +\tgit reset --hard HEAD &&\n>>   \tgit rebase --continue &&\n>>   \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n>>   '\n>> diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\n>> index 4938ebb1c17..804ff819782 100755\n>> --- a/t/t3430-rebase-merges.sh\n>> +++ b/t/t3430-rebase-merges.sh\n>> @@ -169,6 +169,10 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n>>   \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n>>   \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n>>   \ttest_path_is_missing .git/rebase-merge/patch &&\n>> +\techo changed >file1 &&\n>> +\tgit add file1 &&\n>> +\ttest_must_fail git rebase --continue 2>err &&\n>> +\tgrep \"error: you have staged changes in your working tree\" err &&\n>>\n>>   \t: fail because of merge conflict &&\n>>   \tgit reset --hard conflicting-G &&\n>> --\n>> gitgitgadget\n>>\n>>\n"},{"id":"481391","messageId":"xmqqy1hksm61.fsf@gitster.g","threadId":"59416","inReplyTo":"87fbc8c9-f42b-b374-fee1-57c58f5e8fc0@gmx.de","subject":"Re: [PATCH v3 6/7] rebase --continue: refuse to commit after failed command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-09-05T14:57:10Z","receivedAt":"2023-09-05T16:00:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> With your patch, there are now four users of the `is_clean` value, and\n> all but one of them ask for the negated value.\n\nExcellent observation.  That strongly argues for the flipping of\npolarity, i.e. many people want to know \"is it unclean/dirty?\".  It\nis funny that the name of the helper function where the value comes\nfrom, i.e. has_uncommitted_changes(), has the desired polarity.\n\n> It's not really the responsibility of this patch series, but I could\n> imagine that it would be nicer to future readers if a patch was added that\n> would invert the meaning of that variable and rename it to\n> `needs_committing`. At least to me, that would make the intention of the\n> code eminently clearer.\n\nWhile I agree, after reading the code, that it would make it easier\nto follow to flip the polarity of the variable, I would advise\nagainst renaming from state based naming (is it dirty?) to action\nbased naming (must we commit?), *if* the variable is checked to\nsometimes see if we has something that we _could_ commit, while some\nother times to see if we _must_ commit before we can let the user\nproceed.\n\n\"Does the index hold some changes to be committed?\" is better\nquestion than \"Must we commit?\" or \"Could we commit?\" to derive the\nname of this variable from if that is the case, I would think.\n\n\n\n\n\n"},{"id":"481398","messageId":"87fbc8c9-f42b-b374-fee1-57c58f5e8fc0@gmx.de","threadId":"59416","inReplyTo":"02c28b26-4658-43c8-b1d1-7f1e09bda609@gmail.com","subject":"Re: [PATCH v3 6/7] rebase --continue: refuse to commit after failed command","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-05T11:17:53Z","receivedAt":"2023-09-05T16:01:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Mon, 4 Sep 2023, Phillip Wood wrote:\n\n> On 23/08/2023 10:01, Johannes Schindelin wrote:\n>\n> > On Tue, 1 Aug 2023, Phillip Wood via GitGitGadget wrote:\n> >\n> > > From: Phillip Wood <phillip.wood@dunelm.org.uk>\n> > >\n> > > If a commit cannot be picked because it would overwrite an untracked\n> > > file then \"git rebase --continue\" should refuse to commit any staged\n> > > changes as the commit was not picked. This is implemented by refusing to\n> > > commit if the message file is missing. The message file is chosen for\n> > > this check because it is only written when \"git rebase\" stops for the\n> > > user to resolve merge conflicts.\n> > >\n> > > Existing commands that refuse to commit staged changes when continuing\n> > > such as a failed \"exec\" rely on checking for the absence of the author\n> > > script in run_git_commit(). This prevents the staged changes from being\n> > > committed but prints\n> > >\n> > >      error: could not open '.git/rebase-merge/author-script' for\n> > >      reading\n> > >\n> > > before the message about not being able to commit. This is confusing to\n> > > users and so checking for the message file instead improves the user\n> > > experience. The existing test for refusing to commit after a failed exec\n> > > is updated to check that we do not print the error message about a\n> > > missing author script anymore.\n> >\n> > I am delighted to see an improvement of the user experience!\n> >\n> > However, I could imagine that users would still be confused when seeing\n> > the advice about staged changes, even if nothing was staged at all.\n>\n> If nothing is staged then this message wont trigger because is_clean will be\n> false.\n\nAh. I managed to get confused by the first sentence of the commit message\nalready. You clearly talk about \"any staged changes\". As in \"*iff* there\nare any staged changes\". Which I missed.\n\nA further contributing factor for my misunderstading was the slightly\nconvoluted logic where `is_clean` is set to true if there are _not_ any\nuncommitted changes, and then we ask if `is_clean` is _not_ true. Reminds\nme of Smullyan's Knights & Knaves [*1*].\n\nWith your patch, there are now four users of the `is_clean` value, and\nall but one of them ask for the negated value.\n\nIt's not really the responsibility of this patch series, but I could\nimagine that it would be nicer to future readers if a patch was added that\nwould invert the meaning of that variable and rename it to\n`needs_committing`. At least to me, that would make the intention of the\ncode eminently clearer.\n\nCiao,\nJohannes\n\nFootnote *1*: https://en.wikipedia.org/wiki/Knights_and_Knaves\n"},{"id":"481401","messageId":"84227916-92d9-4085-9738-29d7e489f144@gmail.com","threadId":"59416","inReplyTo":"87fbc8c9-f42b-b374-fee1-57c58f5e8fc0@gmx.de","subject":"Re: [PATCH v3 6/7] rebase --continue: refuse to commit after failed command","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-09-05T15:25:25Z","receivedAt":"2023-09-05T16:18:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Johannes\n\nOn 05/09/2023 12:17, Johannes Schindelin wrote:\n> A further contributing factor for my misunderstading was the slightly\n> convoluted logic where `is_clean` is set to true if there are _not_ any\n> uncommitted changes, and then we ask if `is_clean` is _not_ true. Reminds\n> me of Smullyan's Knights & Knaves [*1*].\n\nI agree 'is_clean' is confusing (I have the same problem with \nmerge_recursive() and friends where a return value of zero means that \nthere were conflicts)\n\n> With your patch, there are now four users of the `is_clean` value, and\n> all but one of them ask for the negated value.\n> \n> It's not really the responsibility of this patch series, but I could\n> imagine that it would be nicer to future readers if a patch was added that\n> would invert the meaning of that variable and rename it to\n> `needs_committing`. At least to me, that would make the intention of the\n> code eminently clearer.\n\nInverting and renaming 'is_clean' is a good idea, I might leave it to a \nfollow up series though.\n\nBest Wishes\n\nPhillip\n\n> Ciao,\n> Johannes\n> \n> Footnote *1*: https://en.wikipedia.org/wiki/Knights_and_Knaves\n"},{"id":"481432","messageId":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v3.git.1690903412.gitgitgadget@gmail.com","subject":"[PATCH v4 0/7] rebase -i: impove handling of failed commands","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:44Z","receivedAt":"2023-09-06T15:23:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"This series fixes several bugs in the way we handle a commit cannot be\npicked because it would overwrite an untracked file.\n\n * after a failed pick \"git rebase --continue\" will happily commit any\n   staged changes even though no commit was picked.\n\n * the commit of the failed pick is recorded as rewritten even though no\n   commit was picked.\n\n * the \"done\" file used by \"git status\" to show the recently executed\n   commands contains an incorrect entry.\n\nThanks to Eric, Glen and Junio for their comments on v2. Here are the\nchanges since v2:\n\nPatch 1 - Reworded the commit message.\n\nPatch 2 - Reworded the commit message, added a test and fixed error message\npointed out by Glen.\n\nPatch 3 - New cleanup.\n\nPatch 4 - Reworded the commit message, now only increments\ntodo_list->current if there is no error.\n\nPatch 5 - Swapped with next patch. Reworded the commit message, stopped\ntesting implementation (suggested by Glen). Expanded post-rewrite hook test.\n\nPatch 6 - Reworded the commit message, now uses the message file rather than\nthe author script to check if \"rebase --continue\" should commit staged\nchanges. Junio suggested using a separate file for this but I think that\nwould end up being more involved as we'd need to be careful about creating\nand removing it.\n\nPatch 7 - Reworded the commit message.\n\nThanks for the comments on V1, this series has now grown somewhat.\nPreviously I was worried that refactoring would change the behavior, but\nhaving thought about it the current behavior is wrong and should be changed.\n\nChanges since V1:\n\nRebased onto master to avoid a conflict with\nab/remove-implicit-use-of-the-repository\n\n * Patches 1-3 are new preparatory changes\n * Patches 4 & 5 are new and fix the first two issues listed above.\n * Patch 6 is the old patch 1 which has been rebased and the commit message\n   reworded. It fixes the last issues listed above.\n\nPhillip Wood (7):\n  rebase -i: move unlink() calls\n  rebase -i: remove patch file after conflict resolution\n  sequencer: use rebase_path_message()\n  sequencer: factor out part of pick_commits()\n  rebase: fix rewritten list for failed pick\n  rebase --continue: refuse to commit after failed command\n  rebase -i: fix adding failed command to the todo list\n\n sequencer.c                   | 182 ++++++++++++++++++----------------\n t/t3404-rebase-interactive.sh |  53 +++++++---\n t/t3418-rebase-continue.sh    |  18 ++++\n t/t3430-rebase-merges.sh      |  30 ++++--\n t/t5407-post-rewrite-hook.sh  |  48 +++++++++\n 5 files changed, 228 insertions(+), 103 deletions(-)\n\n\nbase-commit: a80be152923a46f04a06bade7bcc72870e46ca09\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1492%2Fphillipwood%2Frebase-dont-write-done-when-rescheduling-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1492/phillipwood/rebase-dont-write-done-when-rescheduling-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1492\n\nRange-diff vs v3:\n\n 1:  1ab1ad2ef07 ! 1:  ae4f873b3d0 rebase -i: move unlink() calls\n     @@ Metadata\n       ## Commit message ##\n          rebase -i: move unlink() calls\n      \n     -    At the start of each iteration the loop that picks commits removes\n     -    state files from the previous pick. However some of these are only\n     -    written if there are conflicts and so we break out of the loop after\n     -    writing them. Therefore they only need to be removed when the rebase\n     -    continues, not in each iteration.\n     +    At the start of each iteration the loop that picks commits removes the\n     +    state files from the previous pick. However some of these files are only\n     +    written if there are conflicts in which case we exit the loop before the\n     +    end of the loop body. Therefore they only need to be removed when the\n     +    rebase continues, not at the start of each iteration.\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n 2:  e2a758eb4a5 ! 2:  f540ed1d607 rebase -i: remove patch file after conflict resolution\n     @@ Commit message\n          now used in two different places rebase_path_patch() is added and used\n          to obtain the path for the patch.\n      \n     +    To construct the path write_patch() previously used get_dir() which\n     +    returns different paths depending on whether we're rebasing or\n     +    cherry-picking/reverting. As this function is only called when\n     +    rebasing it is safe to use a hard coded string for the directory\n     +    instead. An assertion is added to make sure we don't starting calling\n     +    this function when cherry-picking in the future.\n     +\n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## sequencer.c ##\n     @@ sequencer.c: static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n        * For the post-rewrite hook, we make a list of rewritten commits and\n        * their new sha1s.  The rewritten-pending list keeps the sha1s of\n      @@ sequencer.c: static int make_patch(struct repository *r,\n     + \tchar hex[GIT_MAX_HEXSZ + 1];\n     + \tint res = 0;\n     + \n     ++\tif (!is_rebase_i(opts))\n     ++\t\tBUG(\"make_patch should only be called when rebasing\");\n     ++\n     + \toid_to_hex_r(hex, &commit->object.oid);\n     + \tif (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)\n       \t\treturn -1;\n       \tres |= write_rebase_head(&commit->object.oid);\n       \n 3:  8f6c0e40567 ! 3:  818bdaf772d sequencer: use rebase_path_message()\n     @@ Commit message\n          made function to get the path name instead. This was the last\n          remaining use of the strbuf so remove it as well.\n      \n     +    As with the previous patch we now use a hard coded string rather than\n     +    git_dir() when constructing the path. This is safe for the same\n     +    reason (make_patch() is only called when rebasing) and is protected by\n     +    the assertion added in the previous patch.\n     +\n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n       ## sequencer.c ##\n 4:  a1fad70f4b9 = 4:  bd67765a864 sequencer: factor out part of pick_commits()\n 5:  df401945866 ! 5:  f6f330f7063 rebase: fix rewritten list for failed pick\n     @@ Commit message\n          disabled the user will see the messages from the merge machinery\n          detailing the problem.\n      \n     -    To simplify writing REBASE_HEAD in this case pick_one_commit() is\n     -    modified to avoid duplicating the code that adds the failed command\n     -    back into the todo list.\n     +    The code to add a failed command back into the todo list is duplicated\n     +    between pick_one_commit() and the loop in pick_commits(). Both sites\n     +    print advice about the command being rescheduled, decrement the current\n     +    item and save the todo list. To avoid duplicating this code\n     +    pick_one_commit() is modified to set a flag to indicate that the command\n     +    should be rescheduled in the main loop. This simplifies things as only\n     +    the remaining copy of the code needs to be modified to set REBASE_HEAD\n     +    rather than calling error_with_patch().\n      \n          Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n      \n 6:  2ed7cbe5fff = 6:  0ca5fccca17 rebase --continue: refuse to commit after failed command\n 7:  bbe0afde512 = 7:  8d5f6d51e19 rebase -i: fix adding failed command to the todo list\n\n-- \ngitgitgadget\n"},{"id":"481433","messageId":"818bdaf772dc34ef7282deb2e78ad9a37fa792f8.1694013772.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"[PATCH v4 3/7] sequencer: use rebase_path_message()","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:47Z","receivedAt":"2023-09-06T15:23:03Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nRather than constructing the path in a struct strbuf use the ready\nmade function to get the path name instead. This was the last\nremaining use of the strbuf so remove it as well.\n\nAs with the previous patch we now use a hard coded string rather than\ngit_dir() when constructing the path. This is safe for the same\nreason (make_patch() is only called when rebasing) and is protected by\nthe assertion added in the previous patch.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 7 ++-----\n 1 file changed, 2 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex c1911b0fc14..83be8bf2b6d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3501,7 +3501,6 @@ static int make_patch(struct repository *r,\n \t\t      struct commit *commit,\n \t\t      struct replay_opts *opts)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n \tstruct rev_info log_tree_opt;\n \tconst char *subject;\n \tchar hex[GIT_MAX_HEXSZ + 1];\n@@ -3532,18 +3531,16 @@ static int make_patch(struct repository *r,\n \t\tfclose(log_tree_opt.diffopt.file);\n \t}\n \n-\tstrbuf_addf(&buf, \"%s/message\", get_dir(opts));\n-\tif (!file_exists(buf.buf)) {\n+\tif (!file_exists(rebase_path_message())) {\n \t\tconst char *encoding = get_commit_output_encoding();\n \t\tconst char *commit_buffer = repo_logmsg_reencode(r,\n \t\t\t\t\t\t\t\t commit, NULL,\n \t\t\t\t\t\t\t\t encoding);\n \t\tfind_commit_subject(commit_buffer, &subject);\n-\t\tres |= write_message(subject, strlen(subject), buf.buf, 1);\n+\t\tres |= write_message(subject, strlen(subject), rebase_path_message(), 1);\n \t\trepo_unuse_commit_buffer(r, commit,\n \t\t\t\t\t commit_buffer);\n \t}\n-\tstrbuf_release(&buf);\n \trelease_revisions(&log_tree_opt);\n \n \treturn res;\n-- \ngitgitgadget\n\n"},{"id":"481434","messageId":"f540ed1d6076640f131ef302f7ca66a9cdba40d3.1694013772.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"[PATCH v4 2/7] rebase -i: remove patch file after conflict resolution","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:46Z","receivedAt":"2023-09-06T15:23:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen a rebase stops for the user to resolve conflicts it writes a patch\nfor the conflicting commit to .git/rebase-merge/patch. This file has\nbeen written since the introduction of \"git-rebase-interactive.sh\" in\n1b1dce4bae7 (Teach rebase an interactive mode, 2007-06-25). I assume the\nidea was to enable the user inspect the conflicting commit in the same\nway as they could for the patch based rebase. This file should be\ndeleted when the rebase continues as if the rebase stops for a failed\n\"exec\" command or a \"break\" command it is confusing to the user if there\nis a stale patch lying around from an unrelated command. As the path is\nnow used in two different places rebase_path_patch() is added and used\nto obtain the path for the patch.\n\nTo construct the path write_patch() previously used get_dir() which\nreturns different paths depending on whether we're rebasing or\ncherry-picking/reverting. As this function is only called when\nrebasing it is safe to use a hard coded string for the directory\ninstead. An assertion is added to make sure we don't starting calling\nthis function when cherry-picking in the future.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                | 16 ++++++++++++----\n t/t3418-rebase-continue.sh | 18 ++++++++++++++++++\n 2 files changed, 30 insertions(+), 4 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex de66bda9d5b..c1911b0fc14 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -138,6 +138,11 @@ static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n  * the commit object name of the corresponding patch.\n  */\n static GIT_PATH_FUNC(rebase_path_stopped_sha, \"rebase-merge/stopped-sha\")\n+/*\n+ * When we stop for the user to resolve conflicts this file contains\n+ * the patch of the commit that is being picked.\n+ */\n+static GIT_PATH_FUNC(rebase_path_patch, \"rebase-merge/patch\")\n /*\n  * For the post-rewrite hook, we make a list of rewritten commits and\n  * their new sha1s.  The rewritten-pending list keeps the sha1s of\n@@ -3502,12 +3507,14 @@ static int make_patch(struct repository *r,\n \tchar hex[GIT_MAX_HEXSZ + 1];\n \tint res = 0;\n \n+\tif (!is_rebase_i(opts))\n+\t\tBUG(\"make_patch should only be called when rebasing\");\n+\n \toid_to_hex_r(hex, &commit->object.oid);\n \tif (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)\n \t\treturn -1;\n \tres |= write_rebase_head(&commit->object.oid);\n \n-\tstrbuf_addf(&buf, \"%s/patch\", get_dir(opts));\n \tmemset(&log_tree_opt, 0, sizeof(log_tree_opt));\n \trepo_init_revisions(r, &log_tree_opt, NULL);\n \tlog_tree_opt.abbrev = 0;\n@@ -3515,15 +3522,15 @@ static int make_patch(struct repository *r,\n \tlog_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;\n \tlog_tree_opt.disable_stdin = 1;\n \tlog_tree_opt.no_commit_id = 1;\n-\tlog_tree_opt.diffopt.file = fopen(buf.buf, \"w\");\n+\tlog_tree_opt.diffopt.file = fopen(rebase_path_patch(), \"w\");\n \tlog_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;\n \tif (!log_tree_opt.diffopt.file)\n-\t\tres |= error_errno(_(\"could not open '%s'\"), buf.buf);\n+\t\tres |= error_errno(_(\"could not open '%s'\"),\n+\t\t\t\t   rebase_path_patch());\n \telse {\n \t\tres |= log_tree_commit(&log_tree_opt, commit);\n \t\tfclose(log_tree_opt.diffopt.file);\n \t}\n-\tstrbuf_reset(&buf);\n \n \tstrbuf_addf(&buf, \"%s/message\", get_dir(opts));\n \tif (!file_exists(buf.buf)) {\n@@ -4659,6 +4666,7 @@ static int pick_commits(struct repository *r,\n \tunlink(rebase_path_message());\n \tunlink(rebase_path_stopped_sha());\n \tunlink(rebase_path_amend());\n+\tunlink(rebase_path_patch());\n \n \twhile (todo_list->current < todo_list->nr) {\n \t\tstruct todo_item *item = todo_list->items + todo_list->current;\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 2d0789e554b..261e7cd754c 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -244,6 +244,24 @@ test_expect_success 'the todo command \"break\" works' '\n \ttest_path_is_file execed\n '\n \n+test_expect_success 'patch file is removed before break command' '\n+\ttest_when_finished \"git rebase --abort\" &&\n+\tcat >todo <<-\\EOF &&\n+\tpick commit-new-file-F2-on-topic-branch\n+\tbreak\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i --onto commit-new-file-F2 HEAD\n+\t) &&\n+\ttest_path_is_file .git/rebase-merge/patch &&\n+\techo 22>F2 &&\n+\tgit add F2 &&\n+\tgit rebase --continue &&\n+\ttest_path_is_missing .git/rebase-merge/patch\n+'\n+\n test_expect_success '--reschedule-failed-exec' '\n \ttest_when_finished \"git rebase --abort\" &&\n \ttest_must_fail git rebase -x false --reschedule-failed-exec HEAD^ &&\n-- \ngitgitgadget\n\n"},{"id":"481435","messageId":"ae4f873b3d02ae125835ea585e5cf2ad623409d6.1694013772.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"[PATCH v4 1/7] rebase -i: move unlink() calls","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:45Z","receivedAt":"2023-09-06T15:23:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nAt the start of each iteration the loop that picks commits removes the\nstate files from the previous pick. However some of these files are only\nwritten if there are conflicts in which case we exit the loop before the\nend of the loop body. Therefore they only need to be removed when the\nrebase continues, not at the start of each iteration.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex cc9821ece2c..de66bda9d5b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4656,6 +4656,10 @@ static int pick_commits(struct repository *r,\n \tif (read_and_refresh_cache(r, opts))\n \t\treturn -1;\n \n+\tunlink(rebase_path_message());\n+\tunlink(rebase_path_stopped_sha());\n+\tunlink(rebase_path_amend());\n+\n \twhile (todo_list->current < todo_list->nr) {\n \t\tstruct todo_item *item = todo_list->items + todo_list->current;\n \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n@@ -4679,10 +4683,7 @@ static int pick_commits(struct repository *r,\n \t\t\t\t\t\ttodo_list->total_nr,\n \t\t\t\t\t\topts->verbose ? \"\\n\" : \"\\r\");\n \t\t\t}\n-\t\t\tunlink(rebase_path_message());\n \t\t\tunlink(rebase_path_author_script());\n-\t\t\tunlink(rebase_path_stopped_sha());\n-\t\t\tunlink(rebase_path_amend());\n \t\t\tunlink(git_path_merge_head(r));\n \t\t\tunlink(git_path_auto_merge(r));\n \t\t\tdelete_ref(NULL, \"REBASE_HEAD\", NULL, REF_NO_DEREF);\n-- \ngitgitgadget\n\n"},{"id":"481436","messageId":"bd67765a864ab13b87e4ac9732b8c23c06b254eb.1694013772.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"[PATCH v4 4/7] sequencer: factor out part of pick_commits()","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:48Z","receivedAt":"2023-09-06T15:23:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nThis simplifies the next commit. If a pick fails we now return the error\nat the end of the loop body rather than returning early, a successful\n\"edit\" command continues to return early. There are three things to\ncheck to ensure that removing the early return for an error does not\nchange the behavior of the code:\n\n(1) We could enter the block guarded by \"if (reschedule)\". This block\n    is not entered because \"reschedlue\" is always zero when picking a\n    commit.\n\n(2) We could enter the block guarded by\n    \"else if (is_rebase_i(opts) &&  check_todo && !res)\". This block is\n    not entered when returning an error because \"res\" is non-zero in\n    that case.\n\n(3) todo_list->current could be incremented before returning. That is\n    avoided by moving the increment which is of course a potential\n    change in behavior itself. The move is safe because none of the\n    callers look at todo_list after this function returns. Moving the\n    increment makes it clear we only want to advance the current item\n    if the command was successful.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c | 132 ++++++++++++++++++++++++++++------------------------\n 1 file changed, 71 insertions(+), 61 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 83be8bf2b6d..b434c5a2570 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4645,6 +4645,72 @@ N_(\"Could not execute the todo command\\n\"\n \"    git rebase --edit-todo\\n\"\n \"    git rebase --continue\\n\");\n \n+static int pick_one_commit(struct repository *r,\n+\t\t\t   struct todo_list *todo_list,\n+\t\t\t   struct replay_opts *opts,\n+\t\t\t   int *check_todo)\n+{\n+\tint res;\n+\tstruct todo_item *item = todo_list->items + todo_list->current;\n+\tconst char *arg = todo_item_get_arg(todo_list, item);\n+\tif (is_rebase_i(opts))\n+\t\topts->reflog_message = reflog_message(\n+\t\t\topts, command_to_string(item->command), NULL);\n+\n+\tres = do_pick_commit(r, item, opts, is_final_fixup(todo_list),\n+\t\t\t     check_todo);\n+\tif (is_rebase_i(opts) && res < 0) {\n+\t\t/* Reschedule */\n+\t\tadvise(_(rescheduled_advice),\n+\t\t       get_item_line_length(todo_list, todo_list->current),\n+\t\t       get_item_line(todo_list, todo_list->current));\n+\t\ttodo_list->current--;\n+\t\tif (save_todo(todo_list, opts))\n+\t\t\treturn -1;\n+\t}\n+\tif (item->command == TODO_EDIT) {\n+\t\tstruct commit *commit = item->commit;\n+\t\tif (!res) {\n+\t\t\tif (!opts->verbose)\n+\t\t\t\tterm_clear_line();\n+\t\t\tfprintf(stderr, _(\"Stopped at %s...  %.*s\\n\"),\n+\t\t\t\tshort_commit_name(commit), item->arg_len, arg);\n+\t\t}\n+\t\treturn error_with_patch(r, commit,\n+\t\t\t\t\targ, item->arg_len, opts, res, !res);\n+\t}\n+\tif (is_rebase_i(opts) && !res)\n+\t\trecord_in_rewritten(&item->commit->object.oid,\n+\t\t\t\t    peek_command(todo_list, 1));\n+\tif (res && is_fixup(item->command)) {\n+\t\tif (res == 1)\n+\t\t\tintend_to_amend();\n+\t\treturn error_failed_squash(r, item->commit, opts,\n+\t\t\t\t\t   item->arg_len, arg);\n+\t} else if (res && is_rebase_i(opts) && item->commit) {\n+\t\tint to_amend = 0;\n+\t\tstruct object_id oid;\n+\n+\t\t/*\n+\t\t * If we are rewording and have either\n+\t\t * fast-forwarded already, or are about to\n+\t\t * create a new root commit, we want to amend,\n+\t\t * otherwise we do not.\n+\t\t */\n+\t\tif (item->command == TODO_REWORD &&\n+\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n+\t\t    (oideq(&item->commit->object.oid, &oid) ||\n+\t\t     (opts->have_squash_onto &&\n+\t\t      oideq(&opts->squash_onto, &oid))))\n+\t\t\tto_amend = 1;\n+\n+\t\treturn res | error_with_patch(r, item->commit,\n+\t\t\t\t\t      arg, item->arg_len, opts,\n+\t\t\t\t\t      res, to_amend);\n+\t}\n+\treturn res;\n+}\n+\n static int pick_commits(struct repository *r,\n \t\t\tstruct todo_list *todo_list,\n \t\t\tstruct replay_opts *opts)\n@@ -4700,66 +4766,9 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n-\t\t\tif (is_rebase_i(opts))\n-\t\t\t\topts->reflog_message = reflog_message(opts,\n-\t\t\t\t      command_to_string(item->command), NULL);\n-\n-\t\t\tres = do_pick_commit(r, item, opts,\n-\t\t\t\t\t     is_final_fixup(todo_list),\n-\t\t\t\t\t     &check_todo);\n-\t\t\tif (is_rebase_i(opts) && res < 0) {\n-\t\t\t\t/* Reschedule */\n-\t\t\t\tadvise(_(rescheduled_advice),\n-\t\t\t\t       get_item_line_length(todo_list,\n-\t\t\t\t\t\t\t    todo_list->current),\n-\t\t\t\t       get_item_line(todo_list,\n-\t\t\t\t\t\t     todo_list->current));\n-\t\t\t\ttodo_list->current--;\n-\t\t\t\tif (save_todo(todo_list, opts))\n-\t\t\t\t\treturn -1;\n-\t\t\t}\n-\t\t\tif (item->command == TODO_EDIT) {\n-\t\t\t\tstruct commit *commit = item->commit;\n-\t\t\t\tif (!res) {\n-\t\t\t\t\tif (!opts->verbose)\n-\t\t\t\t\t\tterm_clear_line();\n-\t\t\t\t\tfprintf(stderr,\n-\t\t\t\t\t\t_(\"Stopped at %s...  %.*s\\n\"),\n-\t\t\t\t\t\tshort_commit_name(commit),\n-\t\t\t\t\t\titem->arg_len, arg);\n-\t\t\t\t}\n-\t\t\t\treturn error_with_patch(r, commit,\n-\t\t\t\t\targ, item->arg_len, opts, res, !res);\n-\t\t\t}\n-\t\t\tif (is_rebase_i(opts) && !res)\n-\t\t\t\trecord_in_rewritten(&item->commit->object.oid,\n-\t\t\t\t\tpeek_command(todo_list, 1));\n-\t\t\tif (res && is_fixup(item->command)) {\n-\t\t\t\tif (res == 1)\n-\t\t\t\t\tintend_to_amend();\n-\t\t\t\treturn error_failed_squash(r, item->commit, opts,\n-\t\t\t\t\titem->arg_len, arg);\n-\t\t\t} else if (res && is_rebase_i(opts) && item->commit) {\n-\t\t\t\tint to_amend = 0;\n-\t\t\t\tstruct object_id oid;\n-\n-\t\t\t\t/*\n-\t\t\t\t * If we are rewording and have either\n-\t\t\t\t * fast-forwarded already, or are about to\n-\t\t\t\t * create a new root commit, we want to amend,\n-\t\t\t\t * otherwise we do not.\n-\t\t\t\t */\n-\t\t\t\tif (item->command == TODO_REWORD &&\n-\t\t\t\t    !repo_get_oid(r, \"HEAD\", &oid) &&\n-\t\t\t\t    (oideq(&item->commit->object.oid, &oid) ||\n-\t\t\t\t     (opts->have_squash_onto &&\n-\t\t\t\t      oideq(&opts->squash_onto, &oid))))\n-\t\t\t\t\tto_amend = 1;\n-\n-\t\t\t\treturn res | error_with_patch(r, item->commit,\n-\t\t\t\t\t\targ, item->arg_len, opts,\n-\t\t\t\t\t\tres, to_amend);\n-\t\t\t}\n+\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n+\t\t\tif (!res && item->command == TODO_EDIT)\n+\t\t\t\treturn 0;\n \t\t} else if (item->command == TODO_EXEC) {\n \t\t\tchar *end_of_arg = (char *)(arg + item->arg_len);\n \t\t\tint saved = *end_of_arg;\n@@ -4820,9 +4829,10 @@ static int pick_commits(struct repository *r,\n \t\t\treturn -1;\n \t\t}\n \n-\t\ttodo_list->current++;\n \t\tif (res)\n \t\t\treturn res;\n+\n+\t\ttodo_list->current++;\n \t}\n \n \tif (is_rebase_i(opts)) {\n-- \ngitgitgadget\n\n"},{"id":"481437","messageId":"f6f330f706310f7b1f0232b228455f635196acdb.1694013772.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"[PATCH v4 5/7] rebase: fix rewritten list for failed pick","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:49Z","receivedAt":"2023-09-06T15:23:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\ngit rebase keeps a list that maps the OID of each commit before it was\nrebased to the OID of the equivalent commit after the rebase.  This list\nis used to drive the \"post-rewrite\" hook that is called at the end of a\nsuccessful rebase. When a rebase stops for the user to resolve merge\nconflicts the OID of the commit being picked is written to\n\".git/rebase-merge/stopped-sha\". Then when the rebase is continued that\nOID is added to the list of rewritten commits. Unfortunately if a commit\ncannot be picked because it would overwrite an untracked file we still\nwrite the \"stopped-sha1\" file. This means that when the rebase is\ncontinued the commit is added into the list of rewritten commits even\nthough it has not been picked yet.\n\nFix this by not calling error_with_patch() for failed commands. The pick\nhas failed so there is nothing to commit and therefore we do not want to\nset up the state files for committing staged changes when the rebase\ncontinues. This change means we no-longer write a patch for the failed\ncommand or display the error message printed by error_with_patch(). As\nthe command has failed the patch isn't really useful and in any case the\nuser can inspect the commit associated with the failed command by\ninspecting REBASE_HEAD. Unless the user has disabled it we already print\nan advice message that is more helpful than the message from\nerror_with_patch() which the user will still see. Even if the advice is\ndisabled the user will see the messages from the merge machinery\ndetailing the problem.\n\nThe code to add a failed command back into the todo list is duplicated\nbetween pick_one_commit() and the loop in pick_commits(). Both sites\nprint advice about the command being rescheduled, decrement the current\nitem and save the todo list. To avoid duplicating this code\npick_one_commit() is modified to set a flag to indicate that the command\nshould be rescheduled in the main loop. This simplifies things as only\nthe remaining copy of the code needs to be modified to set REBASE_HEAD\nrather than calling error_with_patch().\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   | 19 +++++---------\n t/t3404-rebase-interactive.sh |  6 +++++\n t/t3430-rebase-merges.sh      |  4 +--\n t/t5407-post-rewrite-hook.sh  | 48 +++++++++++++++++++++++++++++++++++\n 4 files changed, 63 insertions(+), 14 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b434c5a2570..76932ab7b23 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4158,6 +4158,7 @@ static int do_merge(struct repository *r,\n \tif (ret < 0) {\n \t\terror(_(\"could not even attempt to merge '%.*s'\"),\n \t\t      merge_arg_len, arg);\n+\t\tunlink(git_path_merge_msg(r));\n \t\tgoto leave_merge;\n \t}\n \t/*\n@@ -4648,7 +4649,7 @@ N_(\"Could not execute the todo command\\n\"\n static int pick_one_commit(struct repository *r,\n \t\t\t   struct todo_list *todo_list,\n \t\t\t   struct replay_opts *opts,\n-\t\t\t   int *check_todo)\n+\t\t\t   int *check_todo, int* reschedule)\n {\n \tint res;\n \tstruct todo_item *item = todo_list->items + todo_list->current;\n@@ -4661,12 +4662,8 @@ static int pick_one_commit(struct repository *r,\n \t\t\t     check_todo);\n \tif (is_rebase_i(opts) && res < 0) {\n \t\t/* Reschedule */\n-\t\tadvise(_(rescheduled_advice),\n-\t\t       get_item_line_length(todo_list, todo_list->current),\n-\t\t       get_item_line(todo_list, todo_list->current));\n-\t\ttodo_list->current--;\n-\t\tif (save_todo(todo_list, opts))\n-\t\t\treturn -1;\n+\t\t*reschedule = 1;\n+\t\treturn -1;\n \t}\n \tif (item->command == TODO_EDIT) {\n \t\tstruct commit *commit = item->commit;\n@@ -4766,7 +4763,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t}\n \t\tif (item->command <= TODO_SQUASH) {\n-\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo);\n+\t\t\tres = pick_one_commit(r, todo_list, opts, &check_todo,\n+\t\t\t\t\t      &reschedule);\n \t\t\tif (!res && item->command == TODO_EDIT)\n \t\t\t\treturn 0;\n \t\t} else if (item->command == TODO_EXEC) {\n@@ -4820,10 +4818,7 @@ static int pick_commits(struct repository *r,\n \t\t\tif (save_todo(todo_list, opts))\n \t\t\t\treturn -1;\n \t\t\tif (item->commit)\n-\t\t\t\treturn error_with_patch(r,\n-\t\t\t\t\t\t\titem->commit,\n-\t\t\t\t\t\t\targ, item->arg_len,\n-\t\t\t\t\t\t\topts, res, 0);\n+\t\t\t\twrite_rebase_head(&item->commit->object.oid);\n \t\t} else if (is_rebase_i(opts) && check_todo && !res &&\n \t\t\t   reread_todo_if_changed(r, todo_list, opts)) {\n \t\t\treturn -1;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex ff0afad63e2..6d3788c588b 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1287,7 +1287,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \t>file6 &&\n \ttest_must_fail git rebase --continue &&\n \ttest_cmp_rev HEAD F &&\n+\ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \tgit rebase --continue &&\n \ttest_cmp_rev HEAD I\n '\n@@ -1305,7 +1307,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n \t>file6 &&\n \ttest_must_fail git rebase --continue &&\n \ttest_cmp_rev HEAD F &&\n+\ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n \tgit reset --hard original-branch2\n@@ -1323,7 +1327,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n \t>file6 &&\n \ttest_must_fail git rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = F &&\n+\ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 96ae0edf1e1..4938ebb1c17 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -165,12 +165,12 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n \ttest_tick &&\n \ttest_must_fail git rebase -ir HEAD &&\n+\ttest_cmp_rev REBASE_HEAD H^0 &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n-\ttest_path_is_file .git/rebase-merge/patch &&\n+\ttest_path_is_missing .git/rebase-merge/patch &&\n \n \t: fail because of merge conflict &&\n-\trm G.t .git/rebase-merge/patch &&\n \tgit reset --hard conflicting-G &&\n \ttest_must_fail git rebase --continue &&\n \t! grep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex 5f3ff051ca2..ad7f8c6f002 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -17,6 +17,12 @@ test_expect_success 'setup' '\n \tgit checkout A^0 &&\n \ttest_commit E bar E &&\n \ttest_commit F foo F &&\n+\tgit checkout B &&\n+\tgit merge E &&\n+\tgit tag merge-E &&\n+\ttest_commit G G &&\n+\ttest_commit H H &&\n+\ttest_commit I I &&\n \tgit checkout main &&\n \n \ttest_hook --setup post-rewrite <<-EOF\n@@ -173,6 +179,48 @@ test_fail_interactive_rebase () {\n \t)\n }\n \n+test_expect_success 'git rebase with failed pick' '\n+\tclear_hook_input &&\n+\tcat >todo <<-\\EOF &&\n+\texec >bar\n+\tmerge -C merge-E E\n+\texec >G\n+\tpick G\n+\texec >H 2>I\n+\tpick H\n+\tfixup I\n+\tEOF\n+\n+\t(\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i D D 2>err\n+\t) &&\n+\tgrep \"would be overwritten\" err &&\n+\trm bar &&\n+\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"would be overwritten\" err &&\n+\trm G &&\n+\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"would be overwritten\" err &&\n+\trm H &&\n+\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"would be overwritten\" err &&\n+\trm I &&\n+\n+\tgit rebase --continue &&\n+\techo rebase >expected.args &&\n+\tcat >expected.data <<-EOF &&\n+\t$(git rev-parse merge-E) $(git rev-parse HEAD~2)\n+\t$(git rev-parse G) $(git rev-parse HEAD~1)\n+\t$(git rev-parse H) $(git rev-parse HEAD)\n+\t$(git rev-parse I) $(git rev-parse HEAD)\n+\tEOF\n+\tverify_hook_input\n+'\n+\n test_expect_success 'git rebase -i (unchanged)' '\n \tgit reset --hard D &&\n \tclear_hook_input &&\n-- \ngitgitgadget\n\n"},{"id":"481438","messageId":"0ca5fccca17af22de6da846d82e960ebe1aa87b1.1694013772.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"[PATCH v4 6/7] rebase --continue: refuse to commit after failed command","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:50Z","receivedAt":"2023-09-06T15:23:19Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nIf a commit cannot be picked because it would overwrite an untracked\nfile then \"git rebase --continue\" should refuse to commit any staged\nchanges as the commit was not picked. This is implemented by refusing to\ncommit if the message file is missing. The message file is chosen for\nthis check because it is only written when \"git rebase\" stops for the\nuser to resolve merge conflicts.\n\nExisting commands that refuse to commit staged changes when continuing\nsuch as a failed \"exec\" rely on checking for the absence of the author\nscript in run_git_commit(). This prevents the staged changes from being\ncommitted but prints\n\n    error: could not open '.git/rebase-merge/author-script' for\n    reading\n\nbefore the message about not being able to commit. This is confusing to\nusers and so checking for the message file instead improves the user\nexperience. The existing test for refusing to commit after a failed exec\nis updated to check that we do not print the error message about a\nmissing author script anymore.\n\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   |  5 +++++\n t/t3404-rebase-interactive.sh | 18 +++++++++++++++++-\n t/t3430-rebase-merges.sh      |  4 ++++\n 3 files changed, 26 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 76932ab7b23..38b0f213157 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4980,6 +4980,11 @@ static int commit_staged_changes(struct repository *r,\n \n \tis_clean = !has_uncommitted_changes(r, 0);\n \n+\tif (!is_clean && !file_exists(rebase_path_message())) {\n+\t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n+\n+\t\treturn error(_(staged_changes_advice), gpg_opt, gpg_opt);\n+\t}\n \tif (file_exists(rebase_path_amend())) {\n \t\tstruct strbuf rev = STRBUF_INIT;\n \t\tstruct object_id head, to_amend;\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 6d3788c588b..a8ad398956a 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -604,7 +604,8 @@ test_expect_success 'clean error after failed \"exec\"' '\n \techo \"edited again\" > file7 &&\n \tgit add file7 &&\n \ttest_must_fail git rebase --continue 2>error &&\n-\ttest_i18ngrep \"you have staged changes in your working tree\" error\n+\ttest_i18ngrep \"you have staged changes in your working tree\" error &&\n+\ttest_i18ngrep ! \"could not open.*for reading\" error\n '\n \n test_expect_success 'rebase a detached HEAD' '\n@@ -1290,6 +1291,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n+\tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n \ttest_cmp_rev HEAD I\n '\n@@ -1310,6 +1316,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (squash)'\n \ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n+\tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I &&\n \tgit reset --hard original-branch2\n@@ -1330,6 +1341,11 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' '\n \ttest_cmp_rev REBASE_HEAD I &&\n \trm file6 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n+\tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n \ttest $(git cat-file commit HEAD | sed -ne \\$p) = I\n '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 4938ebb1c17..804ff819782 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -169,6 +169,10 @@ test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/done &&\n \tgrep \"^merge -C .* G$\" .git/rebase-merge/git-rebase-todo &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n+\techo changed >file1 &&\n+\tgit add file1 &&\n+\ttest_must_fail git rebase --continue 2>err &&\n+\tgrep \"error: you have staged changes in your working tree\" err &&\n \n \t: fail because of merge conflict &&\n \tgit reset --hard conflicting-G &&\n-- \ngitgitgadget\n\n"},{"id":"481439","messageId":"8d5f6d51e1959c7908ce268692c1ae9d693b93c5.1694013772.git.gitgitgadget@gmail.com","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"[PATCH v4 7/7] rebase -i: fix adding failed command to the todo list","fromName":"Phillip Wood via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-09-06T15:22:51Z","receivedAt":"2023-09-06T15:23:20Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"From: Phillip Wood <phillip.wood@dunelm.org.uk>\n\nWhen rebasing commands are moved from the todo list in \"git-rebase-todo\"\nto the \"done\" file (which is used by \"git status\" to show the recently\nexecuted commands) just before they are executed. This means that if a\ncommand fails because it would overwrite an untracked file it has to be\nadded back into the todo list before the rebase stops for the user to\nfix the problem.\n\nUnfortunately when a failed command is added back into the todo list the\ncommand preceding it is erroneously appended to the \"done\" file.  This\nmeans that when rebase stops after \"pick B\" fails the \"done\" file\ncontains\n\n\tpick A\n\tpick B\n\tpick A\n\ninstead of\n\n\tpick A\n\tpick B\n\nThis happens because save_todo() updates the \"done\" file with the\nprevious command whenever \"git-rebase-todo\" is updated. When we add the\nfailed pick back into \"git-rebase-todo\" we do not want to update\n\"done\". Fix this by adding a \"reschedule\" parameter to save_todo() which\nprevents the \"done\" file from being updated when adding a failed command\nback into the \"git-rebase-todo\" file. A couple of the existing tests are\nmodified to improve their coverage as none of them trigger this bug or\ncheck the \"done\" file.\n\nReported-by: Stefan Haller <lists@haller-berlin.de>\nSigned-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n---\n sequencer.c                   | 12 ++++++------\n t/t3404-rebase-interactive.sh | 29 ++++++++++++++++++-----------\n t/t3430-rebase-merges.sh      | 22 ++++++++++++++++------\n 3 files changed, 40 insertions(+), 23 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 38b0f213157..175addb7ca6 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -3397,7 +3397,8 @@ give_advice:\n \treturn -1;\n }\n \n-static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n+static int save_todo(struct todo_list *todo_list, struct replay_opts *opts,\n+\t\t     int reschedule)\n {\n \tstruct lock_file todo_lock = LOCK_INIT;\n \tconst char *todo_path = get_todo_path(opts);\n@@ -3407,7 +3408,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \t * rebase -i writes \"git-rebase-todo\" without the currently executing\n \t * command, appending it to \"done\" instead.\n \t */\n-\tif (is_rebase_i(opts))\n+\tif (is_rebase_i(opts) && !reschedule)\n \t\tnext++;\n \n \tfd = hold_lock_file_for_update(&todo_lock, todo_path, 0);\n@@ -3420,7 +3421,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)\n \tif (commit_lock_file(&todo_lock) < 0)\n \t\treturn error(_(\"failed to finalize '%s'\"), todo_path);\n \n-\tif (is_rebase_i(opts) && next > 0) {\n+\tif (is_rebase_i(opts) && !reschedule && next > 0) {\n \t\tconst char *done = rebase_path_done();\n \t\tint fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666);\n \t\tint ret = 0;\n@@ -4733,7 +4734,7 @@ static int pick_commits(struct repository *r,\n \t\tconst char *arg = todo_item_get_arg(todo_list, item);\n \t\tint check_todo = 0;\n \n-\t\tif (save_todo(todo_list, opts))\n+\t\tif (save_todo(todo_list, opts, reschedule))\n \t\t\treturn -1;\n \t\tif (is_rebase_i(opts)) {\n \t\t\tif (item->command != TODO_COMMENT) {\n@@ -4814,8 +4815,7 @@ static int pick_commits(struct repository *r,\n \t\t\t       get_item_line_length(todo_list,\n \t\t\t\t\t\t    todo_list->current),\n \t\t\t       get_item_line(todo_list, todo_list->current));\n-\t\t\ttodo_list->current--;\n-\t\t\tif (save_todo(todo_list, opts))\n+\t\t\tif (save_todo(todo_list, opts, reschedule))\n \t\t\t\treturn -1;\n \t\t\tif (item->commit)\n \t\t\t\twrite_rebase_head(&item->commit->object.oid);\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex a8ad398956a..71da9c465a1 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1277,19 +1277,24 @@ test_expect_success 'todo count' '\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n-\tgit checkout --force branch2 &&\n+\tgit checkout --force A &&\n \tgit clean -f &&\n+\tcat >todo <<-EOF &&\n+\texec >file2\n+\tpick $(git rev-parse B) B\n+\tpick $(git rev-parse C) C\n+\tpick $(git rev-parse D) D\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \t(\n-\t\tset_fake_editor &&\n-\t\tFAKE_LINES=\"edit 1 2\" git rebase -i A\n+\t\tset_replace_editor todo &&\n+\t\ttest_must_fail git rebase -i A\n \t) &&\n-\ttest_cmp_rev HEAD F &&\n-\ttest_path_is_missing file6 &&\n-\t>file6 &&\n-\ttest_must_fail git rebase --continue &&\n-\ttest_cmp_rev HEAD F &&\n-\ttest_cmp_rev REBASE_HEAD I &&\n-\trm file6 &&\n+\ttest_cmp_rev HEAD B &&\n+\ttest_cmp_rev REBASE_HEAD C &&\n+\thead -n3 todo >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm file2 &&\n \ttest_path_is_missing .git/rebase-merge/patch &&\n \techo changed >file1 &&\n \tgit add file1 &&\n@@ -1297,7 +1302,9 @@ test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '\n \tgrep \"error: you have staged changes in your working tree\" err &&\n \tgit reset --hard HEAD &&\n \tgit rebase --continue &&\n-\ttest_cmp_rev HEAD I\n+\ttest_cmp_rev HEAD D &&\n+\ttail -n3 todo >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success 'rebase -i commits that overwrite untracked files (squash)' '\ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 804ff819782..0b0877b9846 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -128,14 +128,24 @@ test_expect_success 'generate correct todo list' '\n '\n \n test_expect_success '`reset` refuses to overwrite untracked files' '\n-\tgit checkout -b refuse-to-reset &&\n+\tgit checkout B &&\n \ttest_commit dont-overwrite-untracked &&\n-\tgit checkout @{-1} &&\n-\t: >dont-overwrite-untracked.t &&\n-\techo \"reset refs/tags/dont-overwrite-untracked\" >script-from-scratch &&\n+\tcat >script-from-scratch <<-EOF &&\n+\texec >dont-overwrite-untracked.t\n+\tpick $(git rev-parse B) B\n+\treset refs/tags/dont-overwrite-untracked\n+\tpick $(git rev-parse C) C\n+\texec cat .git/rebase-merge/done >actual\n+\tEOF\n \ttest_config sequence.editor \\\"\"$PWD\"/replace-editor.sh\\\" &&\n-\ttest_must_fail git rebase -ir HEAD &&\n-\tgit rebase --abort\n+\ttest_must_fail git rebase -ir A &&\n+\ttest_cmp_rev HEAD B &&\n+\thead -n3 script-from-scratch >expect &&\n+\ttest_cmp expect .git/rebase-merge/done &&\n+\trm dont-overwrite-untracked.t &&\n+\tgit rebase --continue &&\n+\ttail -n3 script-from-scratch >>expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success '`reset` rejects trees' '\n-- \ngitgitgadget\n"},{"id":"481454","messageId":"xmqqedjbm2xl.fsf@gitster.g","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/7] rebase -i: impove handling of failed commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-09-06T21:01:26Z","receivedAt":"2023-09-06T21:01:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Phillip Wood via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> This series fixes several bugs in the way we handle a commit cannot be\n> picked because it would overwrite an untracked file.\n>\n>  * after a failed pick \"git rebase --continue\" will happily commit any\n>    staged changes even though no commit was picked.\n>\n>  * the commit of the failed pick is recorded as rewritten even though no\n>    commit was picked.\n>\n>  * the \"done\" file used by \"git status\" to show the recently executed\n>    commands contains an incorrect entry.\n>\n> Thanks to Eric, Glen and Junio for their comments on v2. Here are the\n> changes since v2:\n>\n> Patch 1 - Reworded the commit message.\n>\n> Patch 2 - Reworded the commit message, added a test and fixed error message\n> pointed out by Glen.\n>\n> Patch 3 - New cleanup.\n>\n> Patch 4 - Reworded the commit message, now only increments\n> todo_list->current if there is no error.\n>\n> Patch 5 - Swapped with next patch. Reworded the commit message, stopped\n> testing implementation (suggested by Glen). Expanded post-rewrite hook test.\n>\n> Patch 6 - Reworded the commit message, now uses the message file rather than\n> the author script to check if \"rebase --continue\" should commit staged\n> changes. Junio suggested using a separate file for this but I think that\n> would end up being more involved as we'd need to be careful about creating\n> and removing it.\n>\n> Patch 7 - Reworded the commit message.\n\nThanks.  This version looks good to me (although I am not as\nfamiliar with this part of the codebase as others on the Cc: line).\n\n"},{"id":"481517","messageId":"6b927687-cf6e-d73e-78fb-bd4f46736928@gmx.de","threadId":"59416","inReplyTo":"pull.1492.v4.git.1694013771.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 0/7] rebase -i: impove handling of failed commands","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2023-09-07T09:56:39Z","receivedAt":"2023-09-07T20:26:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Phillip,\n\nOn Wed, 6 Sep 2023, Phillip Wood via GitGitGadget wrote:\n\n> Range-diff vs v3:\n>\n>  1:  1ab1ad2ef07 ! 1:  ae4f873b3d0 rebase -i: move unlink() calls\n>      @@ Metadata\n>        ## Commit message ##\n>           rebase -i: move unlink() calls\n>\n>      -    At the start of each iteration the loop that picks commits removes\n>      -    state files from the previous pick. However some of these are only\n>      -    written if there are conflicts and so we break out of the loop after\n>      -    writing them. Therefore they only need to be removed when the rebase\n>      -    continues, not in each iteration.\n>      +    At the start of each iteration the loop that picks commits removes the\n>      +    state files from the previous pick. However some of these files are only\n>      +    written if there are conflicts in which case we exit the loop before the\n>      +    end of the loop body. Therefore they only need to be removed when the\n>      +    rebase continues, not at the start of each iteration.\n>\n>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n>  2:  e2a758eb4a5 ! 2:  f540ed1d607 rebase -i: remove patch file after conflict resolution\n>      @@ Commit message\n>           now used in two different places rebase_path_patch() is added and used\n>           to obtain the path for the patch.\n>\n>      +    To construct the path write_patch() previously used get_dir() which\n>      +    returns different paths depending on whether we're rebasing or\n>      +    cherry-picking/reverting. As this function is only called when\n>      +    rebasing it is safe to use a hard coded string for the directory\n>      +    instead. An assertion is added to make sure we don't starting calling\n>      +    this function when cherry-picking in the future.\n>      +\n>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n>        ## sequencer.c ##\n>      @@ sequencer.c: static GIT_PATH_FUNC(rebase_path_amend, \"rebase-merge/amend\")\n>         * For the post-rewrite hook, we make a list of rewritten commits and\n>         * their new sha1s.  The rewritten-pending list keeps the sha1s of\n>       @@ sequencer.c: static int make_patch(struct repository *r,\n>      + \tchar hex[GIT_MAX_HEXSZ + 1];\n>      + \tint res = 0;\n>      +\n>      ++\tif (!is_rebase_i(opts))\n>      ++\t\tBUG(\"make_patch should only be called when rebasing\");\n>      ++\n>      + \toid_to_hex_r(hex, &commit->object.oid);\n>      + \tif (write_message(hex, strlen(hex), rebase_path_stopped_sha(), 1) < 0)\n>        \t\treturn -1;\n>        \tres |= write_rebase_head(&commit->object.oid);\n>\n>  3:  8f6c0e40567 ! 3:  818bdaf772d sequencer: use rebase_path_message()\n>      @@ Commit message\n>           made function to get the path name instead. This was the last\n>           remaining use of the strbuf so remove it as well.\n>\n>      +    As with the previous patch we now use a hard coded string rather than\n>      +    git_dir() when constructing the path. This is safe for the same\n>      +    reason (make_patch() is only called when rebasing) and is protected by\n>      +    the assertion added in the previous patch.\n>      +\n>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n>        ## sequencer.c ##\n>  4:  a1fad70f4b9 = 4:  bd67765a864 sequencer: factor out part of pick_commits()\n>  5:  df401945866 ! 5:  f6f330f7063 rebase: fix rewritten list for failed pick\n>      @@ Commit message\n>           disabled the user will see the messages from the merge machinery\n>           detailing the problem.\n>\n>      -    To simplify writing REBASE_HEAD in this case pick_one_commit() is\n>      -    modified to avoid duplicating the code that adds the failed command\n>      -    back into the todo list.\n>      +    The code to add a failed command back into the todo list is duplicated\n>      +    between pick_one_commit() and the loop in pick_commits(). Both sites\n>      +    print advice about the command being rescheduled, decrement the current\n>      +    item and save the todo list. To avoid duplicating this code\n>      +    pick_one_commit() is modified to set a flag to indicate that the command\n>      +    should be rescheduled in the main loop. This simplifies things as only\n>      +    the remaining copy of the code needs to be modified to set REBASE_HEAD\n>      +    rather than calling error_with_patch().\n>\n>           Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n>\n>  6:  2ed7cbe5fff = 6:  0ca5fccca17 rebase --continue: refuse to commit after failed command\n>  7:  bbe0afde512 = 7:  8d5f6d51e19 rebase -i: fix adding failed command to the todo list\n\nThank you for indulging me. This iteration looks good to me!\n\nCiao,\nJohannes\n"},{"id":"481518","messageId":"xmqqledhk9jh.fsf@gitster.g","threadId":"59416","inReplyTo":"6b927687-cf6e-d73e-78fb-bd4f46736928@gmx.de","subject":"Re: [PATCH v4 0/7] rebase -i: impove handling of failed commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-09-07T20:33:54Z","receivedAt":"2023-09-07T20:34:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> ...\n>>  6:  2ed7cbe5fff = 6:  0ca5fccca17 rebase --continue: refuse to commit after failed command\n>>  7:  bbe0afde512 = 7:  8d5f6d51e19 rebase -i: fix adding failed command to the todo list\n>\n> Thank you for indulging me. This iteration looks good to me!\n\nThanks, all.  Let's merge it down to 'next'.\n"}]}