{"thread":{"id":"51492","subject":"[RFC PATCH 0/9] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo' and co.","startedAt":"2019-07-17T14:41:54Z","lastAt":"2020-01-28T22:47:03Z","messageCount":57,"participants":["Alban Gruin","Junio C Hamano","Phillip Wood","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"379007","messageId":"20190717143918.7406-1-alban.gruin@gmail.com","threadId":"51492","inReplyTo":null,"subject":"[RFC PATCH 0/9] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo' and co.","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:09Z","receivedAt":"2019-07-17T14:41:54Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"To prevent mistakes when editing a branch, rebase features a knob,\nrebase.missingCommitsCheck, to warn the user if a commit was dropped.\nUnfortunately, this check is only effective for the initial edit, which\nmeans that if you edit the todo list at a later point of the rebase and\ndropped a commit, no warnings or errors would be issued.\n\nThis adds the ability to check if commits were dropped when resuming a\nrebase (with `rebase --continue'), when editing the todo list (with\n`rebase --edit-todo'), or when reloading the todo list after an `exec'\ncommand.\n\nThe idea to extend this feature was suggested to me more than a year ago\nby Phillip Wood, if I'm not mistaken.  I postponed this until four month\nago, when ag/sequencer-reduce-rewriting-todo finally hit master, but I\nhad to stop because of other obligations.  I could go back to work one\nmonth ago, when I did the bulk of this series, but I lacked time to\npolish it, so it waited a bit more.  Now, I think it is in a good shape\nto be sent, although it is still RFC-quality to me.  The advertised\nfunctionality should work well, but perhaps there is some flaws I\nmissed.\n\nThe first two patches are new tests, demonstrating that after the\ninitial edit, the check is not done.  The next four are what could be\nqualified as omissions from ag/sequencer-reduce-rewriting-todo, but they\nare quite important (IMHO) for the rest of the series.  The last three\nactually extend rebase.missingCommitsCheck.\n\nThis is based on master (9d418600f4, \"The fifth batch\").\n\nThe tip of this series is tagged as \"edit-todo-drop-rfc\" in\nhttps://github.com/agrn/git.\n\nAlban Gruin (9):\n  t3404: demonstrate that --edit-todo does not check for dropped commits\n  t3429: demonstrate that rebase exec does not check for dropped commits\n  sequencer: update `total_nr' when adding an item to a todo list\n  sequencer: update `done_nr' when skipping commands in a todo list\n  sequencer: move the code writing total_nr on the disk to a new\n    function\n  sequencer: add a parameter to sequencer_continue() to accept a todo\n    list\n  rebase-interactive: todo_list_check() also uses the done list\n  rebase-interactive: warn if commit is dropped with --edit-todo\n  sequencer: have read_populate_todo() check for dropped commits\n\n builtin/rebase.c              |  2 +-\n builtin/revert.c              |  2 +-\n rebase-interactive.c          | 67 +++++++++++++++++++++++-----\n rebase-interactive.h          |  6 ++-\n sequencer.c                   | 53 ++++++++++++++--------\n sequencer.h                   |  3 +-\n t/t3404-rebase-interactive.sh | 82 +++++++++++++++++++++++++++++++++++\n t/t3429-rebase-edit-todo.sh   | 44 ++++++++++++++++++-\n 8 files changed, 224 insertions(+), 35 deletions(-)\n\n-- \n2.22.0\n\n"},{"id":"379008","messageId":"20190717143918.7406-2-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 1/9] t3404: demonstrate that --edit-todo does not check for dropped commits","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:10Z","receivedAt":"2019-07-17T14:41:56Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"When set to \"warn\" or \"error\", `rebase.missingCommitCheck' would make\nrebase -i warn if the user removed commits from the todo list to prevent\nmistakes.  Unfortunately, rebase --edit-todo and rebase --continue don't\ntake it into account.\n\nThis adds three tests to t3404 to demonstrate this.  The first one is\nnot broken, as when `rebase.missingCommitsCheck' is not set, nothing in\nparticular must be done towards dropped commits.  The two others are\nbroken, demonstrating the problem.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t3404-rebase-interactive.sh | 82 +++++++++++++++++++++++++++++++++++\n 1 file changed, 82 insertions(+)\n\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 461dd539ff..f5c0a8d2bb 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1345,6 +1345,88 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n+\ttest_config rebase.missingCommitsCheck ignore &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n+\t\tgit rebase -i --root >/dev/null 2>stderr &&\n+\tFAKE_LINES=\"1 2 4\" git rebase --edit-todo &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+cat >expect <<EOF\n+error: invalid line 5: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+Warning: some commits may have been dropped accidentally.\n+Dropped commits (newer to older):\n+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+To avoid this message, use \"drop\" to explicitly remove a commit.\n+\n+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n+The possible behaviours are: ignore, warn, error.\n+\n+EOF\n+\n+tail -n 8 <expect >expect.2\n+\n+test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+\ttest_config rebase.missingCommitsCheck warn &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\ttest_must_fail env FAKE_LINES=\"1 2 3 4 bad 5\" \\\n+\t\tgit rebase -i --root >/dev/null 2>stderr &&\n+\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n+\ttest_i18ncmp expect actual &&\n+\tgit rebase --continue 2>actual.2 &&\n+\thead -n 8 <actual.2 >actual &&\n+\ttest_i18ncmp expect.2 actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual.2\n+'\n+\n+cat >expect <<EOF\n+error: invalid line 3: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)\n+Warning: some commits may have been dropped accidentally.\n+Dropped commits (newer to older):\n+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)\n+To avoid this message, use \"drop\" to explicitly remove a commit.\n+\n+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n+The possible behaviours are: ignore, warn, error.\n+\n+EOF\n+\n+tail -n 9 <expect >expect.2\n+\n+test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+\ttest_config rebase.missingCommitsCheck error &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n+\t\tgit rebase -i --root >/dev/null 2>stderr &&\n+\ttest_must_fail env FAKE_LINES=\"1 2 4\" \\\n+\t\tgit rebase --edit-todo 2>actual &&\n+\ttest_i18ncmp expect actual &&\n+\ttest_must_fail git rebase --continue 2>actual &&\n+\ttest_i18ncmp expect.2 actual &&\n+\tcp .git/rebase-merge/git-rebase-todo.backup \\\n+\t\t.git/rebase-merge/git-rebase-todo &&\n+\tFAKE_LINES=\"1 2 drop 3 4 drop 5\" \\\n+\t\tgit rebase --edit-todo &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n \trebase_setup_and_clean abbrevcmd &&\n \ttest_commit \"first\" file1.txt \"first line\" first &&\n-- \n2.22.0\n\n"},{"id":"379009","messageId":"20190717143918.7406-3-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 2/9] t3429: demonstrate that rebase exec does not check for dropped commits","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:11Z","receivedAt":"2019-07-17T14:41:58Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"After executing a command, rebase reloads the todo list from the disk,\nin case the script has modified it, but does not honour\nrebase.missingCommitsCheck.\n\nThis adds three tests to t3429 to demonstrate this.  The first one is\nnot broken, as when `rebase.missingCommitsCheck' is not set, nothing\nshould be done for dropped commits.  The last two are, demonstrating the\nproblem.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t3429-rebase-edit-todo.sh | 44 +++++++++++++++++++++++++++++++++++--\n 1 file changed, 42 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t3429-rebase-edit-todo.sh b/t/t3429-rebase-edit-todo.sh\nindex 76f6d306ea..2bb9fb65fa 100755\n--- a/t/t3429-rebase-edit-todo.sh\n+++ b/t/t3429-rebase-edit-todo.sh\n@@ -4,15 +4,16 @@ test_description='rebase should reread the todo file if an exec modifies it'\n \n . ./test-lib.sh\n \n+todo=.git/rebase-merge/git-rebase-todo\n+\n test_expect_success 'rebase exec modifies rebase-todo' '\n \ttest_commit initial &&\n-\ttodo=.git/rebase-merge/git-rebase-todo &&\n \tgit rebase HEAD -x \"echo exec touch F >>$todo\" &&\n \ttest -e F\n '\n \n test_expect_success SHA1 'loose object cache vs re-reading todo list' '\n-\tGIT_REBASE_TODO=.git/rebase-merge/git-rebase-todo &&\n+\tGIT_REBASE_TODO=$todo &&\n \texport GIT_REBASE_TODO &&\n \twrite_script append-todo.sh <<-\\EOS &&\n \t# For values 5 and 6, this yields SHA-1s with the same first two digits\n@@ -33,4 +34,43 @@ test_expect_success SHA1 'loose object cache vs re-reading todo list' '\n \tgit rebase HEAD -x \"./append-todo.sh 5 6\"\n '\n \n+test_expect_success 'rebase exec respects rebase.missingCommitsCheck = ignore' '\n+\ttest_config rebase.missingCommitsCheck ignore &&\n+\tgit rebase HEAD~2 --keep-empty -x \"echo >$todo\" &&\n+\ttest 5 = $(git cat-file commit HEAD | sed -ne \\$p)\n+'\n+\n+cat >expect <<EOF\n+Warning: some commits may have been dropped accidentally.\n+Dropped commits (newer to older):\n+ - $(git rev-list --pretty=oneline --abbrev-commit -1 HEAD@{2})\n+To avoid this message, use \"drop\" to explicitly remove a commit.\n+\n+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n+The possible behaviours are: ignore, warn, error.\n+EOF\n+\n+test_expect_failure 'rebase exec respects rebase.missingCommitsCheck = warn' '\n+\ttest_config rebase.missingCommitsCheck warn &&\n+\tgit reset --hard HEAD@{2} &&\n+\tgit rebase HEAD~2 --keep-empty -x \"echo >$todo\" 2>actual.2 &&\n+\thead -n8 actual.2 | tail -n7 >actual &&\n+\ttest_i18ncmp expect actual &&\n+\ttest 5 = $(git cat-file commit HEAD | sed -ne \\$p)\n+'\n+\n+test_expect_failure 'rebase exec respects rebase.missingCommitsCheck = error' '\n+\ttest_config rebase.missingCommitsCheck error &&\n+\tgit reset --hard HEAD@{2} &&\n+\ttest_must_fail git rebase HEAD~2 --keep-empty -x \"echo >$todo\" 2>actual.2 &&\n+\thead -n8 actual.2 | tail -n7 >actual &&\n+\ttest_i18ncmp expect actual &&\n+\techo drop $(git rev-list --pretty=oneline -1 HEAD@{1}) >$todo &&\n+\tgit rebase --continue 2>actual &&\n+\ttest 5 = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/master\" \\\n+\t\tactual\n+'\n+\n test_done\n-- \n2.22.0\n\n"},{"id":"379010","messageId":"20190717143918.7406-4-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 3/9] sequencer: update `total_nr' when adding an item to a todo list","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:12Z","receivedAt":"2019-07-17T14:42:00Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"`total_nr' is the total amount of items, done and toto, that are in a\ntodo list.  But unlike `nr', it was not updated when an item was\nappended to the list.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex cf262701e8..e61ae75451 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2070,6 +2070,7 @@ void todo_list_release(struct todo_list *todo_list)\n static struct todo_item *append_new_todo(struct todo_list *todo_list)\n {\n \tALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);\n+\t++todo_list->total_nr;\n \treturn todo_list->items + todo_list->nr++;\n }\n \n-- \n2.22.0\n\n"},{"id":"379011","messageId":"20190717143918.7406-5-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 4/9] sequencer: update `done_nr' when skipping commands in a todo list","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:13Z","receivedAt":"2019-07-17T14:42:02Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"In a todo list, `done_nr' is the amount of commands that were executed\nor skipped, but skip_unnecessary_picks() did not update it.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex e61ae75451..ec9c3d4dc5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4939,6 +4939,7 @@ static int skip_unnecessary_picks(struct repository *r,\n \t\tMOVE_ARRAY(todo_list->items, todo_list->items + i, todo_list->nr - i);\n \t\ttodo_list->nr -= i;\n \t\ttodo_list->current = 0;\n+\t\ttodo_list->done_nr += i;\n \n \t\tif (is_fixup(peek_command(todo_list, 0)))\n \t\t\trecord_in_rewritten(base_oid, peek_command(todo_list, 0));\n-- \n2.22.0\n\n"},{"id":"379012","messageId":"20190717143918.7406-6-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 5/9] sequencer: move the code writing total_nr on the disk to a new function","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:14Z","receivedAt":"2019-07-17T14:42:03Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The total amount of commands can be used to show the progression of the\nrebasing in a shell.  This number is written to the disk by\nread_populate_todo() when the todo list is loaded from\nsequencer_continue() or pick_commits(), but not by complete_action(),\nwhich releases its todo list before calling sequencer_continue(), which\nreloads it immediatly afterwards.\n\nTo avoid to reload the todo list from the disk, sequencer_continue()\nwill be modified to accept a todo list, and if it is not null,\nread_populate_todo() will not be called.\n\nThis moves the part writing total_nr to a new function so it can be\ncalled by complete_action().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c | 16 +++++++++++-----\n 1 file changed, 11 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex ec9c3d4dc5..d66b80d49f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2343,6 +2343,16 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)\n \tsequencer_remove_state(&opts);\n }\n \n+static void todo_list_write_total_nr(struct todo_list *todo_list)\n+{\n+\tFILE *f = fopen_or_warn(rebase_path_msgtotal(), \"w\");\n+\n+\tif (f) {\n+\t\tfprintf(f, \"%d\\n\", todo_list->total_nr);\n+\t\tfclose(f);\n+\t}\n+}\n+\n static int read_populate_todo(struct repository *r,\n \t\t\t      struct todo_list *todo_list,\n \t\t\t      struct replay_opts *opts)\n@@ -2388,7 +2398,6 @@ static int read_populate_todo(struct repository *r,\n \n \tif (is_rebase_i(opts)) {\n \t\tstruct todo_list done = TODO_LIST_INIT;\n-\t\tFILE *f = fopen_or_warn(rebase_path_msgtotal(), \"w\");\n \n \t\tif (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&\n \t\t    !todo_list_parse_insn_buffer(r, done.buf.buf, &done))\n@@ -2400,10 +2409,7 @@ static int read_populate_todo(struct repository *r,\n \t\t\t+ count_commands(todo_list);\n \t\ttodo_list_release(&done);\n \n-\t\tif (f) {\n-\t\t\tfprintf(f, \"%d\\n\", todo_list->total_nr);\n-\t\t\tfclose(f);\n-\t\t}\n+\t\ttodo_list_write_total_nr(todo_list);\n \t}\n \n \treturn 0;\n-- \n2.22.0\n\n"},{"id":"379013","messageId":"20190717143918.7406-7-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 6/9] sequencer: add a parameter to sequencer_continue() to accept a todo list","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:15Z","receivedAt":"2019-07-17T14:42:06Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"As it is called by sequencer_continue() or after an exec command,\nread_populate_todo() is a great place to check for dropped commits in\nthe todo list, but complete_action() (a caller of sequencer_continue())\nalready does.  Double-checking would show the message twice.\n\nThis adds a parameter to sequencer_continue() to accept a todo list.  If\na valid list is provided, read_populate_todo() won't be called.\ncomplete_action() is modified to pass its todo list to\nsequencer_continue().\n\nThis also avoids reloading the todo list from the disk just after\nreleasing it.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n builtin/rebase.c |  2 +-\n builtin/revert.c |  2 +-\n sequencer.c      | 26 +++++++++++++++-----------\n sequencer.h      |  3 ++-\n 4 files changed, 19 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 89fc4b8153..205916ca11 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -393,7 +393,7 @@ static int run_rebase_interactive(struct rebase_options *opts,\n \tcase ACTION_CONTINUE: {\n \t\tstruct replay_opts replay_opts = get_replay_opts(opts);\n \n-\t\tret = sequencer_continue(the_repository, &replay_opts);\n+\t\tret = sequencer_continue(the_repository, &replay_opts, NULL);\n \t\tbreak;\n \t}\n \tcase ACTION_EDIT_TODO:\ndiff --git a/builtin/revert.c b/builtin/revert.c\nindex 4e71b2f2aa..45a5c6217d 100644\n--- a/builtin/revert.c\n+++ b/builtin/revert.c\n@@ -207,7 +207,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)\n \t\treturn ret;\n \t}\n \tif (cmd == 'c')\n-\t\treturn sequencer_continue(the_repository, opts);\n+\t\treturn sequencer_continue(the_repository, opts, NULL);\n \tif (cmd == 'a')\n \t\treturn sequencer_rollback(the_repository, opts);\n \treturn sequencer_pick_revisions(the_repository, opts);\ndiff --git a/sequencer.c b/sequencer.c\nindex d66b80d49f..3fb15ff8d9 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4147,9 +4147,11 @@ static int commit_staged_changes(struct repository *r,\n \treturn 0;\n }\n \n-int sequencer_continue(struct repository *r, struct replay_opts *opts)\n+int sequencer_continue(struct repository *r, struct replay_opts *opts,\n+\t\t       struct todo_list *p_todo_list)\n {\n-\tstruct todo_list todo_list = TODO_LIST_INIT;\n+\tstruct todo_list todo_list = TODO_LIST_INIT,\n+\t\t*ptr_todo = (p_todo_list) ? p_todo_list : &todo_list;\n \tint res;\n \n \tif (read_and_refresh_cache(r, opts))\n@@ -4158,13 +4160,13 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n \tif (read_populate_opts(opts))\n \t\treturn -1;\n \tif (is_rebase_i(opts)) {\n-\t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n+\t\tif (!p_todo_list && (res = read_populate_todo(r, &todo_list, opts)))\n \t\t\tgoto release_todo_list;\n-\t\tif (commit_staged_changes(r, opts, &todo_list))\n+\t\tif (commit_staged_changes(r, opts, ptr_todo))\n \t\t\treturn -1;\n \t} else if (!file_exists(get_todo_path(opts)))\n \t\treturn continue_single_pick(r);\n-\telse if ((res = read_populate_todo(r, &todo_list, opts)))\n+\telse if (!p_todo_list && (res = read_populate_todo(r, &todo_list, opts)))\n \t\tgoto release_todo_list;\n \n \tif (!is_rebase_i(opts)) {\n@@ -4179,18 +4181,18 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n \t\t\tres = error_dirty_index(r, opts);\n \t\t\tgoto release_todo_list;\n \t\t}\n-\t\ttodo_list.current++;\n+\t\tptr_todo->current++;\n \t} else if (file_exists(rebase_path_stopped_sha())) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tstruct object_id oid;\n \n \t\tif (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&\n \t\t    !get_oid_committish(buf.buf, &oid))\n-\t\t\trecord_in_rewritten(&oid, peek_command(&todo_list, 0));\n+\t\t\trecord_in_rewritten(&oid, peek_command(ptr_todo, 0));\n \t\tstrbuf_release(&buf);\n \t}\n \n-\tres = pick_commits(r, &todo_list, opts);\n+\tres = pick_commits(r, ptr_todo, opts);\n release_todo_list:\n \ttodo_list_release(&todo_list);\n \treturn res;\n@@ -5025,15 +5027,17 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n \t}\n \n-\ttodo_list_release(&new_todo);\n-\n \tif (checkout_onto(r, opts, onto_name, &oid, orig_head))\n \t\treturn -1;\n \n \tif (require_clean_work_tree(r, \"rebase\", \"\", 1, 1))\n \t\treturn -1;\n \n-\treturn sequencer_continue(r, opts);\n+\ttodo_list_write_total_nr(&new_todo);\n+\tres = sequencer_continue(r, opts, &new_todo);\n+\ttodo_list_release(&new_todo);\n+\n+\treturn res;\n }\n \n struct subject2item_entry {\ndiff --git a/sequencer.h b/sequencer.h\nindex 3d0b68c34c..858816f94a 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -127,7 +127,8 @@ const char *todo_item_get_arg(struct todo_list *todo_list,\n void sequencer_init_config(struct replay_opts *opts);\n int sequencer_pick_revisions(struct repository *repo,\n \t\t\t     struct replay_opts *opts);\n-int sequencer_continue(struct repository *repo, struct replay_opts *opts);\n+int sequencer_continue(struct repository *repo, struct replay_opts *opts,\n+\t\t       struct todo_list *p_todo_list);\n int sequencer_rollback(struct repository *repo, struct replay_opts *opts);\n int sequencer_remove_state(struct replay_opts *opts);\n \n-- \n2.22.0\n\n"},{"id":"379014","messageId":"20190717143918.7406-8-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 7/9] rebase-interactive: todo_list_check() also uses the done list","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:16Z","receivedAt":"2019-07-17T14:42:08Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"todo_list_check() works by checking if every commit in old_todo (the\nbackup list) is also present in new_todo (the todo list to check).\n\nThis works only when no commits have been picked (ie. right after the\ninitial edit).  In other cases, the backup list will contain one or\nseveral commits that have been applied and are no longer in the todo\nlist.  Let's take this todo list, for instance:\n\n\tpick 123abc An interesting change\n\tbreak\n\tpick cba321 A boring change\n\nAfter executing the `break' command, the backup list will contain the\noriginal todo list, while the todo list will only contain the last\n`pick' insn.  Even if commit 123abc has been picked up, in this case,\ntodo_list_check() will fail because it is not part of the todo list.\n\nThere is another list containing every command that have been executed:\nthe \"done\" list.  In our example, after the `break' command, the \"done\"\nlist will contain the first commit (123abc).  Comparing the backup list\nagainst the todo list and the done list will fix this problem.\n\nThis changes todo_list_check() to compare old_todo against new_todo\n_and_ the done list.  This should be useful to check the todo list when\nresuming a rebase or reloading the todo list after an `exec' command,\nbut not for `--edit-todo' (the todo list is copied before edition).\nBut, for the sake of safety, it is still done in the next commit.\n\nThis also adds a function, todo_list_check_against_backup(), to load the\ndone list and the backup list, and call todo_list_check().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c | 57 ++++++++++++++++++++++++++++++++++++++------\n rebase-interactive.h |  6 ++++-\n sequencer.c          |  4 ++--\n 3 files changed, 57 insertions(+), 10 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..c7dea85553 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -6,6 +6,8 @@\n #include \"commit-slab.h\"\n #include \"config.h\"\n \n+static GIT_PATH_FUNC(rebase_path_done, \"rebase-merge/done\")\n+\n enum missing_commit_check_level {\n \tMISSING_COMMIT_CHECK_IGNORE = 0,\n \tMISSING_COMMIT_CHECK_WARN,\n@@ -124,13 +126,27 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n }\n \n define_commit_slab(commit_seen, unsigned char);\n+\n+static void mark_commits_as_seen(struct commit_seen *commit_seen,\n+\t\t\t\t struct todo_list *list)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < list->nr; i++) {\n+\t\tstruct commit *commit = list->items[i].commit;\n+\t\tif (commit)\n+\t\t\t*commit_seen_at(commit_seen, commit) = 1;\n+\t}\n+}\n+\n /*\n  * Check if the user dropped some commits by mistake\n  * Behaviour determined by rebase.missingCommitsCheck.\n  * Check if there is an unrecognized command or a\n  * bad SHA-1 in a command.\n  */\n-int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n+int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo,\n+\t\t    struct todo_list *done)\n {\n \tenum missing_commit_check_level check_level = get_missing_commit_check_level();\n \tstruct strbuf missing = STRBUF_INIT;\n@@ -142,12 +158,11 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \tif (check_level == MISSING_COMMIT_CHECK_IGNORE)\n \t\tgoto leave_check;\n \n-\t/* Mark the commits in git-rebase-todo as seen */\n-\tfor (i = 0; i < new_todo->nr; i++) {\n-\t\tstruct commit *commit = new_todo->items[i].commit;\n-\t\tif (commit)\n-\t\t\t*commit_seen_at(&commit_seen, commit) = 1;\n-\t}\n+\t/* Mark the commits in git-rebase-todo and git-rebase-done as\n+\t   seen */\n+\tmark_commits_as_seen(&commit_seen, new_todo);\n+\tif (done)\n+\t\tmark_commits_as_seen(&commit_seen, done);\n \n \t/* Find commits in git-rebase-todo.backup yet unseen */\n \tfor (i = old_todo->nr - 1; i >= 0; i--) {\n@@ -187,3 +202,31 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n+\n+int todo_list_check_against_backup(struct repository *r,\n+\t\t\t\t   struct todo_list *todo_list)\n+{\n+\tstruct todo_list done = TODO_LIST_INIT, initial = TODO_LIST_INIT;\n+\tint res;\n+\n+\tif (strbuf_read_file(&done.buf, rebase_path_done(), 0) < 0 && errno != ENOENT)\n+\t\treturn error(_(\"could not read '%s'\"), rebase_path_done());\n+\n+\tstrbuf_read_file(&done.buf, rebase_path_done(), 0);\n+\ttodo_list_parse_insn_buffer(r, done.buf.buf, &done);\n+\n+\tif (strbuf_read_file(&initial.buf, rebase_path_todo_backup(), 0) < 0 &&\n+\t    errno != ENOENT) {\n+\t\ttodo_list_release(&done);\n+\t\treturn error(_(\"could not read '%s'\"), rebase_path_done());\n+\t}\n+\n+\ttodo_list_parse_insn_buffer(r, initial.buf.buf, &initial);\n+\n+\tres = todo_list_check(&initial, todo_list, &done);\n+\n+\ttodo_list_release(&done);\n+\ttodo_list_release(&initial);\n+\n+\treturn res;\n+}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..ac401cda25 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -11,6 +11,10 @@ void append_todo_help(unsigned keep_empty, int command_count,\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags);\n-int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n+\n+int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo,\n+\t\t    struct todo_list *done);\n+int todo_list_check_against_backup(struct repository *r,\n+\t\t\t\t   struct todo_list *todo_list);\n \n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex 3fb15ff8d9..0638c92f12 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4898,7 +4898,7 @@ int check_todo_list_from_file(struct repository *r)\n \tif (!res)\n \t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n \tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n+\t\tres = todo_list_check(&old_todo, &new_todo, NULL);\n \tif (res)\n \t\tfprintf(stderr, _(edit_todo_list_advice));\n out:\n@@ -5008,7 +5008,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t}\n \n \tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n-\t    todo_list_check(todo_list, &new_todo)) {\n+\t    todo_list_check(todo_list, &new_todo, NULL)) {\n \t\tfprintf(stderr, _(edit_todo_list_advice));\n \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n \t\ttodo_list_release(&new_todo);\n-- \n2.22.0\n\n"},{"id":"379015","messageId":"20190717143918.7406-9-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 8/9] rebase-interactive: warn if commit is dropped with --edit-todo","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:17Z","receivedAt":"2019-07-17T14:42:10Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This adds the ability for --edit-todo to check if commits were dropped\nby the user using todo_list_check_against_backup().\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex c7dea85553..c346d4ced4 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -95,6 +95,7 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n {\n \tconst char *todo_file = rebase_path_todo();\n \tunsigned initial = shortrevisions && shortonto;\n+\tint res = 0;\n \n \t/* If the user is editing the todo list, we first try to parse\n \t * it.  If there is an error, we do not return, because the user\n@@ -119,10 +120,13 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \n \t/* For the initial edit, the todo list gets parsed in\n \t * complete_action(). */\n-\tif (!initial)\n-\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n+\tif (!initial) {\n+\t\tres = todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n+\t\tif (!res)\n+\t\t\tres = todo_list_check_against_backup(r, new_todo);\n+\t}\n \n-\treturn 0;\n+\treturn res;\n }\n \n define_commit_slab(commit_seen, unsigned char);\n-- \n2.22.0\n\n"},{"id":"379016","messageId":"20190717143918.7406-10-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH 9/9] sequencer: have read_populate_todo() check for dropped commits","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-17T14:39:18Z","receivedAt":"2019-07-17T14:42:11Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This adds the ability to check if commits were dropped when resuming a\nrebase (with `--continue') or when reloading the todo list after an\n`exec' command.\n\nTests added previously should work now.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n sequencer.c                   | 5 ++++-\n t/t3404-rebase-interactive.sh | 4 ++--\n t/t3429-rebase-edit-todo.sh   | 4 ++--\n 3 files changed, 8 insertions(+), 5 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 0638c92f12..d2c4459e7c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2360,7 +2360,6 @@ static int read_populate_todo(struct repository *r,\n \tstruct stat st;\n \tconst char *todo_file = get_todo_path(opts);\n \tint res;\n-\n \tstrbuf_reset(&todo_list->buf);\n \tif (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)\n \t\treturn -1;\n@@ -2378,6 +2377,10 @@ static int read_populate_todo(struct repository *r,\n \t\treturn error(_(\"unusable instruction sheet: '%s'\"), todo_file);\n \t}\n \n+\tres = todo_list_check_against_backup(r, todo_list);\n+\tif (res)\n+\t\treturn -1;\n+\n \tif (!todo_list->nr &&\n \t    (!is_rebase_i(opts) || !file_exists(rebase_path_done())))\n \t\treturn error(_(\"no commits parsed.\"));\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex f5c0a8d2bb..090a496bcc 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1373,7 +1373,7 @@ EOF\n \n tail -n 8 <expect >expect.2\n \n-test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n \ttest_config rebase.missingCommitsCheck warn &&\n \trebase_setup_and_clean missing-commit &&\n \tset_fake_editor &&\n@@ -1405,7 +1405,7 @@ EOF\n \n tail -n 9 <expect >expect.2\n \n-test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n \ttest_config rebase.missingCommitsCheck error &&\n \trebase_setup_and_clean missing-commit &&\n \tset_fake_editor &&\ndiff --git a/t/t3429-rebase-edit-todo.sh b/t/t3429-rebase-edit-todo.sh\nindex 2bb9fb65fa..79cd5657b3 100755\n--- a/t/t3429-rebase-edit-todo.sh\n+++ b/t/t3429-rebase-edit-todo.sh\n@@ -50,7 +50,7 @@ Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n The possible behaviours are: ignore, warn, error.\n EOF\n \n-test_expect_failure 'rebase exec respects rebase.missingCommitsCheck = warn' '\n+test_expect_success 'rebase exec respects rebase.missingCommitsCheck = warn' '\n \ttest_config rebase.missingCommitsCheck warn &&\n \tgit reset --hard HEAD@{2} &&\n \tgit rebase HEAD~2 --keep-empty -x \"echo >$todo\" 2>actual.2 &&\n@@ -59,7 +59,7 @@ test_expect_failure 'rebase exec respects rebase.missingCommitsCheck = warn' '\n \ttest 5 = $(git cat-file commit HEAD | sed -ne \\$p)\n '\n \n-test_expect_failure 'rebase exec respects rebase.missingCommitsCheck = error' '\n+test_expect_success 'rebase exec respects rebase.missingCommitsCheck = error' '\n \ttest_config rebase.missingCommitsCheck error &&\n \tgit reset --hard HEAD@{2} &&\n \ttest_must_fail git rebase HEAD~2 --keep-empty -x \"echo >$todo\" 2>actual.2 &&\n-- \n2.22.0\n\n"},{"id":"379066","messageId":"xmqqwogfxkhx.fsf@gitster-ct.c.googlers.com","threadId":"51492","inReplyTo":"20190717143918.7406-2-alban.gruin@gmail.com","subject":"Re: [RFC PATCH 1/9] t3404: demonstrate that --edit-todo does not check for dropped commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-18T18:31:22Z","receivedAt":"2019-07-18T18:31:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> When set to \"warn\" or \"error\", `rebase.missingCommitCheck' would make\n> rebase -i warn if the user removed commits from the todo list to prevent\n> mistakes.  Unfortunately, rebase --edit-todo and rebase --continue don't\n> take it into account.\n>\n> This adds three tests to t3404 to demonstrate this.  The first one is\n> not broken, as when `rebase.missingCommitsCheck' is not set, nothing in\n> particular must be done towards dropped commits.  The two others are\n> broken, demonstrating the problem.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  t/t3404-rebase-interactive.sh | 82 +++++++++++++++++++++++++++++++++++\n>  1 file changed, 82 insertions(+)\n>\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 461dd539ff..f5c0a8d2bb 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1345,6 +1345,88 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n>  \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>  '\n>  \n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n> +\ttest_config rebase.missingCommitsCheck ignore &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n> +\t\tgit rebase -i --root >/dev/null 2>stderr &&\n\nDo you need to capture into stderr?  Nobody seems to use it.\n\n> +\tFAKE_LINES=\"1 2 4\" git rebase --edit-todo &&\n> +\tgit rebase --continue 2>actual &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n> +cat >expect <<EOF\n> +error: invalid line 5: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> +Warning: some commits may have been dropped accidentally.\n> +Dropped commits (newer to older):\n> + - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> +To avoid this message, use \"drop\" to explicitly remove a commit.\n> +\n> +Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n> +The possible behaviours are: ignore, warn, error.\n> +\n> +EOF\n> +\n> +tail -n 8 <expect >expect.2\n\nHaving this outside the test_expect_success block that uses the file\nis bad.  You may have mimicked other tests in the same script, but\nthat is not a good excuse to make things worse.  Just make sure\nthese new stuff follow the best-current-practice pattern without\ntouching the existing bad examples (and later fix them up after the\ndust settles, but don't let it distract you from the theme these\npatches are addressing).\n\n> +\n> +test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n> +\ttest_config rebase.missingCommitsCheck warn &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\ttest_must_fail env FAKE_LINES=\"1 2 3 4 bad 5\" \\\n> +\t\tgit rebase -i --root >/dev/null 2>stderr &&\n\nDitto.\n\n> +\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n> +\ttest_i18ncmp expect actual &&\n\nSo, after \"--edit-todo\", you are supposed to get an error and a warning,\nbut ...\n\n> +\tgit rebase --continue 2>actual.2 &&\n> +\thead -n 8 <actual.2 >actual &&\n> +\ttest_i18ncmp expect.2 actual &&\n\n... after \"--continue\", you do not get any error, as you removed\n'bad' from the input, but you still get a warning, followed by a\nreport of the fact that a commit has been dropped.  OK.\n\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual.2\n> +'\n> +\n> +cat >expect <<EOF\n> +error: invalid line 3: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)\n> +Warning: some commits may have been dropped accidentally.\n> +Dropped commits (newer to older):\n> + - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> + - $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)\n> +To avoid this message, use \"drop\" to explicitly remove a commit.\n> +\n> +Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n> +The possible behaviours are: ignore, warn, error.\n> +\n> +EOF\n> +\n> +tail -n 9 <expect >expect.2\n> +\n> +test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n> +\ttest_config rebase.missingCommitsCheck error &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n> +\t\tgit rebase -i --root >/dev/null 2>stderr &&\n> +\ttest_must_fail env FAKE_LINES=\"1 2 4\" \\\n> +\t\tgit rebase --edit-todo 2>actual &&\n> +\ttest_i18ncmp expect actual &&\n> +\ttest_must_fail git rebase --continue 2>actual &&\n\nOK, and this one fails as the configuration is set to 'error'.\n\n> +\ttest_i18ncmp expect.2 actual &&\n> +\tcp .git/rebase-merge/git-rebase-todo.backup \\\n> +\t\t.git/rebase-merge/git-rebase-todo &&\n\nWhy?  Who uses this copy?\n\n> +\tFAKE_LINES=\"1 2 drop 3 4 drop 5\" \\\n> +\t\tgit rebase --edit-todo &&\n> +\tgit rebase --continue 2>actual &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n>  test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n>  \trebase_setup_and_clean abbrevcmd &&\n>  \ttest_commit \"first\" file1.txt \"first line\" first &&\n"},{"id":"379074","messageId":"xmqqsgr3xgr6.fsf@gitster-ct.c.googlers.com","threadId":"51492","inReplyTo":"20190717143918.7406-4-alban.gruin@gmail.com","subject":"Re: [RFC PATCH 3/9] sequencer: update `total_nr' when adding an item to a todo list","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-18T19:52:13Z","receivedAt":"2019-07-18T19:52:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> `total_nr' is the total amount of items, done and toto, that are in a\n\n\"amount\" -> \"number\" perhaps.  Also s/toto/todo/ perhaps but I am\nnot sure what you wanted to say here, so...\n\n> todo list.  But unlike `nr', it was not updated when an item was\n> appended to the list.\n\nGood finding.\n\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  sequencer.c | 1 +\n>  1 file changed, 1 insertion(+)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index cf262701e8..e61ae75451 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -2070,6 +2070,7 @@ void todo_list_release(struct todo_list *todo_list)\n>  static struct todo_item *append_new_todo(struct todo_list *todo_list)\n>  {\n>  \tALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);\n> +\t++todo_list->total_nr;\n\nWhen we do not use the value in an expression, we prefer post\nincrement, not pre increment.\n\n>  \treturn todo_list->items + todo_list->nr++;\n>  }\n"},{"id":"379075","messageId":"xmqqo91rxgm7.fsf@gitster-ct.c.googlers.com","threadId":"51492","inReplyTo":"20190717143918.7406-5-alban.gruin@gmail.com","subject":"Re: [RFC PATCH 4/9] sequencer: update `done_nr' when skipping commands in a todo list","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-18T19:55:12Z","receivedAt":"2019-07-18T19:55:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> In a todo list, `done_nr' is the amount of commands that were executed\n> or skipped, but skip_unnecessary_picks() did not update it.\n\nOK.  Together with 3/9 and this one, any increment of total_nr and\ndone_nr in the existing code is not removed; does it mean that\nnobody actually cares what these fields contain?  IOW, there is no\ncode that says \"if (list->total_nr <= i) { we are done; }\" etc.?\n\nOr are these fields used later, but somehow the lack of increment in\nthe places touched by 3/9 and 4/9 is compensated?\n\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  sequencer.c | 1 +\n>  1 file changed, 1 insertion(+)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index e61ae75451..ec9c3d4dc5 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4939,6 +4939,7 @@ static int skip_unnecessary_picks(struct repository *r,\n>  \t\tMOVE_ARRAY(todo_list->items, todo_list->items + i, todo_list->nr - i);\n>  \t\ttodo_list->nr -= i;\n>  \t\ttodo_list->current = 0;\n> +\t\ttodo_list->done_nr += i;\n>  \n>  \t\tif (is_fixup(peek_command(todo_list, 0)))\n>  \t\t\trecord_in_rewritten(base_oid, peek_command(todo_list, 0));\n"},{"id":"379076","messageId":"xmqqk1cfxg6d.fsf@gitster-ct.c.googlers.com","threadId":"51492","inReplyTo":"20190717143918.7406-6-alban.gruin@gmail.com","subject":"Re: [RFC PATCH 5/9] sequencer: move the code writing total_nr on the disk to a new function","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-18T20:04:42Z","receivedAt":"2019-07-18T20:04:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> The total amount of commands can be used to show the progression of the\n> rebasing in a shell.  This number is written to the disk by\n> read_populate_todo() when the todo list is loaded from\n> sequencer_continue() or pick_commits(), but not by complete_action(),\n> which releases its todo list before calling sequencer_continue(), which\n> reloads it immediatly afterwards.\n>\n> To avoid to reload the todo list from the disk, sequencer_continue()\n> will be modified to accept a todo list, and if it is not null,\n> read_populate_todo() will not be called.\n\nThat may be good as a plan, but readers who are reading this step\nare left puzzled as the changes so far do not seem to have much to\ndo with that change.  Perhaps this paragraph belongs to the step\nthat actually makes that modification?\n\n> This moves the part writing total_nr to a new function so it can be\n> called by complete_action().\n\nSo without 3/9 and 4/9, we have been simply writing out wrong\nnumber?  Good.\n\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  sequencer.c | 16 +++++++++++-----\n>  1 file changed, 11 insertions(+), 5 deletions(-)\n>\n> diff --git a/sequencer.c b/sequencer.c\n> index ec9c3d4dc5..d66b80d49f 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -2343,6 +2343,16 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)\n>  \tsequencer_remove_state(&opts);\n>  }\n>  \n> +static void todo_list_write_total_nr(struct todo_list *todo_list)\n> +{\n> +\tFILE *f = fopen_or_warn(rebase_path_msgtotal(), \"w\");\n> +\n> +\tif (f) {\n> +\t\tfprintf(f, \"%d\\n\", todo_list->total_nr);\n> +\t\tfclose(f);\n> +\t}\n> +}\n> +\n>  static int read_populate_todo(struct repository *r,\n>  \t\t\t      struct todo_list *todo_list,\n>  \t\t\t      struct replay_opts *opts)\n> @@ -2388,7 +2398,6 @@ static int read_populate_todo(struct repository *r,\n>  \n>  \tif (is_rebase_i(opts)) {\n>  \t\tstruct todo_list done = TODO_LIST_INIT;\n> -\t\tFILE *f = fopen_or_warn(rebase_path_msgtotal(), \"w\");\n>  \n>  \t\tif (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&\n>  \t\t    !todo_list_parse_insn_buffer(r, done.buf.buf, &done))\n> @@ -2400,10 +2409,7 @@ static int read_populate_todo(struct repository *r,\n>  \t\t\t+ count_commands(todo_list);\n>  \t\ttodo_list_release(&done);\n>  \n> -\t\tif (f) {\n> -\t\t\tfprintf(f, \"%d\\n\", todo_list->total_nr);\n> -\t\t\tfclose(f);\n> -\t\t}\n> +\t\ttodo_list_write_total_nr(todo_list);\n>  \t}\n>  \n>  \treturn 0;\n"},{"id":"379106","messageId":"2836634.HGSAIcGHtf@andromeda","threadId":"51492","inReplyTo":"xmqqwogfxkhx.fsf@gitster-ct.c.googlers.com","subject":"Re: [RFC PATCH 1/9] t3404: demonstrate that --edit-todo does not check for dropped commits","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-19T18:12:27Z","receivedAt":"2019-07-19T18:12:38Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nLe 18/07/2019 à 20:31, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> > When set to \"warn\" or \"error\", `rebase.missingCommitCheck' would make\n> > rebase -i warn if the user removed commits from the todo list to prevent\n> > mistakes.  Unfortunately, rebase --edit-todo and rebase --continue don't\n> > take it into account.\n> > \n> > This adds three tests to t3404 to demonstrate this.  The first one is\n> > not broken, as when `rebase.missingCommitsCheck' is not set, nothing in\n> > particular must be done towards dropped commits.  The two others are\n> > broken, demonstrating the problem.\n> > \n> > Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> > ---\n> > \n> >  t/t3404-rebase-interactive.sh | 82 +++++++++++++++++++++++++++++++++++\n> >  1 file changed, 82 insertions(+)\n> > \n> > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> > index 461dd539ff..f5c0a8d2bb 100755\n> > --- a/t/t3404-rebase-interactive.sh\n> > +++ b/t/t3404-rebase-interactive.sh\n> > @@ -1345,6 +1345,88 @@ test_expect_success 'rebase -i respects\n> > rebase.missingCommitsCheck = error' '> \n> >  \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n> >  \n> >  '\n> > \n> > +test_expect_success 'rebase --edit-todo respects\n> > rebase.missingCommitsCheck = ignore' ' +\ttest_config\n> > rebase.missingCommitsCheck ignore &&\n> > +\trebase_setup_and_clean missing-commit &&\n> > +\tset_fake_editor &&\n> > +\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n> > +\t\tgit rebase -i --root >/dev/null 2>stderr &&\n> \n> Do you need to capture into stderr?  Nobody seems to use it.\n> \n\nNo.  I’m changing this.\n\n> > +\tFAKE_LINES=\"1 2 4\" git rebase --edit-todo &&\n> > +\tgit rebase --continue 2>actual &&\n> > +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> > +\ttest_i18ngrep \\\n> > +\t\t\"Successfully rebased and updated refs/heads/missing-\ncommit\" \\\n> > +\t\tactual\n> > +'\n> > +\n> > +cat >expect <<EOF\n> > +error: invalid line 5: badcmd $(git rev-list --pretty=oneline\n> > --abbrev-commit -1 master) +Warning: some commits may have been dropped\n> > accidentally.\n> > +Dropped commits (newer to older):\n> > + - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> > +To avoid this message, use \"drop\" to explicitly remove a commit.\n> > +\n> > +Use 'git config rebase.missingCommitsCheck' to change the level of\n> > warnings. +The possible behaviours are: ignore, warn, error.\n> > +\n> > +EOF\n> > +\n> > +tail -n 8 <expect >expect.2\n> \n> Having this outside the test_expect_success block that uses the file\n> is bad.  You may have mimicked other tests in the same script, but\n> that is not a good excuse to make things worse.  Just make sure\n> these new stuff follow the best-current-practice pattern without\n> touching the existing bad examples (and later fix them up after the\n> dust settles, but don't let it distract you from the theme these\n> patches are addressing).\n> \n\nOkay.\n\n> > +\n> > +test_expect_failure 'rebase --edit-todo respects\n> > rebase.missingCommitsCheck = warn' ' +\ttest_config\n> > rebase.missingCommitsCheck warn &&\n> > +\trebase_setup_and_clean missing-commit &&\n> > +\tset_fake_editor &&\n> > +\ttest_must_fail env FAKE_LINES=\"1 2 3 4 bad 5\" \\\n> > +\t\tgit rebase -i --root >/dev/null 2>stderr &&\n> \n> Ditto.\n> \n> > +\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n> > +\ttest_i18ncmp expect actual &&\n> \n> So, after \"--edit-todo\", you are supposed to get an error and a warning,\n> but ...\n> \n> > +\tgit rebase --continue 2>actual.2 &&\n> > +\thead -n 8 <actual.2 >actual &&\n> > +\ttest_i18ncmp expect.2 actual &&\n> \n> ... after \"--continue\", you do not get any error, as you removed\n> 'bad' from the input, but you still get a warning, followed by a\n> report of the fact that a commit has been dropped.  OK.\n> \n> > +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> > +\ttest_i18ngrep \\\n> > +\t\t\"Successfully rebased and updated refs/heads/missing-\ncommit\" \\\n> > +\t\tactual.2\n> > +'\n> > +\n> > +cat >expect <<EOF\n> > +error: invalid line 3: badcmd $(git rev-list --pretty=oneline\n> > --abbrev-commit -1 master~2) +Warning: some commits may have been dropped\n> > accidentally.\n> > +Dropped commits (newer to older):\n> > + - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> > + - $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)\n> > +To avoid this message, use \"drop\" to explicitly remove a commit.\n> > +\n> > +Use 'git config rebase.missingCommitsCheck' to change the level of\n> > warnings. +The possible behaviours are: ignore, warn, error.\n> > +\n> > +EOF\n> > +\n> > +tail -n 9 <expect >expect.2\n> > +\n> > +test_expect_failure 'rebase --edit-todo respects\n> > rebase.missingCommitsCheck = error' ' +\ttest_config\n> > rebase.missingCommitsCheck error &&\n> > +\trebase_setup_and_clean missing-commit &&\n> > +\tset_fake_editor &&\n> > +\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n> > +\t\tgit rebase -i --root >/dev/null 2>stderr &&\n> > +\ttest_must_fail env FAKE_LINES=\"1 2 4\" \\\n> > +\t\tgit rebase --edit-todo 2>actual &&\n> > +\ttest_i18ncmp expect actual &&\n> > +\ttest_must_fail git rebase --continue 2>actual &&\n> \n> OK, and this one fails as the configuration is set to 'error'.\n> \n> > +\ttest_i18ncmp expect.2 actual &&\n> > +\tcp .git/rebase-merge/git-rebase-todo.backup \\\n> > +\t\t.git/rebase-merge/git-rebase-todo &&\n> \n> Why?  Who uses this copy?\n> \n\nThe same technique is used in \"rebase -i respects rebase.missingCommitsCheck = \nerror\".\n\n> > +\tFAKE_LINES=\"1 2 drop 3 4 drop 5\" \\\n> > +\t\tgit rebase --edit-todo &&\n> > +\tgit rebase --continue 2>actual &&\n> > +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> > +\ttest_i18ngrep \\\n> > +\t\t\"Successfully rebased and updated refs/heads/missing-\ncommit\" \\\n> > +\t\tactual\n> > +'\n> > +\n> > \n> >  test_expect_success 'respects rebase.abbreviateCommands with fixup,\n> >  squash and exec' '>  \n> >  \trebase_setup_and_clean abbrevcmd &&\n> >  \ttest_commit \"first\" file1.txt \"first line\" first &&\n\n\n\n"},{"id":"379107","messageId":"7495472.JabEqZW8b6@andromeda","threadId":"51492","inReplyTo":"xmqqsgr3xgr6.fsf@gitster-ct.c.googlers.com","subject":"Re: [RFC PATCH 3/9] sequencer: update `total_nr' when adding an item to a todo list","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-19T18:12:48Z","receivedAt":"2019-07-19T18:13:12Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi,\n\nLe 18/07/2019 à 21:52, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> > `total_nr' is the total amount of items, done and toto, that are in a\n> \n> \"amount\" -> \"number\" perhaps.  Also s/toto/todo/ perhaps but I am\n> not sure what you wanted to say here, so...\n> \n\n`total_nr' is the number of commands, whether they have been executed or not.  \nIt’s used to show the progression of the rebase.\n\n> > todo list.  But unlike `nr', it was not updated when an item was\n> > appended to the list.\n> \n> Good finding.\n> \n> > Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> > ---\n> > \n> >  sequencer.c | 1 +\n> >  1 file changed, 1 insertion(+)\n> > \n> > diff --git a/sequencer.c b/sequencer.c\n> > index cf262701e8..e61ae75451 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -2070,6 +2070,7 @@ void todo_list_release(struct todo_list *todo_list)\n> > \n> >  static struct todo_item *append_new_todo(struct todo_list *todo_list)\n> >  {\n> >  \n> >  \tALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);\n> > \n> > +\t++todo_list->total_nr;\n> \n> When we do not use the value in an expression, we prefer post\n> increment, not pre increment.\n> \n> >  \treturn todo_list->items + todo_list->nr++;\n> >  \n> >  }\n\n\n\n\n"},{"id":"379108","messageId":"1732521.CJWHkCQAay@andromeda","threadId":"51492","inReplyTo":"xmqqo91rxgm7.fsf@gitster-ct.c.googlers.com","subject":"Re: [RFC PATCH 4/9] sequencer: update `done_nr' when skipping commands in a todo list","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-19T18:13:44Z","receivedAt":"2019-07-19T18:13:57Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi,\n\nLe 18/07/2019 à 21:55, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> > In a todo list, `done_nr' is the amount of commands that were executed\n> > or skipped, but skip_unnecessary_picks() did not update it.\n> \n> OK.  Together with 3/9 and this one, any increment of total_nr and\n> done_nr in the existing code is not removed; does it mean that\n> nobody actually cares what these fields contain?  IOW, there is no\n> code that says \"if (list->total_nr <= i) { we are done; }\" etc.?\n> \n> Or are these fields used later, but somehow the lack of increment in\n> the places touched by 3/9 and 4/9 is compensated?\n> \n\n`total_nr' is not used for this, because it’s not necessarily the number of \nitems in the todo list.  That’s the role of `nr'.  So the comparison is more \nlike \"if (list->nr <= i) { we are done; }\".\n\nSame think for `done_nr'.  Each time a command is executed, git prints \n\"Rebasing ($done_nr/$total_nr)\".  These two variables are written to the disk, \nand might be used by a shell prompt (eg. git-prompt.sh, oh my zsh…)\n\nAnd this is actually how I found this.  Originally, I wrote what became 5/9 \nand 6/9, without touching to `done_nr' and `total_nr'.  All rebase tests \n(t34??*) passed, but t9903.15 (\"prompt - rebase merge\") failed, because the \nvalue was incorrect.\n\nThe reason is that, before I changed sequencer_continue() in 6/9, it called \nanother function, read_populate_todo(), which would recompute `done_nr' and \n`total_nr', then write them to the disk.  With my changes, these values would \nnot have been updated after adding `exec' commands or skipping picks in \ncomplete_action(), so the numbers written to the disk were incorrect.\n\ntl;dr: it does not impact how the rebase works, but it might impact the \nmessages printed while rebasing or shell prompts.\n\n> > Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> > ---\n> > \n> >  sequencer.c | 1 +\n> >  1 file changed, 1 insertion(+)\n> > \n> > diff --git a/sequencer.c b/sequencer.c\n> > index e61ae75451..ec9c3d4dc5 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -4939,6 +4939,7 @@ static int skip_unnecessary_picks(struct repository\n> > *r,> \n> >  \t\tMOVE_ARRAY(todo_list->items, todo_list->items + i, \ntodo_list->nr - i);\n> >  \t\ttodo_list->nr -= i;\n> >  \t\ttodo_list->current = 0;\n> > \n> > +\t\ttodo_list->done_nr += i;\n> > \n> >  \t\tif (is_fixup(peek_command(todo_list, 0)))\n> >  \t\t\n> >  \t\t\trecord_in_rewritten(base_oid, \npeek_command(todo_list, 0));\n\n\n\n\n"},{"id":"379109","messageId":"6145932.27YfOFK3Rs@andromeda","threadId":"51492","inReplyTo":"xmqqk1cfxg6d.fsf@gitster-ct.c.googlers.com","subject":"Re: [RFC PATCH 5/9] sequencer: move the code writing total_nr on the disk to a new function","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-19T18:14:02Z","receivedAt":"2019-07-19T18:14:14Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi,\n\nLe 18/07/2019 à 22:04, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> > The total amount of commands can be used to show the progression of the\n> > rebasing in a shell.  This number is written to the disk by\n> > read_populate_todo() when the todo list is loaded from\n> > sequencer_continue() or pick_commits(), but not by complete_action(),\n> > which releases its todo list before calling sequencer_continue(), which\n> > reloads it immediatly afterwards.\n> > \n> > To avoid to reload the todo list from the disk, sequencer_continue()\n> > will be modified to accept a todo list, and if it is not null,\n> > read_populate_todo() will not be called.\n> \n> That may be good as a plan, but readers who are reading this step\n> are left puzzled as the changes so far do not seem to have much to\n> do with that change.  Perhaps this paragraph belongs to the step\n> that actually makes that modification?\n> \n> > This moves the part writing total_nr to a new function so it can be\n> > called by complete_action().\n> \n> So without 3/9 and 4/9, we have been simply writing out wrong\n> number?  Good.\n> \n\nNot here, the numbers computed in read_populate_todo() are correct.  But they \nmay be incorrect in complete_action(), before calling sequencer_continue().  \nThis was not a big deal as the todo list was released before and \nsequencer_continue() re-read it, but it would have became a problem with 6/9.\n\n> > Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> > ---\n> > \n> >  sequencer.c | 16 +++++++++++-----\n> >  1 file changed, 11 insertions(+), 5 deletions(-)\n> > \n> > diff --git a/sequencer.c b/sequencer.c\n> > index ec9c3d4dc5..d66b80d49f 100644\n> > --- a/sequencer.c\n> > +++ b/sequencer.c\n> > @@ -2343,6 +2343,16 @@ void sequencer_post_commit_cleanup(struct\n> > repository *r, int verbose)> \n> >  \tsequencer_remove_state(&opts);\n> >  \n> >  }\n> > \n> > +static void todo_list_write_total_nr(struct todo_list *todo_list)\n> > +{\n> > +\tFILE *f = fopen_or_warn(rebase_path_msgtotal(), \"w\");\n> > +\n> > +\tif (f) {\n> > +\t\tfprintf(f, \"%d\\n\", todo_list->total_nr);\n> > +\t\tfclose(f);\n> > +\t}\n> > +}\n> > +\n> > \n> >  static int read_populate_todo(struct repository *r,\n> >  \n> >  \t\t\t      struct todo_list *todo_list,\n> >  \t\t\t      struct replay_opts *opts)\n> > \n> > @@ -2388,7 +2398,6 @@ static int read_populate_todo(struct repository *r,\n> > \n> >  \tif (is_rebase_i(opts)) {\n> >  \t\n> >  \t\tstruct todo_list done = TODO_LIST_INIT;\n> > \n> > -\t\tFILE *f = fopen_or_warn(rebase_path_msgtotal(), \"w\");\n> > \n> >  \t\tif (strbuf_read_file(&done.buf, rebase_path_done(), 0) \n> 0 &&\n> >  \t\t\n> >  \t\t    !todo_list_parse_insn_buffer(r, done.buf.buf, \n&done))\n> > \n> > @@ -2400,10 +2409,7 @@ static int read_populate_todo(struct repository *r,\n> > \n> >  \t\t\t+ count_commands(todo_list);\n> >  \t\t\n> >  \t\ttodo_list_release(&done);\n> > \n> > -\t\tif (f) {\n> > -\t\t\tfprintf(f, \"%d\\n\", todo_list->total_nr);\n> > -\t\t\tfclose(f);\n> > -\t\t}\n> > +\t\ttodo_list_write_total_nr(todo_list);\n> > \n> >  \t}\n> >  \t\n> >  \treturn 0;\n\n\n\n\n"},{"id":"379115","messageId":"xmqqimrxvm4e.fsf@gitster-ct.c.googlers.com","threadId":"51492","inReplyTo":"2836634.HGSAIcGHtf@andromeda","subject":"Re: [RFC PATCH 1/9] t3404: demonstrate that --edit-todo does not check for dropped commits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-19T19:51:29Z","receivedAt":"2019-07-19T19:51:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n>> > +\ttest_i18ncmp expect.2 actual &&\n>> > +\tcp .git/rebase-merge/git-rebase-todo.backup \\\n>> > +\t\t.git/rebase-merge/git-rebase-todo &&\n>> \n>> Why?  Who uses this copy?\n>> \n>\n> The same technique is used in \"rebase -i respects rebase.missingCommitsCheck = \n> error\".\n\nAh, I misread the code.  I thought you were making a copy in .backup\nthat nobody looks at after the copy is made.\n"},{"id":"379201","messageId":"ce6bdffd-0a04-803c-4a1e-0959342db01f@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"Re: [RFC PATCH 0/9] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo' and co.","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-07-24T13:29:01Z","receivedAt":"2019-07-24T13:29:10Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nThanks for working on this, it's great to see you back on the list and I \nthink it would be a useful addition to rebase. Unfortunately I'm not \nsure about this implementation though (although the early bug fix \npatches are useful in their own right)\n\nOn 17/07/2019 15:39, Alban Gruin wrote:\n> To prevent mistakes when editing a branch, rebase features a knob,\n> rebase.missingCommitsCheck, to warn the user if a commit was dropped.\n> Unfortunately, this check is only effective for the initial edit, which\n> means that if you edit the todo list at a later point of the rebase and\n> dropped a commit, no warnings or errors would be issued.\n> \n> This adds the ability to check if commits were dropped when resuming a\n> rebase (with `rebase --continue'), when editing the todo list (with\n> `rebase --edit-todo'), or when reloading the todo list after an `exec'\n> command.\n\nI'm not sure if we really need to check the todo list when continuing or \nafter an exec command. The official way to edit the todo list is to run \n'git rebase --edit-todo' and I'm not sure if we support scripts writing \nto .git/rebase-merge/git-rebase-todo directly. If we only support the \ncheck after --edit-todo then I think the implementation can be \nsimplified as we can just write a copy of the file before it is edited \nand don't need to check .git/rebase-merge/done. Additionally that would \ncatch commits that are added by the user and then deleted in a later \nedit. They wont be in the original list so I don't think this series \nwill detect their deletion.\n\nAt the extreme I have a script around rebase that runs 'rebase -i HEAD' \nand then fills in the todo list with a fake editor that adds 'reset ...' \nas the first line to set the starting point of the rebase. I think \ndscho's garden-shears script does something similar. Under the proposed \nscheme if I subsequently edit the todo list it will not catch any \ndeleted commits as the original list is empty.\n\nBest Wishes\n\nPhillip\n\n> The idea to extend this feature was suggested to me more than a year ago\n> by Phillip Wood, if I'm not mistaken.  I postponed this until four month\n> ago, when ag/sequencer-reduce-rewriting-todo finally hit master, but I\n> had to stop because of other obligations.  I could go back to work one\n> month ago, when I did the bulk of this series, but I lacked time to\n> polish it, so it waited a bit more.  Now, I think it is in a good shape\n> to be sent, although it is still RFC-quality to me.  The advertised\n> functionality should work well, but perhaps there is some flaws I\n> missed.\n> \n> The first two patches are new tests, demonstrating that after the\n> initial edit, the check is not done.  The next four are what could be\n> qualified as omissions from ag/sequencer-reduce-rewriting-todo, but they\n> are quite important (IMHO) for the rest of the series.  The last three\n> actually extend rebase.missingCommitsCheck.\n> \n> This is based on master (9d418600f4, \"The fifth batch\").\n> \n> The tip of this series is tagged as \"edit-todo-drop-rfc\" in\n> https://github.com/agrn/git.\n> \n> Alban Gruin (9):\n>    t3404: demonstrate that --edit-todo does not check for dropped commits\n>    t3429: demonstrate that rebase exec does not check for dropped commits\n>    sequencer: update `total_nr' when adding an item to a todo list\n>    sequencer: update `done_nr' when skipping commands in a todo list\n>    sequencer: move the code writing total_nr on the disk to a new\n>      function\n>    sequencer: add a parameter to sequencer_continue() to accept a todo\n>      list\n>    rebase-interactive: todo_list_check() also uses the done list\n>    rebase-interactive: warn if commit is dropped with --edit-todo\n>    sequencer: have read_populate_todo() check for dropped commits\n> \n>   builtin/rebase.c              |  2 +-\n>   builtin/revert.c              |  2 +-\n>   rebase-interactive.c          | 67 +++++++++++++++++++++++-----\n>   rebase-interactive.h          |  6 ++-\n>   sequencer.c                   | 53 ++++++++++++++--------\n>   sequencer.h                   |  3 +-\n>   t/t3404-rebase-interactive.sh | 82 +++++++++++++++++++++++++++++++++++\n>   t/t3429-rebase-edit-todo.sh   | 44 ++++++++++++++++++-\n>   8 files changed, 224 insertions(+), 35 deletions(-)\n> \n"},{"id":"379241","messageId":"nycvar.QRO.7.76.6.1907251058120.21907@tvgsbejvaqbjf.bet","threadId":"51492","inReplyTo":"ce6bdffd-0a04-803c-4a1e-0959342db01f@gmail.com","subject":"Re: [RFC PATCH 0/9] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo' and co.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-07-25T09:01:06Z","receivedAt":"2019-07-25T09:01:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 24 Jul 2019, Phillip Wood wrote:\n\n> On 17/07/2019 15:39, Alban Gruin wrote:\n>\n> > To prevent mistakes when editing a branch, rebase features a knob,\n> > rebase.missingCommitsCheck, to warn the user if a commit was\n> > dropped.  Unfortunately, this check is only effective for the\n> > initial edit, which means that if you edit the todo list at a later\n> > point of the rebase and dropped a commit, no warnings or errors\n> > would be issued.\n> >\n> > This adds the ability to check if commits were dropped when resuming\n> > a rebase (with `rebase --continue'), when editing the todo list\n> > (with `rebase --edit-todo'), or when reloading the todo list after\n> > an `exec' command.\n>\n> I'm not sure if we really need to check the todo list when continuing or after\n> an exec command. The official way to edit the todo list is to run 'git rebase\n> --edit-todo' and I'm not sure if we support scripts writing to\n> .git/rebase-merge/git-rebase-todo directly. If we only support the check after\n> --edit-todo then I think the implementation can be simplified as we can just\n> write a copy of the file before it is edited and don't need to check\n> .git/rebase-merge/done. Additionally that would catch commits that are added\n> by the user and then deleted in a later edit. They wont be in the original\n> list so I don't think this series will detect their deletion.\n>\n> At the extreme I have a script around rebase that runs 'rebase -i HEAD' and\n> then fills in the todo list with a fake editor that adds 'reset ...' as the\n> first line to set the starting point of the rebase. I think dscho's\n> garden-shears script does something similar. Under the proposed scheme if I\n> subsequently edit the todo list it will not catch any deleted commits as the\n> original list is empty.\n\nIndeed, and in the meantime there is also an ever-green.sh script that\nnot only uses that trick to custom-populate the todo list, but also\n_extends_ it via an `exec` command at the end of the first rebase run,\nessentially allowing for a nested rebase.\n\nCiao,\nDscho\n"},{"id":"379317","messageId":"69ceed35-ced4-51e7-d724-1818ae45e9a6@gmail.com","threadId":"51492","inReplyTo":"ce6bdffd-0a04-803c-4a1e-0959342db01f@gmail.com","subject":"Re: [RFC PATCH 0/9] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo' and co.","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-07-25T20:26:36Z","receivedAt":"2019-07-25T20:26:53Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nLe 24/07/2019 à 15:29, Phillip Wood a écrit :\n> Hi Alban\n> \n> Thanks for working on this, it's great to see you back on the list and I\n> think it would be a useful addition to rebase. Unfortunately I'm not\n> sure about this implementation though (although the early bug fix\n> patches are useful in their own right)\n> \n> On 17/07/2019 15:39, Alban Gruin wrote:\n>> To prevent mistakes when editing a branch, rebase features a knob,\n>> rebase.missingCommitsCheck, to warn the user if a commit was dropped.\n>> Unfortunately, this check is only effective for the initial edit, which\n>> means that if you edit the todo list at a later point of the rebase and\n>> dropped a commit, no warnings or errors would be issued.\n>>\n>> This adds the ability to check if commits were dropped when resuming a\n>> rebase (with `rebase --continue'), when editing the todo list (with\n>> `rebase --edit-todo'), or when reloading the todo list after an `exec'\n>> command.\n> \n> I'm not sure if we really need to check the todo list when continuing or\n> after an exec command.\n\nIn which case I don’t really understand why there is an `error' mode if\none can completely bypass it with `--continue'.\n\n> The official way to edit the todo list is to run\n> 'git rebase --edit-todo' and I'm not sure if we support scripts writing\n> to .git/rebase-merge/git-rebase-todo directly. If we only support the\n> check after --edit-todo then I think the implementation can be\n> simplified as we can just write a copy of the file before it is edited\n> and don't need to check .git/rebase-merge/done. Additionally that would\n> catch commits that are added by the user and then deleted in a later\n> edit. They wont be in the original list so I don't think this series\n> will detect their deletion.\n> \n\nTrue -- but with this solution, if a bad command is introduced, there\nwill be false negatives.  Given the pitfall of my solution, this should\nbe an acceptable trade-off.\n\n> At the extreme I have a script around rebase that runs 'rebase -i HEAD'\n> and then fills in the todo list with a fake editor that adds 'reset ...'\n> as the first line to set the starting point of the rebase. I think\n> dscho's garden-shears script does something similar. Under the proposed\n> scheme if I subsequently edit the todo list it will not catch any\n> deleted commits as the original list is empty.\n> \n> Best Wishes\n> \n> Phillip\n> \n\nCheers,\nAlban\n\n"},{"id":"379484","messageId":"8214207e-fa35-44c4-4135-45bd90209e18@gmail.com","threadId":"51492","inReplyTo":"69ceed35-ced4-51e7-d724-1818ae45e9a6@gmail.com","subject":"Re: [RFC PATCH 0/9] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo' and co.","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-07-29T09:38:07Z","receivedAt":"2019-07-29T09:38:12Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 25/07/2019 21:26, Alban Gruin wrote:\n> Hi Phillip,\n> \n> Le 24/07/2019 à 15:29, Phillip Wood a écrit :\n>> Hi Alban\n>>\n>> Thanks for working on this, it's great to see you back on the list and I\n>> think it would be a useful addition to rebase. Unfortunately I'm not\n>> sure about this implementation though (although the early bug fix\n>> patches are useful in their own right)\n>>\n>> On 17/07/2019 15:39, Alban Gruin wrote:\n>>> To prevent mistakes when editing a branch, rebase features a knob,\n>>> rebase.missingCommitsCheck, to warn the user if a commit was dropped.\n>>> Unfortunately, this check is only effective for the initial edit, which\n>>> means that if you edit the todo list at a later point of the rebase and\n>>> dropped a commit, no warnings or errors would be issued.\n>>>\n>>> This adds the ability to check if commits were dropped when resuming a\n>>> rebase (with `rebase --continue'), when editing the todo list (with\n>>> `rebase --edit-todo'), or when reloading the todo list after an `exec'\n>>> command.\n>>\n>> I'm not sure if we really need to check the todo list when continuing or\n>> after an exec command.\n> \n> In which case I don’t really understand why there is an `error' mode if\n> one can completely bypass it with `--continue'.\n\nThat's an interesting point about `--continue`. Perhaps if `--edit-todo` \ndetects deleted lines in error mode it should write a file to stop \n`--continue` continuing rather than having to validate the entire list \neach time we continue a rebase. Alternatively we could annotate the todo \nlist with a message the dropped commits commented out and reopen the \neditor for the user to fix the problem, but that would cause scripted \neditors to enter a infinite loop as they're unlikely to fix the problem \nthe second time round. A third possibility is to keep your code \nvalidating the list each time we run continue, but update the backup \nfile with each edit so it detects added commits that are deleted in a \nlater edit. This would also provide some protection for users who edit \ngit-rebase-todo directly, though if they are using a script that deletes \nlines in git-rebase-todo directly it will suddenly stop working with \nthis change if they have rebase.missingCommitsCheck set to error.\n\nHaving said all that we could decide that the existing error message is \nenough and allow the user to skip re-editing the list if they really did \nmean to remove those lines. It would be annoying to have to re-edit the \nlist when one had intended to delete those lines.\n\n>> The official way to edit the todo list is to run\n>> 'git rebase --edit-todo' and I'm not sure if we support scripts writing\n>> to .git/rebase-merge/git-rebase-todo directly. If we only support the\n>> check after --edit-todo then I think the implementation can be\n>> simplified as we can just write a copy of the file before it is edited\n>> and don't need to check .git/rebase-merge/done. Additionally that would\n>> catch commits that are added by the user and then deleted in a later\n>> edit. They wont be in the original list so I don't think this series\n>> will detect their deletion.\n>>\n> \n> True -- but with this solution, if a bad command is introduced, there\n> will be false negatives.  Given the pitfall of my solution, this should\n> be an acceptable trade-off.\n\nWe could detect a bad commit by checking the oid and not complaining if \nit is not valid. That's slightly complicated by labels, but we could \nfairly easily keep of list of the labels defined so far as we scan the \nlist. That would also open the possibility of detecting errors where the \nuser references an undefined label in `merge` or `reset` commands but \nthat's a separate problem.\n\nBest Wishes\n\nPhillip\n\n>> At the extreme I have a script around rebase that runs 'rebase -i HEAD'\n>> and then fills in the todo list with a fake editor that adds 'reset ...'\n>> as the first line to set the starting point of the rebase. I think\n>> dscho's garden-shears script does something similar. Under the proposed\n>> scheme if I subsequently edit the todo list it will not catch any\n>> deleted commits as the original list is empty.\n>>\n>> Best Wishes\n>>\n>> Phillip\n>>\n> \n> Cheers,\n> Alban\n> \n"},{"id":"382869","messageId":"5dc165ae-8d56-04e1-911e-06836af30cde@gmail.com","threadId":"51492","inReplyTo":"8214207e-fa35-44c4-4135-45bd90209e18@gmail.com","subject":"Re: [RFC PATCH 0/9] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo' and co.","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-09-24T20:15:46Z","receivedAt":"2019-09-24T20:16:02Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi,\n\nThis has been a long time since I have not given an update on this --\nsorry about that.  I think it’s time to do it.\n\nLe 29/07/2019 à 11:38, Phillip Wood a écrit :\n> Hi Alban\n> \n> -%<-\n> \n> That's an interesting point about `--continue`. Perhaps if `--edit-todo`\n> detects deleted lines in error mode it should write a file to stop\n> `--continue` continuing rather than having to validate the entire list\n> each time we continue a rebase. Alternatively we could annotate the todo\n> list with a message the dropped commits commented out and reopen the\n> editor for the user to fix the problem, but that would cause scripted\n> editors to enter a infinite loop as they're unlikely to fix the problem\n> the second time round. A third possibility is to keep your code\n> validating the list each time we run continue, but update the backup\n> file with each edit so it detects added commits that are deleted in a\n> later edit. This would also provide some protection for users who edit\n> git-rebase-todo directly, though if they are using a script that deletes\n> lines in git-rebase-todo directly it will suddenly stop working with\n> this change if they have rebase.missingCommitsCheck set to error.\n> \n> Having said all that we could decide that the existing error message is\n> enough and allow the user to skip re-editing the list if they really did\n> mean to remove those lines. It would be annoying to have to re-edit the\n> list when one had intended to delete those lines.\n> \n\nIf we do not check the todo list after `exec' commands, patches 3 to 6\nshould be useless in this series and could be sent separately (I’m still\ninterested in reducing useless round trips to the disk).  I found a\ncleaner way to do that, without touching to sequencer_continue().\n\nFor the main feature of this series, I need to write tests for it, and\nthen I’ll send it as a WIP series, once again.\n\nCheers,\nAlban\n\n"},{"id":"385369","messageId":"20191104095449.9964-1-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20190717143918.7406-1-alban.gruin@gmail.com","subject":"[RFC PATCH v2 0/2] rebase -i: extend rebase.missingCommitsCheck to `--edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-11-04T09:54:47Z","receivedAt":"2019-11-04T09:55:43Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"To prevent mistakes when editing a branch, rebase features a knob,\nrebase.missingCommitsCheck, to warn the user if a commit was dropped.\nUnfortunately, this check is only effective for the initial edit, which\nmeans that if you edit the todo list at a later point of the rebase and\ndropped a commit, no warnings or errors would be issued.\n\nThis adds the ability to check if commits were dropped when editing the\ntodo list with `--edit-todo'.\n\nThe first patch adds new tests demonstrating that the todo list is not\nchecked with `--edit-todo'.  The second patch implements the check.\n\nThis series is pretty much a revamp of the first version.  Some patches\nfrom the original series were rerolled separately and live now in\nag/sequencer-todo-updates.  This version is pretty much brand new, as\nyou can see in the range diff below.\n\nChanges since v1 are about what the new todo list is compared to, and\nwhen.  The original series compared the todo list against the original\nedit, so if a commit was added then deleted, it would go unnoticed.\nNow, it is compared against the previous version to avoid that.  This is\nhighlighted in the new tests.  The original series checked the todo list\nafter `--edit-todo', but also before continuing a rebase or after having\nexecuted a command.  Now, it only checks it after `--edit-todo', as\nsuggested by Phillip Wood.\n\nThis is based on master (da72936f54, \"Git 2.24\").\n\nThe tip of this series is tagged as \"edit-todo-drop-rfc-v2\" at\nhttps://github.com/agrn/git.\n\nAlban Gruin (2):\n  t3404: demonstrate that --edit-todo does not check for dropped commits\n  rebase-interactive: warn if commit is dropped with --edit-todo\n\n rebase-interactive.c          |  7 ++--\n sequencer.c                   |  5 +--\n t/t3404-rebase-interactive.sh | 75 +++++++++++++++++++++++++++++++++++\n 3 files changed, 79 insertions(+), 8 deletions(-)\n\nDiff-intervalle contre v1 :\n 1:  b35f920318 !  1:  6974b6c8f2 t3404: demonstrate that --edit-todo does not check for dropped commits\n    @@ Commit message\n     \n         When set to \"warn\" or \"error\", `rebase.missingCommitCheck' would make\n         rebase -i warn if the user removed commits from the todo list to prevent\n    -    mistakes.  Unfortunately, rebase --edit-todo and rebase --continue don't\n    -    take it into account.\n    +    mistakes.  Unfortunately, rebase --edit-todo don't take it into account.\n     \n         This adds three tests to t3404 to demonstrate this.  The first one is\n         not broken, as when `rebase.missingCommitsCheck' is not set, nothing in\n         particular must be done towards dropped commits.  The two others are\n         broken, demonstrating the problem.\n     \n    +    The tests for `rebase.missingCommitsCheck = warn' and\n    +    `rebase.missingCommitsCheck = error' have a similar structure.  First,\n    +    we start a rebase with an incorrect command on the first line.  Then, we\n    +    edit the todo list, removing the first and the last lines.  This\n    +    demonstrates that `--edit-todo' notices dropped commits, but not when\n    +    the command is incorrect.  Then, we restore the original todo list, and\n    +    edit it to remove the last line.  This demonstrates that if we add a\n    +    commit after the initial edit, then remove it, `--edit-todo' will notice\n    +    that it has been dropped.  Then, the actual rebase takes place.\n    +\n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## t/t3404-rebase-interactive.sh ##\n    @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.mi\n     +\ttest_config rebase.missingCommitsCheck ignore &&\n     +\trebase_setup_and_clean missing-commit &&\n     +\tset_fake_editor &&\n    -+\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n    -+\t\tgit rebase -i --root >/dev/null 2>stderr &&\n    -+\tFAKE_LINES=\"1 2 4\" git rebase --edit-todo &&\n    ++\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n    ++\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n     +\tgit rebase --continue 2>actual &&\n     +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n     +\ttest_i18ngrep \\\n    @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.mi\n     +\t\tactual\n     +'\n     +\n    -+cat >expect <<EOF\n    -+error: invalid line 5: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n    -+Warning: some commits may have been dropped accidentally.\n    -+Dropped commits (newer to older):\n    -+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n    -+To avoid this message, use \"drop\" to explicitly remove a commit.\n    -+\n    -+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n    -+The possible behaviours are: ignore, warn, error.\n    -+\n    -+EOF\n    -+\n    -+tail -n 8 <expect >expect.2\n    -+\n     +test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n    ++\tcat >expect <<-EOF &&\n    ++\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n    ++\tWarning: some commits may have been dropped accidentally.\n    ++\tDropped commits (newer to older):\n    ++\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n    ++\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n    ++\tEOF\n    ++\ttail -n4 expect >expect.2 &&\n     +\ttest_config rebase.missingCommitsCheck warn &&\n     +\trebase_setup_and_clean missing-commit &&\n     +\tset_fake_editor &&\n    -+\ttest_must_fail env FAKE_LINES=\"1 2 3 4 bad 5\" \\\n    -+\t\tgit rebase -i --root >/dev/null 2>stderr &&\n    -+\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n    ++\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n    ++\t\tgit rebase -i --root &&\n    ++\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n    ++\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n    ++\thead -n5 actual.2 >actual &&\n     +\ttest_i18ncmp expect actual &&\n    -+\tgit rebase --continue 2>actual.2 &&\n    -+\thead -n 8 <actual.2 >actual &&\n    ++\tcp orig .git/rebase-merge/git-rebase-todo &&\n    ++\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n    ++\thead -n4 actual.2 >actual &&\n     +\ttest_i18ncmp expect.2 actual &&\n    ++\tgit rebase --continue 2>actual &&\n     +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n     +\ttest_i18ngrep \\\n     +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n    -+\t\tactual.2\n    ++\t\tactual\n     +'\n     +\n    -+cat >expect <<EOF\n    -+error: invalid line 3: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)\n    -+Warning: some commits may have been dropped accidentally.\n    -+Dropped commits (newer to older):\n    -+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n    -+ - $(git rev-list --pretty=oneline --abbrev-commit -1 master~2)\n    -+To avoid this message, use \"drop\" to explicitly remove a commit.\n    -+\n    -+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.\n    -+The possible behaviours are: ignore, warn, error.\n    -+\n    -+EOF\n    -+\n    -+tail -n 9 <expect >expect.2\n    -+\n     +test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n    ++\tcat >expect <<-EOF &&\n    ++\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n    ++\tWarning: some commits may have been dropped accidentally.\n    ++\tDropped commits (newer to older):\n    ++\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n    ++\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n    ++\tEOF\n    ++\ttail -n4 expect >expect.2 &&\n     +\ttest_config rebase.missingCommitsCheck error &&\n     +\trebase_setup_and_clean missing-commit &&\n     +\tset_fake_editor &&\n    -+\ttest_must_fail env FAKE_LINES=\"1 2 bad 3 4\" \\\n    -+\t\tgit rebase -i --root >/dev/null 2>stderr &&\n    -+\ttest_must_fail env FAKE_LINES=\"1 2 4\" \\\n    -+\t\tgit rebase --edit-todo 2>actual &&\n    ++\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n    ++\t\tgit rebase -i --root &&\n    ++\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n    ++\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n    ++\t\tgit rebase --edit-todo 2>actual.2 &&\n    ++\thead -n5 actual.2 >actual &&\n     +\ttest_i18ncmp expect actual &&\n    -+\ttest_must_fail git rebase --continue 2>actual &&\n    ++\tcp orig .git/rebase-merge/git-rebase-todo &&\n    ++\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n    ++\t\tgit rebase --edit-todo 2>actual.2 &&\n    ++\thead -n4 actual.2 >actual &&\n     +\ttest_i18ncmp expect.2 actual &&\n    -+\tcp .git/rebase-merge/git-rebase-todo.backup \\\n    -+\t\t.git/rebase-merge/git-rebase-todo &&\n    -+\tFAKE_LINES=\"1 2 drop 3 4 drop 5\" \\\n    -+\t\tgit rebase --edit-todo &&\n    ++\tcp orig .git/rebase-merge/git-rebase-todo &&\n    ++\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n     +\tgit rebase --continue 2>actual &&\n     +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n     +\ttest_i18ngrep \\\n 2:  7410a1bc4b <  -:  ---------- t3429: demonstrate that rebase exec does not check for dropped commits\n 3:  f9ef6d5569 <  -:  ---------- sequencer: update `total_nr' when adding an item to a todo list\n 4:  049a92dec0 <  -:  ---------- sequencer: update `done_nr' when skipping commands in a todo list\n 5:  f7aae0c763 <  -:  ---------- sequencer: move the code writing total_nr on the disk to a new function\n 6:  0e41c4c85e <  -:  ---------- sequencer: add a parameter to sequencer_continue() to accept a todo list\n 7:  e6f5589f18 <  -:  ---------- rebase-interactive: todo_list_check() also uses the done list\n 8:  69a562b0ab <  -:  ---------- rebase-interactive: warn if commit is dropped with --edit-todo\n 9:  4656ab11ae <  -:  ---------- sequencer: have read_populate_todo() check for dropped commits\n -:  ---------- >  2:  a4a700ce8b rebase-interactive: warn if commit is dropped with --edit-todo\n-- \n2.24.0.2.ga4a700ce8b\n\n"},{"id":"385370","messageId":"20191104095449.9964-2-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20191104095449.9964-1-alban.gruin@gmail.com","subject":"[RFC PATCH v2 1/2] t3404: demonstrate that --edit-todo does not check for dropped commits","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-11-04T09:54:48Z","receivedAt":"2019-11-04T09:55:43Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"When set to \"warn\" or \"error\", `rebase.missingCommitCheck' would make\nrebase -i warn if the user removed commits from the todo list to prevent\nmistakes.  Unfortunately, rebase --edit-todo don't take it into account.\n\nThis adds three tests to t3404 to demonstrate this.  The first one is\nnot broken, as when `rebase.missingCommitsCheck' is not set, nothing in\nparticular must be done towards dropped commits.  The two others are\nbroken, demonstrating the problem.\n\nThe tests for `rebase.missingCommitsCheck = warn' and\n`rebase.missingCommitsCheck = error' have a similar structure.  First,\nwe start a rebase with an incorrect command on the first line.  Then, we\nedit the todo list, removing the first and the last lines.  This\ndemonstrates that `--edit-todo' notices dropped commits, but not when\nthe command is incorrect.  Then, we restore the original todo list, and\nedit it to remove the last line.  This demonstrates that if we add a\ncommit after the initial edit, then remove it, `--edit-todo' will notice\nthat it has been dropped.  Then, the actual rebase takes place.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n t/t3404-rebase-interactive.sh | 75 +++++++++++++++++++++++++++++++++++\n 1 file changed, 75 insertions(+)\n\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex d2dfbe46b9..be8badf7b3 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1353,6 +1353,81 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n+\ttest_config rebase.missingCommitsCheck ignore &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n+\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\tEOF\n+\ttail -n4 expect >expect.2 &&\n+\ttest_config rebase.missingCommitsCheck warn &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\tgit rebase -i --root &&\n+\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\thead -n5 actual.2 >actual &&\n+\ttest_i18ncmp expect actual &&\n+\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\thead -n4 actual.2 >actual &&\n+\ttest_i18ncmp expect.2 actual &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\tEOF\n+\ttail -n4 expect >expect.2 &&\n+\ttest_config rebase.missingCommitsCheck error &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\tgit rebase -i --root &&\n+\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n+\t\tgit rebase --edit-todo 2>actual.2 &&\n+\thead -n5 actual.2 >actual &&\n+\ttest_i18ncmp expect actual &&\n+\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n+\t\tgit rebase --edit-todo 2>actual.2 &&\n+\thead -n4 actual.2 >actual &&\n+\ttest_i18ncmp expect.2 actual &&\n+\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n \trebase_setup_and_clean abbrevcmd &&\n \ttest_commit \"first\" file1.txt \"first line\" first &&\n-- \n2.24.0.2.ga4a700ce8b\n\n"},{"id":"385371","messageId":"20191104095449.9964-3-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20191104095449.9964-1-alban.gruin@gmail.com","subject":"[RFC PATCH v2 2/2] rebase-interactive: warn if commit is dropped with --edit-todo","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-11-04T09:54:49Z","receivedAt":"2019-11-04T09:55:46Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"This adds the ability for --edit-todo to check if commits were dropped\nby the user.  As both edit_todo_list() and complete_action() parse the\ntodo list and check for dropped commits, the code doing so in the latter\nis removed to reduce duplication.\n\nTests added previously should work now.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c          | 7 +++----\n sequencer.c                   | 5 +----\n t/t3404-rebase-interactive.sh | 4 ++--\n 3 files changed, 6 insertions(+), 10 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..1b8d7f25bf 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -115,10 +115,9 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \tif (initial && new_todo->buf.len == 0)\n \t\treturn -3;\n \n-\t/* For the initial edit, the todo list gets parsed in\n-\t * complete_action(). */\n-\tif (!initial)\n-\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n+\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo) ||\n+\t    todo_list_check(todo_list, new_todo))\n+\t\treturn -4;\n \n \treturn 0;\n }\ndiff --git a/sequencer.c b/sequencer.c\nindex 9d5964fd81..92f2e6f6a0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5062,10 +5062,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\ttodo_list_release(&new_todo);\n \n \t\treturn error(_(\"nothing to do\"));\n-\t}\n-\n-\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n-\t    todo_list_check(todo_list, &new_todo)) {\n+\t} else if (res == -4) {\n \t\tfprintf(stderr, _(edit_todo_list_advice));\n \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n \t\ttodo_list_release(&new_todo);\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex be8badf7b3..040ec616f3 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1366,7 +1366,7 @@ test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ig\n \t\tactual\n '\n \n-test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n \tcat >expect <<-EOF &&\n \terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n \tWarning: some commits may have been dropped accidentally.\n@@ -1395,7 +1395,7 @@ test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = wa\n \t\tactual\n '\n \n-test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n \tcat >expect <<-EOF &&\n \terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n \tWarning: some commits may have been dropped accidentally.\n-- \n2.24.0.2.ga4a700ce8b\n\n"},{"id":"385448","messageId":"7ca0f5c7-9c6c-34fe-215a-c57946f18f2c@gmail.com","threadId":"51492","inReplyTo":"20191104095449.9964-3-alban.gruin@gmail.com","subject":"Re: [RFC PATCH v2 2/2] rebase-interactive: warn if commit is dropped with --edit-todo","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-11-05T14:20:37Z","receivedAt":"2019-11-05T14:20:43Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nThanks for working on this. I've got a couple of specific comments \nbelow. On a more general level I'd be tempted to squash the two patches \ntogether rather than adding the tests as a separate commit. Also I think \nbefore we discussed having `rebase --continue` refuse to continue if \n`rebase --edit-todo` had dropped commits when rebase.missingCommitsCheck \nwas set to error. I think that would be useful but could always come \nlater. --edit-todo could write a file to .git/rebase-merge to prevent \n--continue from continuing in case of errors.\n\nOn 04/11/2019 09:54, Alban Gruin wrote:\n> This adds the ability for --edit-todo to check if commits were dropped\n> by the user.  As both edit_todo_list() and complete_action() parse the\n> todo list and check for dropped commits, the code doing so in the latter\n> is removed to reduce duplication.\n> \n> Tests added previously should work now.\n> \n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>   rebase-interactive.c          | 7 +++----\n>   sequencer.c                   | 5 +----\n>   t/t3404-rebase-interactive.sh | 4 ++--\n>   3 files changed, 6 insertions(+), 10 deletions(-)\n> \n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index aa18ae82b7..1b8d7f25bf 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -115,10 +115,9 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>   \tif (initial && new_todo->buf.len == 0)\n>   \t\treturn -3;\n>   \n> -\t/* For the initial edit, the todo list gets parsed in\n> -\t * complete_action(). */\n> -\tif (!initial)\n> -\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n> +\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo) ||\n> +\t    todo_list_check(todo_list, new_todo))\n> +\t\treturn -4;\n>   \n>   \treturn 0;\n>   }\n> diff --git a/sequencer.c b/sequencer.c\n> index 9d5964fd81..92f2e6f6a0 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -5062,10 +5062,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>   \t\ttodo_list_release(&new_todo);\n>   \n>   \t\treturn error(_(\"nothing to do\"));\n> -\t}\n> -\n> -\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n> -\t    todo_list_check(todo_list, &new_todo)) {\n> +\t} else if (res == -4) {\n>   \t\tfprintf(stderr, _(edit_todo_list_advice));\n\nDo we want to print this advice when `rebase --edit-todo` detects \ndropped commits as well? I'm not clear what error messages are printed \nit that case. If we always want this printed then it could be moved into \nedit_todo_list()\n\nThanks again for working on this, it's a really useful addition to rebase\n\nBest Wishes\n\nPhillip\n\n>   \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n>   \t\ttodo_list_release(&new_todo);\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index be8badf7b3..040ec616f3 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1366,7 +1366,7 @@ test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ig\n>   \t\tactual\n>   '\n>   \n> -test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n>   \tcat >expect <<-EOF &&\n>   \terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n>   \tWarning: some commits may have been dropped accidentally.\n> @@ -1395,7 +1395,7 @@ test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = wa\n>   \t\tactual\n>   '\n>   \n> -test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n>   \tcat >expect <<-EOF &&\n>   \terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n>   \tWarning: some commits may have been dropped accidentally.\n> \n"},{"id":"387425","messageId":"20191202234759.26201-1-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20191104095449.9964-1-alban.gruin@gmail.com","subject":"[PATCH v3 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-12-02T23:47:57Z","receivedAt":"2019-12-02T23:48:34Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"To prevent mistakes when editing a branch, rebase features a knob,\nrebase.missingCommitsCheck, to warn the user if a commit was dropped.\nUnfortunately, this check is only effective for the initial edit, which\nmeans that if you edit the todo list at a later point of the rebase and\ndrop a commit, no warnings or errors would be issued.\n\nThis adds the ability to check if commits were dropped when editing the\ntodo list with `--edit-todo', and when resuming a rebase.\n\nThe first patch moves moves check_todo_list_from_file() and\n`edit_todo_list_advice' from sequencer.c to rebase-interactive.c so the\nlatter can be used by edit_todo_list() and todo_list_check().  The\nsecond patch adds the check to `--edit-todo' and `--continue' and tests.\n\nThis is based on 393adf7a6f (\"sequencer: directly call pick_commits()\nfrom complete_action()\", 2019-11-24).\n\nThe tip of this series is tagged as \"edit-todo-drop-v3\" at\nhttps://github.com/agrn/git.\n\nChanges since v2:\n - Merged patches 1 and 2.\n - Reinstated support for `git rebase --continue'.\n - Print `edit_todo_list_advice' if a dropped commit was detected by\n   `--edit-todo' or `--continue'.\n\nAlban Gruin (2):\n  sequencer: move check_todo_list_from_file() to rebase-interactive.c\n  rebase-interactive: warn if commit is dropped with `rebase\n    --edit-todo'\n\n rebase-interactive.c          | 57 ++++++++++++++++++++----\n rebase-interactive.h          |  2 +\n sequencer.c                   | 53 ++++++----------------\n sequencer.h                   |  1 -\n t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n 5 files changed, 147 insertions(+), 49 deletions(-)\n\nDiff-intervalle contre v2:\n-:  ---------- > 1:  996045a300 sequencer: move check_todo_list_from_file() to rebase-interactive.c\n1:  6974b6c8f2 ! 2:  ba6d27e5b4 t3404: demonstrate that --edit-todo does not check for dropped commits\n    @@ Metadata\n     Author: Alban Gruin <alban.gruin@gmail.com>\n     \n      ## Commit message ##\n    -    t3404: demonstrate that --edit-todo does not check for dropped commits\n    +    rebase-interactive: warn if commit is dropped with `rebase --edit-todo'\n     \n    -    When set to \"warn\" or \"error\", `rebase.missingCommitCheck' would make\n    -    rebase -i warn if the user removed commits from the todo list to prevent\n    -    mistakes.  Unfortunately, rebase --edit-todo don't take it into account.\n    +    When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n    +    `rebase -i' warn if the user removed commits from the todo list to\n    +    prevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n    +    --continue' don't take it into account.\n     \n    -    This adds three tests to t3404 to demonstrate this.  The first one is\n    -    not broken, as when `rebase.missingCommitsCheck' is not set, nothing in\n    -    particular must be done towards dropped commits.  The two others are\n    -    broken, demonstrating the problem.\n    +    This adds the ability for `rebase --edit-todo' and `rebase --continue'\n    +    to check if commits were dropped by the user.  As both edit_todo_list()\n    +    and complete_action() parse the todo list and check for dropped commits,\n    +    the code doing so in the latter is removed to reduce duplication.\n    +    `edit_todo_list_advice' is removed from sequencer.c as it is no longer\n    +    used there.\n     \n    -    The tests for `rebase.missingCommitsCheck = warn' and\n    -    `rebase.missingCommitsCheck = error' have a similar structure.  First,\n    -    we start a rebase with an incorrect command on the first line.  Then, we\n    -    edit the todo list, removing the first and the last lines.  This\n    -    demonstrates that `--edit-todo' notices dropped commits, but not when\n    -    the command is incorrect.  Then, we restore the original todo list, and\n    -    edit it to remove the last line.  This demonstrates that if we add a\n    -    commit after the initial edit, then remove it, `--edit-todo' will notice\n    -    that it has been dropped.  Then, the actual rebase takes place.\n    +    This changes when a backup of the todo list is made.  Until now, it was\n    +    saved only before the initial edit.  Now, it is always performed before\n    +    the todo list is edited.  Without this, sequencer_continue() (`rebase\n    +    --continue') could only compare the current todo list against the\n    +    original, unedited list.  Before this change, this file was only used by\n    +    edit_todo_list() and `rebase -p' to create the backup before the initial\n    +    edit, and check_todo_list_from_file(), only used by `rebase -p' to check\n    +    for dropped commits after its own initial edit.\n    +\n    +    Three tests are added to t3404.  The tests for\n    +    `rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\n    +    error' have a similar structure.  First, we start a rebase with an\n    +    incorrect command on the first line.  Then, we edit the todo list,\n    +    removing the first and the last lines.  This demonstrates that\n    +    `--edit-todo' notices dropped commits, but not when the command is\n    +    incorrect.  Then, we restore the original todo list, and edit it to\n    +    remove the last line.  This demonstrates that if we add a commit after\n    +    the initial edit, then remove it, `--edit-todo' will notice that it has\n    +    been dropped.  Then, the actual rebase takes place.  In the third test,\n    +    it is also checked that `--continue' will refuse to resume the rebase if\n    +    commits were dropped.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n     \n    + ## rebase-interactive.c ##\n    +@@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n    + \t\t   struct todo_list *new_todo, const char *shortrevisions,\n    + \t\t   const char *shortonto, unsigned flags)\n    + {\n    +-\tconst char *todo_file = rebase_path_todo();\n    ++\tconst char *todo_file = rebase_path_todo(),\n    ++\t\t*todo_backup = rebase_path_todo_backup();\n    + \tunsigned initial = shortrevisions && shortonto;\n    + \n    + \t/* If the user is editing the todo list, we first try to parse\n    +@@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n    + \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n    + \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n    + \n    +-\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n    +-\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n    +-\t\t\t     rebase_path_todo_backup());\n    ++\tunlink(todo_backup);\n    ++\tif (copy_file(todo_backup, todo_file, 0666))\n    ++\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n    + \n    + \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n    + \t\treturn -2;\n    +@@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n    + \tif (initial && new_todo->buf.len == 0)\n    + \t\treturn -3;\n    + \n    +-\t/* For the initial edit, the todo list gets parsed in\n    +-\t * complete_action(). */\n    +-\tif (!initial)\n    +-\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n    ++\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {\n    ++\t\tfprintf(stderr, _(edit_todo_list_advice));\n    ++\t\treturn -4;\n    ++\t}\n    ++\n    ++\tif (todo_list_check(todo_list, new_todo))\n    ++\t\treturn -4;\n    + \n    + \treturn 0;\n    + }\n    +@@ rebase-interactive.c: int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n    + \t\t\"the level of warnings.\\n\"\n    + \t\t\"The possible behaviours are: ignore, warn, error.\\n\\n\"));\n    + \n    ++\tfprintf(stderr, _(edit_todo_list_advice));\n    ++\n    + leave_check:\n    + \tclear_commit_seen(&commit_seen);\n    + \treturn res;\n    +\n    + ## sequencer.c ##\n    +@@ sequencer.c: int sequencer_continue(struct repository *r, struct replay_opts *opts)\n    + \tif (read_populate_opts(opts))\n    + \t\treturn -1;\n    + \tif (is_rebase_i(opts)) {\n    ++\t\tstruct todo_list backup = TODO_LIST_INIT;\n    ++\n    + \t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n    + \t\t\tgoto release_todo_list;\n    ++\n    ++\t\tif (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {\n    ++\t\t\ttodo_list_parse_insn_buffer(r, backup.buf.buf, &backup);\n    ++\t\t\tres = todo_list_check(&backup, &todo_list);\n    ++\t\t\ttodo_list_release(&backup);\n    ++\n    ++\t\t\tif (res)\n    ++\t\t\t\tgoto release_todo_list;\n    ++\t\t}\n    ++\n    + \t\tif (commit_staged_changes(r, opts, &todo_list))\n    + \t\t\treturn -1;\n    + \t} else if (!file_exists(get_todo_path(opts)))\n    +@@ sequencer.c: int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n    + \treturn res;\n    + }\n    + \n    +-static const char edit_todo_list_advice[] =\n    +-N_(\"You can fix this with 'git rebase --edit-todo' \"\n    +-\"and then run 'git rebase --continue'.\\n\"\n    +-\"Or you can abort the rebase with 'git rebase\"\n    +-\" --abort'.\\n\");\n    +-\n    + /* skip picking commits whose parents are unchanged */\n    + static int skip_unnecessary_picks(struct repository *r,\n    + \t\t\t\t  struct todo_list *todo_list,\n    +@@ sequencer.c: int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n    + \t\ttodo_list_release(&new_todo);\n    + \n    + \t\treturn error(_(\"nothing to do\"));\n    +-\t}\n    +-\n    +-\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n    +-\t    todo_list_check(todo_list, &new_todo)) {\n    +-\t\tfprintf(stderr, _(edit_todo_list_advice));\n    ++\t} else if (res == -4) {\n    + \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n    + \t\ttodo_list_release(&new_todo);\n    + \n    +\n      ## t/t3404-rebase-interactive.sh ##\n     @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n      \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n    @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.mi\n     +\t\tactual\n     +'\n     +\n    -+test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n    ++test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n     +\tcat >expect <<-EOF &&\n     +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n     +\tWarning: some commits may have been dropped accidentally.\n    @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.mi\n     +\t\tactual\n     +'\n     +\n    -+test_expect_failure 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n    ++test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n     +\tcat >expect <<-EOF &&\n     +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n     +\tWarning: some commits may have been dropped accidentally.\n     +\tDropped commits (newer to older):\n     +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n     +\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n    ++\n    ++\tUse '\\''git config rebase.missingCommitsCheck'\\'' to change the level of warnings.\n    ++\tThe possible behaviours are: ignore, warn, error.\n    ++\n    ++\tYou can fix this with '\\''git rebase --edit-todo'\\'' and then run '\\''git rebase --continue'\\''.\n    ++\tOr you can abort the rebase with '\\''git rebase --abort'\\''.\n     +\tEOF\n    -+\ttail -n4 expect >expect.2 &&\n    ++\ttail -n10 expect >expect.2 &&\n     +\ttest_config rebase.missingCommitsCheck error &&\n     +\trebase_setup_and_clean missing-commit &&\n     +\tset_fake_editor &&\n    @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.mi\n     +\t\tgit rebase -i --root &&\n     +\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n     +\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n    -+\t\tgit rebase --edit-todo 2>actual.2 &&\n    -+\thead -n5 actual.2 >actual &&\n    ++\t\tgit rebase --edit-todo 2>actual &&\n    ++\ttest_i18ncmp expect actual &&\n    ++\ttest_must_fail git rebase --continue 2>actual &&\n     +\ttest_i18ncmp expect actual &&\n     +\tcp orig .git/rebase-merge/git-rebase-todo &&\n     +\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n    -+\t\tgit rebase --edit-todo 2>actual.2 &&\n    -+\thead -n4 actual.2 >actual &&\n    ++\t\tgit rebase --edit-todo 2>actual &&\n    ++\ttest_i18ncmp expect.2 actual &&\n    ++\ttest_must_fail git rebase --continue 2>actual &&\n     +\ttest_i18ncmp expect.2 actual &&\n     +\tcp orig .git/rebase-merge/git-rebase-todo &&\n     +\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n2:  a4a700ce8b < -:  ---------- rebase-interactive: warn if commit is dropped with --edit-todo\n-- \n2.24.0\n\n"},{"id":"387424","messageId":"20191202234759.26201-2-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20191202234759.26201-1-alban.gruin@gmail.com","subject":"[PATCH v3 1/2] sequencer: move check_todo_list_from_file() to rebase-interactive.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-12-02T23:47:58Z","receivedAt":"2019-12-02T23:48:35Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The message contained in `edit_todo_list_advice' (sequencer.c) is\nprinted after the initial edit of the todo list if it can't be parsed or\nif commits were dropped.  This is done either in complete_action() for\n`rebase -i', or in check_todo_list_from_file() for `rebase -p'.\n\nSince we want to add this check when editing the list, we also want to\nuse this message from edit_todo_list() (rebase-interactive.c).  To this\nend, check_todo_list_from_file() is moved to rebase-interactive.c, and\n`edit_todo_list_advice' is copied there.  In the next commit,\ncomplete_action() will stop using it, and `edit_todo_list_advice' will\nbe removed from sequencer.c.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c | 35 +++++++++++++++++++++++++++++++++++\n rebase-interactive.h |  2 ++\n sequencer.c          | 29 -----------------------------\n sequencer.h          |  1 -\n 4 files changed, 37 insertions(+), 30 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..ad5dd49c31 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -6,6 +6,12 @@\n #include \"commit-slab.h\"\n #include \"config.h\"\n \n+static const char edit_todo_list_advice[] =\n+N_(\"You can fix this with 'git rebase --edit-todo' \"\n+\"and then run 'git rebase --continue'.\\n\"\n+\"Or you can abort the rebase with 'git rebase\"\n+\" --abort'.\\n\");\n+\n enum missing_commit_check_level {\n \tMISSING_COMMIT_CHECK_IGNORE = 0,\n \tMISSING_COMMIT_CHECK_WARN,\n@@ -187,3 +193,32 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n+\n+int check_todo_list_from_file(struct repository *r)\n+{\n+\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n+\tint res = 0;\n+\n+\tif (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo());\n+\t\tgoto out;\n+\t}\n+\n+\tif (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo_backup());\n+\t\tgoto out;\n+\t}\n+\n+\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n+\tif (!res)\n+\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n+\tif (!res)\n+\t\tres = todo_list_check(&old_todo, &new_todo);\n+\tif (res)\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+out:\n+\ttodo_list_release(&old_todo);\n+\ttodo_list_release(&new_todo);\n+\n+\treturn res;\n+}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..5f41bf5a28 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -13,4 +13,6 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   const char *shortonto, unsigned flags);\n int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n \n+int check_todo_list_from_file(struct repository *r);\n+\n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex ec0b793fc5..181bb35f5f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4992,35 +4992,6 @@ N_(\"You can fix this with 'git rebase --edit-todo' \"\n \"Or you can abort the rebase with 'git rebase\"\n \" --abort'.\\n\");\n \n-int check_todo_list_from_file(struct repository *r)\n-{\n-\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n-\tint res = 0;\n-\n-\tif (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tif (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n-\tif (!res)\n-\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n-\tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n-\tif (res)\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n-out:\n-\ttodo_list_release(&old_todo);\n-\ttodo_list_release(&new_todo);\n-\n-\treturn res;\n-}\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\ndiff --git a/sequencer.h b/sequencer.h\nindex 574260f621..75ddc5db3a 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -155,7 +155,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \n void todo_list_add_exec_commands(struct todo_list *todo_list,\n \t\t\t\t struct string_list *commands);\n-int check_todo_list_from_file(struct repository *r);\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const char *orig_head, struct string_list *commands,\n-- \n2.24.0\n\n"},{"id":"387426","messageId":"20191202234759.26201-3-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20191202234759.26201-1-alban.gruin@gmail.com","subject":"[PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-12-02T23:47:59Z","receivedAt":"2019-12-02T23:48:38Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n`rebase -i' warn if the user removed commits from the todo list to\nprevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n--continue' don't take it into account.\n\nThis adds the ability for `rebase --edit-todo' and `rebase --continue'\nto check if commits were dropped by the user.  As both edit_todo_list()\nand complete_action() parse the todo list and check for dropped commits,\nthe code doing so in the latter is removed to reduce duplication.\n`edit_todo_list_advice' is removed from sequencer.c as it is no longer\nused there.\n\nThis changes when a backup of the todo list is made.  Until now, it was\nsaved only before the initial edit.  Now, it is always performed before\nthe todo list is edited.  Without this, sequencer_continue() (`rebase\n--continue') could only compare the current todo list against the\noriginal, unedited list.  Before this change, this file was only used by\nedit_todo_list() and `rebase -p' to create the backup before the initial\nedit, and check_todo_list_from_file(), only used by `rebase -p' to check\nfor dropped commits after its own initial edit.\n\nThree tests are added to t3404.  The tests for\n`rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\nerror' have a similar structure.  First, we start a rebase with an\nincorrect command on the first line.  Then, we edit the todo list,\nremoving the first and the last lines.  This demonstrates that\n`--edit-todo' notices dropped commits, but not when the command is\nincorrect.  Then, we restore the original todo list, and edit it to\nremove the last line.  This demonstrates that if we add a commit after\nthe initial edit, then remove it, `--edit-todo' will notice that it has\nbeen dropped.  Then, the actual rebase takes place.  In the third test,\nit is also checked that `--continue' will refuse to resume the rebase if\ncommits were dropped.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c          | 22 ++++++----\n sequencer.c                   | 24 +++++-----\n t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n 3 files changed, 110 insertions(+), 19 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex ad5dd49c31..80b6a2e7a6 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -97,7 +97,8 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags)\n {\n-\tconst char *todo_file = rebase_path_todo();\n+\tconst char *todo_file = rebase_path_todo(),\n+\t\t*todo_backup = rebase_path_todo_backup();\n \tunsigned initial = shortrevisions && shortonto;\n \n \t/* If the user is editing the todo list, we first try to parse\n@@ -110,9 +111,9 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n \n-\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n-\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n-\t\t\t     rebase_path_todo_backup());\n+\tunlink(todo_backup);\n+\tif (copy_file(todo_backup, todo_file, 0666))\n+\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n \n \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n \t\treturn -2;\n@@ -121,10 +122,13 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \tif (initial && new_todo->buf.len == 0)\n \t\treturn -3;\n \n-\t/* For the initial edit, the todo list gets parsed in\n-\t * complete_action(). */\n-\tif (!initial)\n-\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n+\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t\treturn -4;\n+\t}\n+\n+\tif (todo_list_check(todo_list, new_todo))\n+\t\treturn -4;\n \n \treturn 0;\n }\n@@ -189,6 +193,8 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \t\t\"the level of warnings.\\n\"\n \t\t\"The possible behaviours are: ignore, warn, error.\\n\\n\"));\n \n+\tfprintf(stderr, _(edit_todo_list_advice));\n+\n leave_check:\n \tclear_commit_seen(&commit_seen);\n \treturn res;\ndiff --git a/sequencer.c b/sequencer.c\nindex 181bb35f5f..75d5ad0496 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4271,8 +4271,20 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n \tif (read_populate_opts(opts))\n \t\treturn -1;\n \tif (is_rebase_i(opts)) {\n+\t\tstruct todo_list backup = TODO_LIST_INIT;\n+\n \t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n \t\t\tgoto release_todo_list;\n+\n+\t\tif (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {\n+\t\t\ttodo_list_parse_insn_buffer(r, backup.buf.buf, &backup);\n+\t\t\tres = todo_list_check(&backup, &todo_list);\n+\t\t\ttodo_list_release(&backup);\n+\n+\t\t\tif (res)\n+\t\t\t\tgoto release_todo_list;\n+\t\t}\n+\n \t\tif (commit_staged_changes(r, opts, &todo_list))\n \t\t\treturn -1;\n \t} else if (!file_exists(get_todo_path(opts)))\n@@ -4986,12 +4998,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \treturn res;\n }\n \n-static const char edit_todo_list_advice[] =\n-N_(\"You can fix this with 'git rebase --edit-todo' \"\n-\"and then run 'git rebase --continue'.\\n\"\n-\"Or you can abort the rebase with 'git rebase\"\n-\" --abort'.\\n\");\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\n@@ -5089,11 +5095,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\ttodo_list_release(&new_todo);\n \n \t\treturn error(_(\"nothing to do\"));\n-\t}\n-\n-\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n-\t    todo_list_check(todo_list, &new_todo)) {\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t} else if (res == -4) {\n \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n \t\ttodo_list_release(&new_todo);\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 29a35840ed..9051c1e11d 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1343,6 +1343,89 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n+\ttest_config rebase.missingCommitsCheck ignore &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n+\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\tEOF\n+\ttail -n4 expect >expect.2 &&\n+\ttest_config rebase.missingCommitsCheck warn &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\tgit rebase -i --root &&\n+\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\thead -n5 actual.2 >actual &&\n+\ttest_i18ncmp expect actual &&\n+\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\thead -n4 actual.2 >actual &&\n+\ttest_i18ncmp expect.2 actual &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\n+\tUse '\\''git config rebase.missingCommitsCheck'\\'' to change the level of warnings.\n+\tThe possible behaviours are: ignore, warn, error.\n+\n+\tYou can fix this with '\\''git rebase --edit-todo'\\'' and then run '\\''git rebase --continue'\\''.\n+\tOr you can abort the rebase with '\\''git rebase --abort'\\''.\n+\tEOF\n+\ttail -n10 expect >expect.2 &&\n+\ttest_config rebase.missingCommitsCheck error &&\n+\trebase_setup_and_clean missing-commit &&\n+\tset_fake_editor &&\n+\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\tgit rebase -i --root &&\n+\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n+\t\tgit rebase --edit-todo 2>actual &&\n+\ttest_i18ncmp expect actual &&\n+\ttest_must_fail git rebase --continue 2>actual &&\n+\ttest_i18ncmp expect actual &&\n+\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n+\t\tgit rebase --edit-todo 2>actual &&\n+\ttest_i18ncmp expect.2 actual &&\n+\ttest_must_fail git rebase --continue 2>actual &&\n+\ttest_i18ncmp expect.2 actual &&\n+\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n+\tgit rebase --continue 2>actual &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n \trebase_setup_and_clean abbrevcmd &&\n \ttest_commit \"first\" file1.txt \"first line\" first &&\n-- \n2.24.0\n\n"},{"id":"387504","messageId":"xmqqk17b5263.fsf@gitster-ct.c.googlers.com","threadId":"51492","inReplyTo":"20191202234759.26201-3-alban.gruin@gmail.com","subject":"Re: [PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-04T19:19:32Z","receivedAt":"2019-12-04T19:19:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n> `rebase -i' warn if the user removed commits from the todo list to\n> prevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n> --continue' don't take it into account.\n>\n> This adds the ability for `rebase --edit-todo' and `rebase --continue'\n> to check if commits were dropped by the user.  As both edit_todo_list()\n> and complete_action() parse the todo list and check for dropped commits,\n> the code doing so in the latter is removed to reduce duplication.\n> `edit_todo_list_advice' is removed from sequencer.c as it is no longer\n> used there.\n>\n> This changes when a backup of the todo list is made.  Until now, it was\n> saved only before the initial edit.  Now, it is always performed before\n> the todo list is edited.  Without this, sequencer_continue() (`rebase\n> --continue') could only compare the current todo list against the\n> original, unedited list.  Before this change, this file was only used by\n> edit_todo_list() and `rebase -p' to create the backup before the initial\n> edit, and check_todo_list_from_file(), only used by `rebase -p' to check\n> for dropped commits after its own initial edit.\n>\n> Three tests are added to t3404.  The tests for\n> `rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\n> error' have a similar structure.  First, we start a rebase with an\n> incorrect command on the first line.  Then, we edit the todo list,\n> removing the first and the last lines.  This demonstrates that\n> `--edit-todo' notices dropped commits, but not when the command is\n> incorrect.  Then, we restore the original todo list, and edit it to\n> remove the last line.  This demonstrates that if we add a commit after\n> the initial edit, then remove it, `--edit-todo' will notice that it has\n> been dropped.  Then, the actual rebase takes place.  In the third test,\n> it is also checked that `--continue' will refuse to resume the rebase if\n> commits were dropped.\n>\n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n>  rebase-interactive.c          | 22 ++++++----\n>  sequencer.c                   | 24 +++++-----\n>  t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n>  3 files changed, 110 insertions(+), 19 deletions(-)\n\nLet me see if I understand the primary idea behind this change by\ntrying to paraphrase the log (read: this is not to suggest a better\nphrasing of the log message proposed in your message).\n\n * rebase-interactive.c::edit_todo_list() does not perform \"did the\n   user delete a pick, instead of turning pick into drop?\" but after\n   the end-user edits the file is the most logical place to do so.\n   Let's do that there.\n\n * The sequencer used to perform \"did the user delete a pick,\n   instead of turning pick into drop?\" check in complete_action().\n   We drop that call but for this particular codepath it does not\n   make any behaviour difference due to the next item.\n\n * New code does the check in sequencer_continue(), which is called\n   at the end of complete_action(), as well as many other places,\n   like builtin/rebase.c, builtin/revert.c, and sequencer_skip().\n   Because the check is only done when we are running \"rebase-i\",\n   this is safe---it only affects complete_action().\n\nI hope I got it more-or-less correctly ;-)\n\n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index ad5dd49c31..80b6a2e7a6 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -97,7 +97,8 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>  \t\t   struct todo_list *new_todo, const char *shortrevisions,\n>  \t\t   const char *shortonto, unsigned flags)\n>  {\n> -\tconst char *todo_file = rebase_path_todo();\n> +\tconst char *todo_file = rebase_path_todo(),\n> +\t\t*todo_backup = rebase_path_todo_backup();\n>  \t/* If the user is editing the todo list, we first try to parse\n> @@ -110,9 +111,9 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>  \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n>  \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n>  \n> -\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n> -\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n> -\t\t\t     rebase_path_todo_backup());\n> +\tunlink(todo_backup);\n> +\tif (copy_file(todo_backup, todo_file, 0666))\n> +\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n\nWe used to copy ONLY when initial is set and we left old todo_backup\nintact when !initial.  That is no longer true after this change, but\nit is intended---we create an exact copy of what we would hand out\nto the end-user, so that we can compare it with the edited result\nto figure out what got changed.\n\nWe unlink(2) unconditionally because the only effect we want to see\nhere is that todo_backup does not exist before we call copy_file()\nthat wants to do O_CREAT|O_EXCL.  I wonder if we want to avoid\nunlink() when initial, and also if we want to do unlink_or_warn()\nwhen !initial (read: this is just \"wondering\" without thinking long\nenough to suggest that doing so would be better)\n\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 29a35840ed..9051c1e11d 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1343,6 +1343,89 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n>  \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>  '\n>  \n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n> +\ttest_config rebase.missingCommitsCheck ignore &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n> +\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n\nOK, so we lost \"pick 5\" but with missing-check disabled, that should\nnot trigger any annoying warning or error.\n\n> +\tgit rebase --continue 2>actual &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n> +\tcat >expect <<-EOF &&\n> +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n> +\tWarning: some commits may have been dropped accidentally.\n> +\tDropped commits (newer to older):\n> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> +\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n> +\tEOF\n> +\ttail -n4 expect >expect.2 &&\n> +\ttest_config rebase.missingCommitsCheck warn &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n> +\t\tgit rebase -i --root &&\n> +\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n> +\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n> +\thead -n5 actual.2 >actual &&\n> +\ttest_i18ncmp expect actual &&\n\nOK, so we lost \"pick 1\" while discarding \"bad\", and we should notice\nthe lossage?  I see \"head -n5\" there, which means we are still\ngetting \"invalid line 1: badcmd\", even though FAKE_LINES now got rid\nof \"bad\"?  Puzzled...\n"},{"id":"387521","messageId":"xmqqmuc73gk7.fsf@gitster-ct.c.googlers.com","threadId":"51492","inReplyTo":"20191202234759.26201-1-alban.gruin@gmail.com","subject":"Re: [PATCH v3 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-12-04T21:51:36Z","receivedAt":"2019-12-04T21:51:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Alban Gruin <alban.gruin@gmail.com> writes:\n\n> To prevent mistakes when editing a branch, rebase features a knob,\n> rebase.missingCommitsCheck, to warn the user if a commit was dropped.\n> Unfortunately, this check is only effective for the initial edit, which\n> means that if you edit the todo list at a later point of the rebase and\n> drop a commit, no warnings or errors would be issued.\n> ...\n>  rebase-interactive.c          | 57 ++++++++++++++++++++----\n>  rebase-interactive.h          |  2 +\n>  sequencer.c                   | 53 ++++++----------------\n>  sequencer.h                   |  1 -\n>  t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n>  5 files changed, 147 insertions(+), 49 deletions(-)\n\nThis passes the self-test when tested by itself, but when merged\nnear the tip of 'pu', it breaks t3404.116, it seems.\n"},{"id":"387591","messageId":"580f1a75-be77-8720-b368-e87a70fe8fec@gmail.com","threadId":"51492","inReplyTo":"xmqqmuc73gk7.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2019-12-05T23:15:49Z","receivedAt":"2019-12-05T23:16:05Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Junio,\n\nLe 04/12/2019 à 22:51, Junio C Hamano a écrit :\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> To prevent mistakes when editing a branch, rebase features a knob,\n>> rebase.missingCommitsCheck, to warn the user if a commit was dropped.\n>> Unfortunately, this check is only effective for the initial edit, which\n>> means that if you edit the todo list at a later point of the rebase and\n>> drop a commit, no warnings or errors would be issued.\n>> ...\n>>  rebase-interactive.c          | 57 ++++++++++++++++++++----\n>>  rebase-interactive.h          |  2 +\n>>  sequencer.c                   | 53 ++++++----------------\n>>  sequencer.h                   |  1 -\n>>  t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n>>  5 files changed, 147 insertions(+), 49 deletions(-)\n> \n> This passes the self-test when tested by itself, but when merged\n> near the tip of 'pu', it breaks t3404.116, it seems.\n> \n\nAfter a quick investigation, it comes from\npw/post-commit-from-sequencer.  Since then, tests are expected to setup\nthe editor and run the commands using it in a subshell.  So the fix is\nstraightforward.\n\nPerhaps I should take ag/sequencer-todo-updates, merge\npw/post-commit-from-sequencer, rebase this series onto the result, fix\nthe issue, and reroll the series?\n\nCheers,\nAlban\n\n"},{"id":"387599","messageId":"bc1fdd40-0490-eb65-247c-189b16865db5@gmail.com","threadId":"51492","inReplyTo":"580f1a75-be77-8720-b368-e87a70fe8fec@gmail.com","subject":"Re: [PATCH v3 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-12-06T10:41:51Z","receivedAt":"2019-12-06T10:41:57Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 05/12/2019 23:15, Alban Gruin wrote:\n> Hi Junio,\n> \n> Le 04/12/2019 à 22:51, Junio C Hamano a écrit :\n>> Alban Gruin <alban.gruin@gmail.com> writes:\n>>\n>>> To prevent mistakes when editing a branch, rebase features a knob,\n>>> rebase.missingCommitsCheck, to warn the user if a commit was dropped.\n>>> Unfortunately, this check is only effective for the initial edit, which\n>>> means that if you edit the todo list at a later point of the rebase and\n>>> drop a commit, no warnings or errors would be issued.\n>>> ...\n>>>   rebase-interactive.c          | 57 ++++++++++++++++++++----\n>>>   rebase-interactive.h          |  2 +\n>>>   sequencer.c                   | 53 ++++++----------------\n>>>   sequencer.h                   |  1 -\n>>>   t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n>>>   5 files changed, 147 insertions(+), 49 deletions(-)\n>>\n>> This passes the self-test when tested by itself, but when merged\n>> near the tip of 'pu', it breaks t3404.116, it seems.\n>>\n> \n> After a quick investigation, it comes from\n> pw/post-commit-from-sequencer.  Since then, tests are expected to setup\n> the editor and run the commands using it in a subshell.  So the fix is\n> straightforward.\n> \n> Perhaps I should take ag/sequencer-todo-updates, merge\n> pw/post-commit-from-sequencer, rebase this series onto the result, fix\n> the issue, and reroll the series?\n\nIf the issue is just using a subshell to set the editor then (assuming \nyou're only adding new tests) I don't think you need to rebase - just \nchange your tests and it should be fine when Junio merges it into pu. \nI'm sorry I've not looked at the latest version yet, I'll try and get \nround to it next week.\n\nBest Wishes\n\nPhillip\n> Cheers,\n> Alban\n> \n"},{"id":"387615","messageId":"nycvar.QRO.7.76.6.1912061529270.31080@tvgsbejvaqbjf.bet","threadId":"51492","inReplyTo":"bc1fdd40-0490-eb65-247c-189b16865db5@gmail.com","subject":"Re: [PATCH v3 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-12-06T14:30:51Z","receivedAt":"2019-12-06T14:31:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Fri, 6 Dec 2019, Phillip Wood wrote:\n\n> On 05/12/2019 23:15, Alban Gruin wrote:\n> >\n> > Le 04/12/2019 à 22:51, Junio C Hamano a écrit :\n> > > Alban Gruin <alban.gruin@gmail.com> writes:\n> > >\n> > > > To prevent mistakes when editing a branch, rebase features a knob,\n> > > > rebase.missingCommitsCheck, to warn the user if a commit was dropped.\n> > > > Unfortunately, this check is only effective for the initial edit, which\n> > > > means that if you edit the todo list at a later point of the rebase and\n> > > > drop a commit, no warnings or errors would be issued.\n> > > > ...\n> > > >   rebase-interactive.c          | 57 ++++++++++++++++++++----\n> > > >   rebase-interactive.h          |  2 +\n> > > >   sequencer.c                   | 53 ++++++----------------\n> > > >   sequencer.h                   |  1 -\n> > > >   t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n> > > >   5 files changed, 147 insertions(+), 49 deletions(-)\n> > >\n> > > This passes the self-test when tested by itself, but when merged\n> > > near the tip of 'pu', it breaks t3404.116, it seems.\n> > >\n> >\n> > After a quick investigation, it comes from\n> > pw/post-commit-from-sequencer.  Since then, tests are expected to setup\n> > the editor and run the commands using it in a subshell.  So the fix is\n> > straightforward.\n> >\n> > Perhaps I should take ag/sequencer-todo-updates, merge\n> > pw/post-commit-from-sequencer, rebase this series onto the result, fix\n> > the issue, and reroll the series?\n>\n> If the issue is just using a subshell to set the editor then (assuming you're\n> only adding new tests) I don't think you need to rebase - just change your\n> tests and it should be fine when Junio merges it into pu. I'm sorry I've not\n> looked at the latest version yet, I'll try and get round to it next week.\n\nJust squash this in:\n\n-- snipsnap --\nSubject: [PATCH] fixup??? rebase-interactive: warn if commit is dropped with\n `rebase --edit-todo'\n\nThis is required to appease the interaction with\n`pw/post-commit-from-sequencer`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t3404-rebase-interactive.sh | 74 +++++++++++++++++++----------------\n 1 file changed, 40 insertions(+), 34 deletions(-)\n\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex e3f64bc2a59..72718d0d839 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1453,10 +1453,12 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n \ttest_config rebase.missingCommitsCheck ignore &&\n \trebase_setup_and_clean missing-commit &&\n-\tset_fake_editor &&\n-\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n-\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n-\tgit rebase --continue 2>actual &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n \ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n \ttest_i18ngrep \\\n \t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n@@ -1474,18 +1476,20 @@ test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = wa\n \ttail -n4 expect >expect.2 &&\n \ttest_config rebase.missingCommitsCheck warn &&\n \trebase_setup_and_clean missing-commit &&\n-\tset_fake_editor &&\n-\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n-\t\tgit rebase -i --root &&\n-\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n-\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n-\thead -n5 actual.2 >actual &&\n-\ttest_i18ncmp expect actual &&\n-\tcp orig .git/rebase-merge/git-rebase-todo &&\n-\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n-\thead -n4 actual.2 >actual &&\n-\ttest_i18ncmp expect.2 actual &&\n-\tgit rebase --continue 2>actual &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n5 actual.2 >actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n4 actual.2 >actual &&\n+\t\ttest_i18ncmp expect.2 actual &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n \ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n \ttest_i18ngrep \\\n \t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n@@ -1509,24 +1513,26 @@ test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = er\n \ttail -n10 expect >expect.2 &&\n \ttest_config rebase.missingCommitsCheck error &&\n \trebase_setup_and_clean missing-commit &&\n-\tset_fake_editor &&\n-\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n-\t\tgit rebase -i --root &&\n-\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n-\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n-\t\tgit rebase --edit-todo 2>actual &&\n-\ttest_i18ncmp expect actual &&\n-\ttest_must_fail git rebase --continue 2>actual &&\n-\ttest_i18ncmp expect actual &&\n-\tcp orig .git/rebase-merge/git-rebase-todo &&\n-\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n-\t\tgit rebase --edit-todo 2>actual &&\n-\ttest_i18ncmp expect.2 actual &&\n-\ttest_must_fail git rebase --continue 2>actual &&\n-\ttest_i18ncmp expect.2 actual &&\n-\tcp orig .git/rebase-merge/git-rebase-todo &&\n-\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n-\tgit rebase --continue 2>actual &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect.2 actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect.2 actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n \ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n \ttest_i18ngrep \\\n \t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n--\n2.24.0.windows.2.611.ge9aced84530\n"},{"id":"387616","messageId":"nycvar.QRO.7.76.6.1912061533590.31080@tvgsbejvaqbjf.bet","threadId":"51492","inReplyTo":"20191202234759.26201-2-alban.gruin@gmail.com","subject":"Re: [PATCH v3 1/2] sequencer: move check_todo_list_from_file() to rebase-interactive.c","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-12-06T14:38:04Z","receivedAt":"2019-12-06T14:38:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Tue, 3 Dec 2019, Alban Gruin wrote:\n\n> The message contained in `edit_todo_list_advice' (sequencer.c) is\n> printed after the initial edit of the todo list if it can't be parsed or\n> if commits were dropped.  This is done either in complete_action() for\n> `rebase -i', or in check_todo_list_from_file() for `rebase -p'.\n>\n> Since we want to add this check when editing the list, we also want to\n> use this message from edit_todo_list() (rebase-interactive.c).  To this\n> end, check_todo_list_from_file() is moved to rebase-interactive.c, and\n> `edit_todo_list_advice' is copied there.  In the next commit,\n> complete_action() will stop using it, and `edit_todo_list_advice' will\n> be removed from sequencer.c.\n\nMakes sense to me.\n\n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index aa18ae82b7..ad5dd49c31 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -187,3 +193,32 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n>  \tclear_commit_seen(&commit_seen);\n>  \treturn res;\n>  }\n> +\n> +int check_todo_list_from_file(struct repository *r)\n> +{\n> +\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n> +\tint res = 0;\n> +\n> +\tif (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {\n> +\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo());\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tif (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {\n> +\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo_backup());\n> +\t\tgoto out;\n> +\t}\n> +\n> +\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n> +\tif (!res)\n> +\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n> +\tif (!res)\n> +\t\tres = todo_list_check(&old_todo, &new_todo);\n> +\tif (res)\n> +\t\tfprintf(stderr, _(edit_todo_list_advice));\n> +out:\n> +\ttodo_list_release(&old_todo);\n> +\ttodo_list_release(&new_todo);\n> +\n> +\treturn res;\n> +}\n\nNo need to address the following concern in this patch series, but I do\nthink that a #leftoverbits project could be to simplify this to\n\n\tif (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0)\n\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo());\n\telse if (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0)\n\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo_backup());\n\telse if ((res = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo)) ||\n\t\t (res = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo)) ||\n\t\t (res = todo_list_check(&old_todo, &new_todo)))\n\t\tfprintf(stderr, _(edit_todo_list_advice));\n\nCiao,\nDscho\n"},{"id":"387793","messageId":"64aa4049-ee35-df4c-1e6c-80707f4f9070@gmail.com","threadId":"51492","inReplyTo":"xmqqk17b5263.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-12-09T16:00:14Z","receivedAt":"2019-12-09T16:00:22Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban and Junio\n\nOn 04/12/2019 19:19, Junio C Hamano wrote:\n> Alban Gruin <alban.gruin@gmail.com> writes:\n> \n>> When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n>> `rebase -i' warn if the user removed commits from the todo list to\n>> prevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n>> --continue' don't take it into account.\n>>\n>> This adds the ability for `rebase --edit-todo' and `rebase --continue'\n>> to check if commits were dropped by the user.  As both edit_todo_list()\n>> and complete_action() parse the todo list and check for dropped commits,\n>> the code doing so in the latter is removed to reduce duplication.\n>> `edit_todo_list_advice' is removed from sequencer.c as it is no longer\n>> used there.\n>>\n>> This changes when a backup of the todo list is made.  Until now, it was\n>> saved only before the initial edit.  Now, it is always performed before\n>> the todo list is edited.  Without this, sequencer_continue() (`rebase\n>> --continue') could only compare the current todo list against the\n>> original, unedited list.  Before this change, this file was only used by\n>> edit_todo_list() and `rebase -p' to create the backup before the initial\n>> edit, and check_todo_list_from_file(), only used by `rebase -p' to check\n>> for dropped commits after its own initial edit.\n>>\n>> Three tests are added to t3404.  The tests for\n>> `rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\n>> error' have a similar structure.  First, we start a rebase with an\n>> incorrect command on the first line.  Then, we edit the todo list,\n>> removing the first and the last lines.  This demonstrates that\n>> `--edit-todo' notices dropped commits, but not when the command is\n>> incorrect.  Then, we restore the original todo list, and edit it to\n>> remove the last line.  This demonstrates that if we add a commit after\n>> the initial edit, then remove it, `--edit-todo' will notice that it has\n>> been dropped.  Then, the actual rebase takes place.  In the third test,\n>> it is also checked that `--continue' will refuse to resume the rebase if\n>> commits were dropped.\n>>\n>> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n>> ---\n>>   rebase-interactive.c          | 22 ++++++----\n>>   sequencer.c                   | 24 +++++-----\n>>   t/t3404-rebase-interactive.sh | 83 +++++++++++++++++++++++++++++++++++\n>>   3 files changed, 110 insertions(+), 19 deletions(-)\n> \n> Let me see if I understand the primary idea behind this change by\n> trying to paraphrase the log (read: this is not to suggest a better\n> phrasing of the log message proposed in your message).\n> \n>   * rebase-interactive.c::edit_todo_list() does not perform \"did the\n>     user delete a pick, instead of turning pick into drop?\" but after\n>     the end-user edits the file is the most logical place to do so.\n>     Let's do that there.\n> \n>   * The sequencer used to perform \"did the user delete a pick,\n>     instead of turning pick into drop?\" check in complete_action().\n>     We drop that call but for this particular codepath it does not\n>     make any behaviour difference due to the next item.\n> \n>   * New code does the check in sequencer_continue(), which is called\n>     at the end of complete_action(), as well as many other places,\n>     like builtin/rebase.c, builtin/revert.c, and sequencer_skip().\n>     Because the check is only done when we are running \"rebase-i\",\n>     this is safe---it only affects complete_action().\n> \n> I hope I got it more-or-less correctly ;-)\n> \n>> diff --git a/rebase-interactive.c b/rebase-interactive.c\n>> index ad5dd49c31..80b6a2e7a6 100644\n>> --- a/rebase-interactive.c\n>> +++ b/rebase-interactive.c\n>> @@ -97,7 +97,8 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>>   \t\t   struct todo_list *new_todo, const char *shortrevisions,\n>>   \t\t   const char *shortonto, unsigned flags)\n>>   {\n>> -\tconst char *todo_file = rebase_path_todo();\n>> +\tconst char *todo_file = rebase_path_todo(),\n>> +\t\t*todo_backup = rebase_path_todo_backup();\n>>   \t/* If the user is editing the todo list, we first try to parse\n>> @@ -110,9 +111,9 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>>   \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n>>   \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n>>   \n>> -\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n>> -\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>> -\t\t\t     rebase_path_todo_backup());\n>> +\tunlink(todo_backup);\n>> +\tif (copy_file(todo_backup, todo_file, 0666))\n>> +\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n> \n> We used to copy ONLY when initial is set and we left old todo_backup\n> intact when !initial.  That is no longer true after this change, but\n> it is intended---we create an exact copy of what we would hand out\n> to the end-user, so that we can compare it with the edited result\n> to figure out what got changed.\n\nI think it would be better to only create a new copy if the last edit \nwas successful. As it stands if I edit the todo list and accidentally \ndelete some lines and then edit the todo list again to try and fix it \nthe second edit will succeed whether or not I reinserted the deleted lines.\n\nWe could add this to the tests to check that a subsequent edit that does \nnot fix the problem fails\n\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 969e12d281..8544d8ab2c 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n\n@@ -1416,6 +1416,7 @@ test_expect_success 'rebase --edit-todo respects \nrebase.missingCommitsCheck = er\n                 test_i18ncmp expect actual &&\n                 test_must_fail git rebase --continue 2>actual &&\n                 test_i18ncmp expect actual &&\n+               test_must_fail git rebase --edit-todo &&\n                 cp orig .git/rebase-merge/git-rebase-todo &&\n                 test_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n                         git rebase --edit-todo 2>actual &&\n\n\n> \n> We unlink(2) unconditionally because the only effect we want to see\n> here is that todo_backup does not exist before we call copy_file()\n> that wants to do O_CREAT|O_EXCL.  I wonder if we want to avoid\n> unlink() when initial, and also if we want to do unlink_or_warn()\n> when !initial (read: this is just \"wondering\" without thinking long\n> enough to suggest that doing so would be better)\n> \n>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n>> index 29a35840ed..9051c1e11d 100755\n>> --- a/t/t3404-rebase-interactive.sh\n>> +++ b/t/t3404-rebase-interactive.sh\n>> @@ -1343,6 +1343,89 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n>>   \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>>   '\n>>   \n>> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n>> +\ttest_config rebase.missingCommitsCheck ignore &&\n>> +\trebase_setup_and_clean missing-commit &&\n>> +\tset_fake_editor &&\n>> +\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n>> +\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n> \n> OK, so we lost \"pick 5\" but with missing-check disabled, that should\n> not trigger any annoying warning or error.\n> \n>> +\tgit rebase --continue 2>actual &&\n\nThis clobbers actual which hasn't been used yet\n\n>> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> \n>> +\ttest_i18ngrep \\\n>> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n>> +\t\tactual\n>> +'\n>> +\n>> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n>> +\tcat >expect <<-EOF &&\n>> +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n>> +\tWarning: some commits may have been dropped accidentally.\n>> +\tDropped commits (newer to older):\n>> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n>> +\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n>> +\tEOF\n>> +\ttail -n4 expect >expect.2 &&\n>> +\ttest_config rebase.missingCommitsCheck warn &&\n>> +\trebase_setup_and_clean missing-commit &&\n>> +\tset_fake_editor &&\n>> +\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n>> +\t\tgit rebase -i --root &&\n>> +\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n>> +\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n>> +\thead -n5 actual.2 >actual &&\n>> +\ttest_i18ncmp expect actual &&\n> \n> OK, so we lost \"pick 1\" while discarding \"bad\", and we should notice\n> the lossage?  I see \"head -n5\" there, which means we are still\n> getting \"invalid line 1: badcmd\", even though FAKE_LINES now got rid\n> of \"bad\"?  Puzzled...\n\nIs the bad there to stop the rebase so we can edit the todo list? If so \nit would be better to use 'break' instead.\n\nBest Wishes\n\nPhillip\n"},{"id":"387794","messageId":"b399fe9c-0b76-c7ac-7f88-48b31a7e2ce3@gmail.com","threadId":"51492","inReplyTo":"20191202234759.26201-3-alban.gruin@gmail.com","subject":"Re: [PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-12-09T16:08:24Z","receivedAt":"2019-12-09T16:08:31Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 02/12/2019 23:47, Alban Gruin wrote:\n > [...]\n> diff --git a/sequencer.c b/sequencer.c\n> index 181bb35f5f..75d5ad0496 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -4271,8 +4271,20 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n>   \tif (read_populate_opts(opts))\n>   \t\treturn -1;\n>   \tif (is_rebase_i(opts)) {\n> +\t\tstruct todo_list backup = TODO_LIST_INIT;\n> +\n>   \t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n>   \t\t\tgoto release_todo_list;\n> +\n> +\t\tif (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {\n> +\t\t\ttodo_list_parse_insn_buffer(r, backup.buf.buf, &backup);\n> +\t\t\tres = todo_list_check(&backup, &todo_list);\n> +\t\t\ttodo_list_release(&backup);\n\nThis causes problems if the user edits the todo list and then later runs \n'git rebase --continue' after resolving conflicts as the backup todo \nlist has a bunch of commands that have already been processed but they \nare seen as missing\n\nThis test fails\n\ntest_expect_success 'rebase.missingCommitsCheck = error after resolving \nconflicts' '\n\ttest_config rebase.missingCommitsCheck error &&\n\t(\n\t\tset_fake_editor &&\n\t\tFAKE_LINES=\"break 2 3 4\" git rebase -i A E\n\t) &&\n\tgit rebase --edit-todo &&\n\ttest_must_fail git rebase --continue &&\n\techo x >file1 &&\n\tgit add file1 &&\n\tgit rebase --continue\n'\n\nI think it would be better to write a file if the check fails when \nediting the todo list and check for the presence of that file when \ncontinuing and error out if it exists. This would also allow --edit-todo \nto only remove the backup file if there are no outstanding errors from \nthe last edit and so check that those errors are fixed by the second \nedit. I think we'd only want to do this if rebase.missingCommitsCheck is \nset to error.\n\nBest Wishes\n\nPhillip\n\n> +\n> +\t\t\tif (res)\n> +\t\t\t\tgoto release_todo_list;\n> +\t\t}\n> +\n>   \t\tif (commit_staged_changes(r, opts, &todo_list))\n>   \t\t\treturn -1;\n>   \t} else if (!file_exists(get_todo_path(opts)))\n> @@ -4986,12 +4998,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n>   \treturn res;\n>   }\n>   \n> -static const char edit_todo_list_advice[] =\n> -N_(\"You can fix this with 'git rebase --edit-todo' \"\n> -\"and then run 'git rebase --continue'.\\n\"\n> -\"Or you can abort the rebase with 'git rebase\"\n> -\" --abort'.\\n\");\n> -\n>   /* skip picking commits whose parents are unchanged */\n>   static int skip_unnecessary_picks(struct repository *r,\n>   \t\t\t\t  struct todo_list *todo_list,\n> @@ -5089,11 +5095,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>   \t\ttodo_list_release(&new_todo);\n>   \n>   \t\treturn error(_(\"nothing to do\"));\n> -\t}\n> -\n> -\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n> -\t    todo_list_check(todo_list, &new_todo)) {\n> -\t\tfprintf(stderr, _(edit_todo_list_advice));\n> +\t} else if (res == -4) {\n>   \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n>   \t\ttodo_list_release(&new_todo);\n>   \n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 29a35840ed..9051c1e11d 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1343,6 +1343,89 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n>   \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>   '\n>   \n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n> +\ttest_config rebase.missingCommitsCheck ignore &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n> +\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n> +\tgit rebase --continue 2>actual &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n> +\tcat >expect <<-EOF &&\n> +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n> +\tWarning: some commits may have been dropped accidentally.\n> +\tDropped commits (newer to older):\n> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> +\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n> +\tEOF\n> +\ttail -n4 expect >expect.2 &&\n> +\ttest_config rebase.missingCommitsCheck warn &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n> +\t\tgit rebase -i --root &&\n> +\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n> +\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n> +\thead -n5 actual.2 >actual &&\n> +\ttest_i18ncmp expect actual &&\n> +\tcp orig .git/rebase-merge/git-rebase-todo &&\n> +\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n> +\thead -n4 actual.2 >actual &&\n> +\ttest_i18ncmp expect.2 actual &&\n> +\tgit rebase --continue 2>actual &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n> +\tcat >expect <<-EOF &&\n> +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n> +\tWarning: some commits may have been dropped accidentally.\n> +\tDropped commits (newer to older):\n> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> +\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n> +\n> +\tUse '\\''git config rebase.missingCommitsCheck'\\'' to change the level of warnings.\n> +\tThe possible behaviours are: ignore, warn, error.\n> +\n> +\tYou can fix this with '\\''git rebase --edit-todo'\\'' and then run '\\''git rebase --continue'\\''.\n> +\tOr you can abort the rebase with '\\''git rebase --abort'\\''.\n> +\tEOF\n> +\ttail -n10 expect >expect.2 &&\n> +\ttest_config rebase.missingCommitsCheck error &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\tset_fake_editor &&\n> +\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n> +\t\tgit rebase -i --root &&\n> +\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n> +\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n> +\t\tgit rebase --edit-todo 2>actual &&\n> +\ttest_i18ncmp expect actual &&\n> +\ttest_must_fail git rebase --continue 2>actual &&\n> +\ttest_i18ncmp expect actual &&\n> +\tcp orig .git/rebase-merge/git-rebase-todo &&\n> +\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n> +\t\tgit rebase --edit-todo 2>actual &&\n> +\ttest_i18ncmp expect.2 actual &&\n> +\ttest_must_fail git rebase --continue 2>actual &&\n> +\ttest_i18ncmp expect.2 actual &&\n> +\tcp orig .git/rebase-merge/git-rebase-todo &&\n> +\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n> +\tgit rebase --continue 2>actual &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n>   test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n>   \trebase_setup_and_clean abbrevcmd &&\n>   \ttest_commit \"first\" file1.txt \"first line\" first &&\n> \n"},{"id":"389548","messageId":"0bf602bb-eff0-12f1-fa6c-c670a12f2cee@gmail.com","threadId":"51492","inReplyTo":"64aa4049-ee35-df4c-1e6c-80707f4f9070@gmail.com","subject":"Re: [PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-09T21:13:58Z","receivedAt":"2020-01-09T21:14:21Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nLe 09/12/2019 à 17:00, Phillip Wood a écrit :\n>>> diff --git a/rebase-interactive.c b/rebase-interactive.c\n>>> index ad5dd49c31..80b6a2e7a6 100644\n>>> --- a/rebase-interactive.c\n>>> +++ b/rebase-interactive.c\n>>> @@ -97,7 +97,8 @@ int edit_todo_list(struct repository *r, struct\n>>> todo_list *todo_list,\n>>>              struct todo_list *new_todo, const char *shortrevisions,\n>>>              const char *shortonto, unsigned flags)\n>>>   {\n>>> -    const char *todo_file = rebase_path_todo();\n>>> +    const char *todo_file = rebase_path_todo(),\n>>> +        *todo_backup = rebase_path_todo_backup();\n>>>       /* If the user is editing the todo list, we first try to parse\n>>> @@ -110,9 +111,9 @@ int edit_todo_list(struct repository *r, struct\n>>> todo_list *todo_list,\n>>>                       -1, flags | TODO_LIST_SHORTEN_IDS |\n>>> TODO_LIST_APPEND_TODO_HELP))\n>>>           return error_errno(_(\"could not write '%s'\"), todo_file);\n>>>   -    if (initial && copy_file(rebase_path_todo_backup(), todo_file,\n>>> 0666))\n>>> -        return error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>>> -                 rebase_path_todo_backup());\n>>> +    unlink(todo_backup);\n>>> +    if (copy_file(todo_backup, todo_file, 0666))\n>>> +        return error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>>> todo_backup);\n>>\n>> We used to copy ONLY when initial is set and we left old todo_backup\n>> intact when !initial.  That is no longer true after this change, but\n>> it is intended---we create an exact copy of what we would hand out\n>> to the end-user, so that we can compare it with the edited result\n>> to figure out what got changed.\n> \n> I think it would be better to only create a new copy if the last edit\n> was successful. As it stands if I edit the todo list and accidentally\n> delete some lines and then edit the todo list again to try and fix it\n> the second edit will succeed whether or not I reinserted the deleted lines.\n> \n> We could add this to the tests to check that a subsequent edit that does\n> not fix the problem fails\n> \n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 969e12d281..8544d8ab2c 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> \n> @@ -1416,6 +1416,7 @@ test_expect_success 'rebase --edit-todo respects\n> rebase.missingCommitsCheck = er\n>                 test_i18ncmp expect actual &&\n>                 test_must_fail git rebase --continue 2>actual &&\n>                 test_i18ncmp expect actual &&\n> +               test_must_fail git rebase --edit-todo &&\n>                 cp orig .git/rebase-merge/git-rebase-todo &&\n>                 test_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n>                         git rebase --edit-todo 2>actual &&\n> \n> \n\nIn which case, if the check did not pass at the previous edit, the new\ntodo list should be compared to the backup.  As sequencer_continue()\nalready does this, extract this to its own function in\nrebase-interactive.c.  To keep track of this, a file is created on the\ndisk (as you suggested in your other email.)  At the next edit, if this\nfile exists and no errors were found, it is deleted.  The backup is only\ncreated if there is no errors in `todo_list' and in `new_todo'.\n\nThis would guarantee that there is no errors in the backup, and that the\nedited list is always compared to a list exempt of errors.\n\nThis approach also has the benefit to detect if a commit part of a\nbadcmd was dropped.\n\nAfter some tweaks (ie. `expect' now lists 2 commits instead of one),\nthis passes the test with the change you suggested, and the one you sent\nin your other email.\n\n\n>>\n>> We unlink(2) unconditionally because the only effect we want to see\n>> here is that todo_backup does not exist before we call copy_file()\n>> that wants to do O_CREAT|O_EXCL.  I wonder if we want to avoid\n>> unlink() when initial, and also if we want to do unlink_or_warn()\n>> when !initial (read: this is just \"wondering\" without thinking long\n>> enough to suggest that doing so would be better)\n>>\n>>> diff --git a/t/t3404-rebase-interactive.sh\n>>> b/t/t3404-rebase-interactive.sh\n>>> index 29a35840ed..9051c1e11d 100755\n>>> --- a/t/t3404-rebase-interactive.sh\n>>> +++ b/t/t3404-rebase-interactive.sh\n>>> @@ -1343,6 +1343,89 @@ test_expect_success 'rebase -i respects\n>>> rebase.missingCommitsCheck = error' '\n>>>       test B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>>>   '\n>>>   +test_expect_success 'rebase --edit-todo respects\n>>> rebase.missingCommitsCheck = ignore' '\n>>> +    test_config rebase.missingCommitsCheck ignore &&\n>>> +    rebase_setup_and_clean missing-commit &&\n>>> +    set_fake_editor &&\n>>> +    FAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n>>> +    FAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n>>\n>> OK, so we lost \"pick 5\" but with missing-check disabled, that should\n>> not trigger any annoying warning or error.\n>>\n>>> +    git rebase --continue 2>actual &&\n> \n> This clobbers actual which hasn't been used yet\n> \n\nGood catch.\n\n>>> +    test D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n>>\n>>> +    test_i18ngrep \\\n>>> +        \"Successfully rebased and updated refs/heads/missing-commit\" \\\n>>> +        actual\n>>> +'\n>>> +\n>>> +test_expect_success 'rebase --edit-todo respects\n>>> rebase.missingCommitsCheck = warn' '\n>>> +    cat >expect <<-EOF &&\n>>> +    error: invalid line 1: badcmd $(git rev-list --pretty=oneline\n>>> --abbrev-commit -1 master~4)\n>>> +    Warning: some commits may have been dropped accidentally.\n>>> +    Dropped commits (newer to older):\n>>> +     - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n>>> +    To avoid this message, use \"drop\" to explicitly remove a commit.\n>>> +    EOF\n>>> +    tail -n4 expect >expect.2 &&\n>>> +    test_config rebase.missingCommitsCheck warn &&\n>>> +    rebase_setup_and_clean missing-commit &&\n>>> +    set_fake_editor &&\n>>> +    test_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n>>> +        git rebase -i --root &&\n>>> +    cp .git/rebase-merge/git-rebase-todo.backup orig &&\n>>> +    FAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n>>> +    head -n5 actual.2 >actual &&\n>>> +    test_i18ncmp expect actual &&\n>>\n>> OK, so we lost \"pick 1\" while discarding \"bad\", and we should notice\n>> the lossage?  I see \"head -n5\" there, which means we are still\n>> getting \"invalid line 1: badcmd\", even though FAKE_LINES now got rid\n>> of \"bad\"?  Puzzled...\n> \n> Is the bad there to stop the rebase so we can edit the todo list? If so\n> it would be better to use 'break' instead.\n> \n\nNo, it was here to show that we can detect dropped commits, even if the\ntodo list has an error.\n\n> Best Wishes\n> \n> Phillip\n\nCheers,\nAlban\n\n"},{"id":"389587","messageId":"79ad2e01-2fc3-cd52-7879-5a81370628ef@gmail.com","threadId":"51492","inReplyTo":"0bf602bb-eff0-12f1-fa6c-c670a12f2cee@gmail.com","subject":"Re: [PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-10T17:13:12Z","receivedAt":"2020-01-10T17:13:19Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 09/01/2020 21:13, Alban Gruin wrote:\n> Hi Phillip,\n> \n> Le 09/12/2019 à 17:00, Phillip Wood a écrit :\n>>>> diff --git a/rebase-interactive.c b/rebase-interactive.c\n>>>> index ad5dd49c31..80b6a2e7a6 100644\n>>>> --- a/rebase-interactive.c\n>>>> +++ b/rebase-interactive.c\n>>>> @@ -97,7 +97,8 @@ int edit_todo_list(struct repository *r, struct\n>>>> todo_list *todo_list,\n>>>>               struct todo_list *new_todo, const char *shortrevisions,\n>>>>               const char *shortonto, unsigned flags)\n>>>>    {\n>>>> -    const char *todo_file = rebase_path_todo();\n>>>> +    const char *todo_file = rebase_path_todo(),\n>>>> +        *todo_backup = rebase_path_todo_backup();\n>>>>        /* If the user is editing the todo list, we first try to parse\n>>>> @@ -110,9 +111,9 @@ int edit_todo_list(struct repository *r, struct\n>>>> todo_list *todo_list,\n>>>>                        -1, flags | TODO_LIST_SHORTEN_IDS |\n>>>> TODO_LIST_APPEND_TODO_HELP))\n>>>>            return error_errno(_(\"could not write '%s'\"), todo_file);\n>>>>    -    if (initial && copy_file(rebase_path_todo_backup(), todo_file,\n>>>> 0666))\n>>>> -        return error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>>>> -                 rebase_path_todo_backup());\n>>>> +    unlink(todo_backup);\n>>>> +    if (copy_file(todo_backup, todo_file, 0666))\n>>>> +        return error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>>>> todo_backup);\n>>>\n>>> We used to copy ONLY when initial is set and we left old todo_backup\n>>> intact when !initial.  That is no longer true after this change, but\n>>> it is intended---we create an exact copy of what we would hand out\n>>> to the end-user, so that we can compare it with the edited result\n>>> to figure out what got changed.\n>>\n>> I think it would be better to only create a new copy if the last edit\n>> was successful. As it stands if I edit the todo list and accidentally\n>> delete some lines and then edit the todo list again to try and fix it\n>> the second edit will succeed whether or not I reinserted the deleted lines.\n>>\n>> We could add this to the tests to check that a subsequent edit that does\n>> not fix the problem fails\n>>\n>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n>> index 969e12d281..8544d8ab2c 100755\n>> --- a/t/t3404-rebase-interactive.sh\n>> +++ b/t/t3404-rebase-interactive.sh\n>>\n>> @@ -1416,6 +1416,7 @@ test_expect_success 'rebase --edit-todo respects\n>> rebase.missingCommitsCheck = er\n>>                  test_i18ncmp expect actual &&\n>>                  test_must_fail git rebase --continue 2>actual &&\n>>                  test_i18ncmp expect actual &&\n>> +               test_must_fail git rebase --edit-todo &&\n>>                  cp orig .git/rebase-merge/git-rebase-todo &&\n>>                  test_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n>>                          git rebase --edit-todo 2>actual &&\n>>\n>>\n> \n> In which case, if the check did not pass at the previous edit, the new\n> todo list should be compared to the backup.  As sequencer_continue()\n> already does this, extract this to its own function in\n> rebase-interactive.c.  To keep track of this, a file is created on the\n> disk (as you suggested in your other email.)  At the next edit, if this\n> file exists and no errors were found, it is deleted.  The backup is only\n> created if there is no errors in `todo_list' and in `new_todo'.\n> \n> This would guarantee that there is no errors in the backup, and that the\n> edited list is always compared to a list exempt of errors.\n> \n> This approach also has the benefit to detect if a commit part of a\n> badcmd was dropped.\n> \n> After some tweaks (ie. `expect' now lists 2 commits instead of one),\n> this passes the test with the change you suggested, and the one you sent\n> in your other email.\n\nThat sounds good. I'm not sure how it passes the test in my other email \nthough, if sequencer_continue() compares the todo list to the backup \nwont it still fail when continuing after conflicts as the backup is out \nof date?\n\nBest Wishes\n\nPhillip\n\n\n>>>\n>>> We unlink(2) unconditionally because the only effect we want to see\n>>> here is that todo_backup does not exist before we call copy_file()\n>>> that wants to do O_CREAT|O_EXCL.  I wonder if we want to avoid\n>>> unlink() when initial, and also if we want to do unlink_or_warn()\n>>> when !initial (read: this is just \"wondering\" without thinking long\n>>> enough to suggest that doing so would be better)\n>>>\n>>>> diff --git a/t/t3404-rebase-interactive.sh\n>>>> b/t/t3404-rebase-interactive.sh\n>>>> index 29a35840ed..9051c1e11d 100755\n>>>> --- a/t/t3404-rebase-interactive.sh\n>>>> +++ b/t/t3404-rebase-interactive.sh\n>>>> @@ -1343,6 +1343,89 @@ test_expect_success 'rebase -i respects\n>>>> rebase.missingCommitsCheck = error' '\n>>>>        test B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>>>>    '\n>>>>    +test_expect_success 'rebase --edit-todo respects\n>>>> rebase.missingCommitsCheck = ignore' '\n>>>> +    test_config rebase.missingCommitsCheck ignore &&\n>>>> +    rebase_setup_and_clean missing-commit &&\n>>>> +    set_fake_editor &&\n>>>> +    FAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n>>>> +    FAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual &&\n>>>\n>>> OK, so we lost \"pick 5\" but with missing-check disabled, that should\n>>> not trigger any annoying warning or error.\n>>>\n>>>> +    git rebase --continue 2>actual &&\n>>\n>> This clobbers actual which hasn't been used yet\n>>\n> \n> Good catch.\n> \n>>>> +    test D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n>>>\n>>>> +    test_i18ngrep \\\n>>>> +        \"Successfully rebased and updated refs/heads/missing-commit\" \\\n>>>> +        actual\n>>>> +'\n>>>> +\n>>>> +test_expect_success 'rebase --edit-todo respects\n>>>> rebase.missingCommitsCheck = warn' '\n>>>> +    cat >expect <<-EOF &&\n>>>> +    error: invalid line 1: badcmd $(git rev-list --pretty=oneline\n>>>> --abbrev-commit -1 master~4)\n>>>> +    Warning: some commits may have been dropped accidentally.\n>>>> +    Dropped commits (newer to older):\n>>>> +     - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n>>>> +    To avoid this message, use \"drop\" to explicitly remove a commit.\n>>>> +    EOF\n>>>> +    tail -n4 expect >expect.2 &&\n>>>> +    test_config rebase.missingCommitsCheck warn &&\n>>>> +    rebase_setup_and_clean missing-commit &&\n>>>> +    set_fake_editor &&\n>>>> +    test_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n>>>> +        git rebase -i --root &&\n>>>> +    cp .git/rebase-merge/git-rebase-todo.backup orig &&\n>>>> +    FAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n>>>> +    head -n5 actual.2 >actual &&\n>>>> +    test_i18ncmp expect actual &&\n>>>\n>>> OK, so we lost \"pick 1\" while discarding \"bad\", and we should notice\n>>> the lossage?  I see \"head -n5\" there, which means we are still\n>>> getting \"invalid line 1: badcmd\", even though FAKE_LINES now got rid\n>>> of \"bad\"?  Puzzled...\n>>\n>> Is the bad there to stop the rebase so we can edit the todo list? If so\n>> it would be better to use 'break' instead.\n>>\n> \n> No, it was here to show that we can detect dropped commits, even if the\n> todo list has an error.\n> \n>> Best Wishes\n>>\n>> Phillip\n> \n> Cheers,\n> Alban\n> \n"},{"id":"389597","messageId":"85e3d40a-9829-0f50-8d91-7e8e8fa319f1@gmail.com","threadId":"51492","inReplyTo":"79ad2e01-2fc3-cd52-7879-5a81370628ef@gmail.com","subject":"Re: [PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-10T21:31:03Z","receivedAt":"2020-01-10T21:31:22Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nLe 10/01/2020 à 18:13, Phillip Wood a écrit :\n> Hi Alban\n> \n> On 09/01/2020 21:13, Alban Gruin wrote:\n>> Hi Phillip,\n>>\n>> Le 09/12/2019 à 17:00, Phillip Wood a écrit :\n>>>>> diff --git a/rebase-interactive.c b/rebase-interactive.c\n>>>>> index ad5dd49c31..80b6a2e7a6 100644\n>>>>> --- a/rebase-interactive.c\n>>>>> +++ b/rebase-interactive.c\n>>>>> @@ -97,7 +97,8 @@ int edit_todo_list(struct repository *r, struct\n>>>>> todo_list *todo_list,\n>>>>>               struct todo_list *new_todo, const char *shortrevisions,\n>>>>>               const char *shortonto, unsigned flags)\n>>>>>    {\n>>>>> -    const char *todo_file = rebase_path_todo();\n>>>>> +    const char *todo_file = rebase_path_todo(),\n>>>>> +        *todo_backup = rebase_path_todo_backup();\n>>>>>        /* If the user is editing the todo list, we first try to parse\n>>>>> @@ -110,9 +111,9 @@ int edit_todo_list(struct repository *r, struct\n>>>>> todo_list *todo_list,\n>>>>>                        -1, flags | TODO_LIST_SHORTEN_IDS |\n>>>>> TODO_LIST_APPEND_TODO_HELP))\n>>>>>            return error_errno(_(\"could not write '%s'\"), todo_file);\n>>>>>    -    if (initial && copy_file(rebase_path_todo_backup(), todo_file,\n>>>>> 0666))\n>>>>> -        return error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>>>>> -                 rebase_path_todo_backup());\n>>>>> +    unlink(todo_backup);\n>>>>> +    if (copy_file(todo_backup, todo_file, 0666))\n>>>>> +        return error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>>>>> todo_backup);\n>>>>\n>>>> We used to copy ONLY when initial is set and we left old todo_backup\n>>>> intact when !initial.  That is no longer true after this change, but\n>>>> it is intended---we create an exact copy of what we would hand out\n>>>> to the end-user, so that we can compare it with the edited result\n>>>> to figure out what got changed.\n>>>\n>>> I think it would be better to only create a new copy if the last edit\n>>> was successful. As it stands if I edit the todo list and accidentally\n>>> delete some lines and then edit the todo list again to try and fix it\n>>> the second edit will succeed whether or not I reinserted the deleted\n>>> lines.\n>>>\n>>> We could add this to the tests to check that a subsequent edit that does\n>>> not fix the problem fails\n>>>\n>>> diff --git a/t/t3404-rebase-interactive.sh\n>>> b/t/t3404-rebase-interactive.sh\n>>> index 969e12d281..8544d8ab2c 100755\n>>> --- a/t/t3404-rebase-interactive.sh\n>>> +++ b/t/t3404-rebase-interactive.sh\n>>>\n>>> @@ -1416,6 +1416,7 @@ test_expect_success 'rebase --edit-todo respects\n>>> rebase.missingCommitsCheck = er\n>>>                  test_i18ncmp expect actual &&\n>>>                  test_must_fail git rebase --continue 2>actual &&\n>>>                  test_i18ncmp expect actual &&\n>>> +               test_must_fail git rebase --edit-todo &&\n>>>                  cp orig .git/rebase-merge/git-rebase-todo &&\n>>>                  test_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n>>>                          git rebase --edit-todo 2>actual &&\n>>>\n>>>\n>>\n>> In which case, if the check did not pass at the previous edit, the new\n>> todo list should be compared to the backup.  As sequencer_continue()\n>> already does this, extract this to its own function in\n>> rebase-interactive.c.  To keep track of this, a file is created on the\n>> disk (as you suggested in your other email.)  At the next edit, if this\n>> file exists and no errors were found, it is deleted.  The backup is only\n>> created if there is no errors in `todo_list' and in `new_todo'.\n>>\n>> This would guarantee that there is no errors in the backup, and that the\n>> edited list is always compared to a list exempt of errors.\n>>\n>> This approach also has the benefit to detect if a commit part of a\n>> badcmd was dropped.\n>>\n>> After some tweaks (ie. `expect' now lists 2 commits instead of one),\n>> this passes the test with the change you suggested, and the one you sent\n>> in your other email.\n> \n> That sounds good. I'm not sure how it passes the test in my other email\n> though, if sequencer_continue() compares the todo list to the backup\n> wont it still fail when continuing after conflicts as the backup is out\n> of date?\n> \n\nI changed sequencer_continue() to check the todo list only if the file\nindicating an error exists.\n\nI still have to rewrite the commit message, then I’ll re-send this series.\n\nCheers,\nAlban\n\n> Best Wishes\n> \n> Phillip\n> \n"},{"id":"389610","messageId":"1e72a306-3421-f4e7-4330-280f2a3fc821@gmail.com","threadId":"51492","inReplyTo":"85e3d40a-9829-0f50-8d91-7e8e8fa319f1@gmail.com","subject":"Re: [PATCH v3 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-11T14:44:13Z","receivedAt":"2020-01-11T14:45:06Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 10/01/2020 21:31, Alban Gruin wrote:\n> Hi Phillip,\n> \n> Le 10/01/2020 à 18:13, Phillip Wood a écrit :\n>> Hi Alban\n>>\n>> On 09/01/2020 21:13, Alban Gruin wrote:\n>>> Hi Phillip,\n>>> [...]\n>>>\n>>> In which case, if the check did not pass at the previous edit, the new\n>>> todo list should be compared to the backup.  As sequencer_continue()\n>>> already does this, extract this to its own function in\n>>> rebase-interactive.c.  To keep track of this, a file is created on the\n>>> disk (as you suggested in your other email.)  At the next edit, if this\n>>> file exists and no errors were found, it is deleted.  The backup is only\n>>> created if there is no errors in `todo_list' and in `new_todo'.\n>>>\n>>> This would guarantee that there is no errors in the backup, and that the\n>>> edited list is always compared to a list exempt of errors.\n>>>\n>>> This approach also has the benefit to detect if a commit part of a\n>>> badcmd was dropped.\n>>>\n>>> After some tweaks (ie. `expect' now lists 2 commits instead of one),\n>>> this passes the test with the change you suggested, and the one you sent\n>>> in your other email.\n>>\n>> That sounds good. I'm not sure how it passes the test in my other email\n>> though, if sequencer_continue() compares the todo list to the backup\n>> wont it still fail when continuing after conflicts as the backup is out\n>> of date?\n>>\n> \n> I changed sequencer_continue() to check the todo list only if the file\n> indicating an error exists.\n\nThat makes sense\n\n> I still have to rewrite the commit message, then I’ll re-send this series.\n\nExcellent, I look forward to reading them\n\nBest Wishes\n\nPhillip\n\n> Cheers,\n> Alban\n> \n>> Best Wishes\n>>\n>> Phillip\n>>\n"},{"id":"389613","messageId":"20200111173917.15690-1-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20191202234759.26201-1-alban.gruin@gmail.com","subject":"[PATCH v4 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-11T17:39:15Z","receivedAt":"2020-01-11T17:40:21Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"To prevent mistakes when editing a branch, rebase features a knob,\nrebase.missingCommitsCheck, to warn the user if a commit was dropped.\nUnfortunately, this check is only effective for the initial edit, which\nmeans that if you edit the todo list at a later point of the rebase and\ndrop a commit, no warnings or errors would be issued.\n\nThis adds the ability to check if commits were dropped when editing the\ntodo list with `--edit-todo', and when resuming a rebase.\n\nThe first patch moves moves check_todo_list_from_file() and\n`edit_todo_list_advice' from sequencer.c to rebase-interactive.c so the\nlatter can be used by edit_todo_list() and todo_list_check().  The\nsecond patch adds the check to `--edit-todo' and `--continue' and tests.\n\nThis is based on 393adf7a6f (\"sequencer: directly call pick_commits()\nfrom complete_action()\", 2019-11-24).\n\nThe tip of this series is tagged as \"edit-todo-drop-v4\" at\nhttps://github.com/agrn/git.\n\nChanges since v3:\n\n - Set the fake editor in a subshell in the tests to avoid conflicts with\n   pw/post-commit-from-sequencer.\n\n - Added the `dropped' file to indicate that the todo list is incorrect\n   (ie. no invalid commands or dropped commits.)\n\n - The todo list is copied only if it is correct.\n\n - sequencer_continue() checks the todo list for dropped commits only\n   when `dropped' exists.\n\n - Added a new test to check that `rebase --continue' does not warn for\n   dropped commits after solving a conflict.  This was provided by\n   Phillip Wood.\n\n - check_todo_list_from_file() no longer prints `edit_todo_list_advice'\n   if there is dropped commits since todo_list_check() do it now.\n\nAlban Gruin (2):\n  sequencer: move check_todo_list_from_file() to rebase-interactive.c\n  rebase-interactive: warn if commit is dropped with `rebase\n    --edit-todo'\n\n rebase-interactive.c          |  89 ++++++++++++++++++++++++---\n rebase-interactive.h          |   5 ++\n sequencer.c                   |  51 ++++------------\n sequencer.h                   |   2 +-\n t/t3404-rebase-interactive.sh | 109 ++++++++++++++++++++++++++++++++++\n 5 files changed, 206 insertions(+), 50 deletions(-)\n\nDiff-intervalle contre v3:\n1:  996045a300 = 1:  996045a300 sequencer: move check_todo_list_from_file() to rebase-interactive.c\n2:  ba6d27e5b4 < -:  ---------- rebase-interactive: warn if commit is dropped with `rebase --edit-todo'\n-:  ---------- > 2:  11b0e1e78c rebase-interactive: warn if commit is dropped with `rebase --edit-todo'\n-- \n2.24.1\n\n"},{"id":"389614","messageId":"20200111173917.15690-2-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200111173917.15690-1-alban.gruin@gmail.com","subject":"[PATCH v4 1/2] sequencer: move check_todo_list_from_file() to rebase-interactive.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-11T17:39:16Z","receivedAt":"2020-01-11T17:40:22Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The message contained in `edit_todo_list_advice' (sequencer.c) is\nprinted after the initial edit of the todo list if it can't be parsed or\nif commits were dropped.  This is done either in complete_action() for\n`rebase -i', or in check_todo_list_from_file() for `rebase -p'.\n\nSince we want to add this check when editing the list, we also want to\nuse this message from edit_todo_list() (rebase-interactive.c).  To this\nend, check_todo_list_from_file() is moved to rebase-interactive.c, and\n`edit_todo_list_advice' is copied there.  In the next commit,\ncomplete_action() will stop using it, and `edit_todo_list_advice' will\nbe removed from sequencer.c.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c | 35 +++++++++++++++++++++++++++++++++++\n rebase-interactive.h |  2 ++\n sequencer.c          | 29 -----------------------------\n sequencer.h          |  1 -\n 4 files changed, 37 insertions(+), 30 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..ad5dd49c31 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -6,6 +6,12 @@\n #include \"commit-slab.h\"\n #include \"config.h\"\n \n+static const char edit_todo_list_advice[] =\n+N_(\"You can fix this with 'git rebase --edit-todo' \"\n+\"and then run 'git rebase --continue'.\\n\"\n+\"Or you can abort the rebase with 'git rebase\"\n+\" --abort'.\\n\");\n+\n enum missing_commit_check_level {\n \tMISSING_COMMIT_CHECK_IGNORE = 0,\n \tMISSING_COMMIT_CHECK_WARN,\n@@ -187,3 +193,32 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n+\n+int check_todo_list_from_file(struct repository *r)\n+{\n+\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n+\tint res = 0;\n+\n+\tif (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo());\n+\t\tgoto out;\n+\t}\n+\n+\tif (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo_backup());\n+\t\tgoto out;\n+\t}\n+\n+\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n+\tif (!res)\n+\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n+\tif (!res)\n+\t\tres = todo_list_check(&old_todo, &new_todo);\n+\tif (res)\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+out:\n+\ttodo_list_release(&old_todo);\n+\ttodo_list_release(&new_todo);\n+\n+\treturn res;\n+}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..5f41bf5a28 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -13,4 +13,6 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   const char *shortonto, unsigned flags);\n int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n \n+int check_todo_list_from_file(struct repository *r);\n+\n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex ec0b793fc5..181bb35f5f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4992,35 +4992,6 @@ N_(\"You can fix this with 'git rebase --edit-todo' \"\n \"Or you can abort the rebase with 'git rebase\"\n \" --abort'.\\n\");\n \n-int check_todo_list_from_file(struct repository *r)\n-{\n-\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n-\tint res = 0;\n-\n-\tif (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tif (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n-\tif (!res)\n-\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n-\tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n-\tif (res)\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n-out:\n-\ttodo_list_release(&old_todo);\n-\ttodo_list_release(&new_todo);\n-\n-\treturn res;\n-}\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\ndiff --git a/sequencer.h b/sequencer.h\nindex 574260f621..75ddc5db3a 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -155,7 +155,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \n void todo_list_add_exec_commands(struct todo_list *todo_list,\n \t\t\t\t struct string_list *commands);\n-int check_todo_list_from_file(struct repository *r);\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const char *orig_head, struct string_list *commands,\n-- \n2.24.1\n\n"},{"id":"389615","messageId":"20200111173917.15690-3-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200111173917.15690-1-alban.gruin@gmail.com","subject":"[PATCH v4 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-11T17:39:17Z","receivedAt":"2020-01-11T17:40:22Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n`rebase -i' warn if the user removed commits from the todo list to\nprevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n--continue' don't take it into account.\n\nThis adds the ability for `rebase --edit-todo' and `rebase --continue'\nto check if commits were dropped by the user.  As both edit_todo_list()\nand complete_action() parse the todo list and check for dropped commits,\nthe code doing so in the latter is removed to reduce duplication.\n`edit_todo_list_advice' is removed from sequencer.c as it is no longer\nused there.\n\nThis changes when a backup of the todo list is made.  Until now, it\nwas saved only once, before the initial edit.  Now, it is also made if\nafter the user edited the list, if it has no errors or if no commits\nwere dropped and `rebase.missingCommitsCheck' is set.  Thus, the\nbackup should be error-free.  Without this, sequencer_continue()\n(`rebase --continue') could only compare the current todo list against\nthe original, unedited list.  Before this change, this file was only\nused by edit_todo_list() and `rebase -p' to create the backup before\nthe initial edit, and check_todo_list_from_file(), only used by\n`rebase -p' to check for dropped commits after its own initial edit.\n\nIf the edited list has an error, a file, `dropped', is created to\nreport the issue.  Otherwise, it is deleted.  Usually, the edited list\nis compared against the list before editing, but if this file exists,\nit will be compared to the backup.  Also, if the file exists,\nsequencer_continue() checks the list for dropped commits.  If the\ncheck was performed every time, it would fail when resuming a rebase\nafter resolving a conflict, as the backup will contain commits that\nwere picked, but they will not be in the new list.  It's safe to\nignore this check if `dropped' does not exist, because that means that\nno errors were found at the last edition, so any missing commits here\nhave already been picked.\n\nFour tests are added to t3404.  The tests for\n`rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\nerror' have a similar structure.  First, we start a rebase with an\nincorrect command on the first line.  Then, we edit the todo list,\nremoving the first and the last lines.  This demonstrates that\n`--edit-todo' notices dropped commits, but not when the command is\nincorrect.  Then, we restore the original todo list, and edit it to\nremove the last line.  This demonstrates that if we add a commit after\nthe initial edit, then remove it, `--edit-todo' will notice that it\nhas been dropped.  Then, the actual rebase takes place.  In the third\ntest, it is also checked that `--continue' will refuse to resume the\nrebase if commits were dropped.  The fourth test checks that no errors\nare raised when resuming a rebase after resolving a conflict.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    I don't think the way I create `expect.3' files in \"rebase --edit-todo\n    respects rebase.missingCommitsCheck = warning\" & \"... = error\" is the\n    best practice.  Perhaps I should create a new file from scratch instead\n    of calling `head' and `tail' successively?\n\n rebase-interactive.c          |  58 ++++++++++++++----\n rebase-interactive.h          |   3 +\n sequencer.c                   |  22 +++----\n sequencer.h                   |   1 +\n t/t3404-rebase-interactive.sh | 109 ++++++++++++++++++++++++++++++++++\n 5 files changed, 171 insertions(+), 22 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex ad5dd49c31..36b08a55ef 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n #include \"commit-slab.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n \n static const char edit_todo_list_advice[] =\n N_(\"You can fix this with 'git rebase --edit-todo' \"\n@@ -97,22 +98,25 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags)\n {\n-\tconst char *todo_file = rebase_path_todo();\n+\tconst char *todo_file = rebase_path_todo(),\n+\t\t*todo_backup = rebase_path_todo_backup();\n \tunsigned initial = shortrevisions && shortonto;\n+\tint incorrect = 1;\n \n \t/* If the user is editing the todo list, we first try to parse\n \t * it.  If there is an error, we do not return, because the user\n \t * might want to fix it in the first place. */\n \tif (!initial)\n-\t\ttodo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n+\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n+\n+\tincorrect |= file_exists(rebase_path_dropped());\n \n \tif (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,\n \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n \n-\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n-\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n-\t\t\t     rebase_path_todo_backup());\n+\tif (initial && copy_file(todo_backup, todo_file, 0666))\n+\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n \n \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n \t\treturn -2;\n@@ -121,10 +125,26 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \tif (initial && new_todo->buf.len == 0)\n \t\treturn -3;\n \n-\t/* For the initial edit, the todo list gets parsed in\n-\t * complete_action(). */\n-\tif (!initial)\n-\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n+\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t\treturn -4;\n+\t}\n+\n+\tif (incorrect) {\n+\t\tif (todo_list_check_against_backup(r, new_todo)) {\n+\t\t\twrite_file(rebase_path_dropped(), \"\");\n+\t\t\treturn -4;\n+\t\t}\n+\n+\t\tif (incorrect > 0)\n+\t\t\tunlink(rebase_path_dropped());\n+\t} else if (todo_list_check(todo_list, new_todo)) {\n+\t\twrite_file(rebase_path_dropped(), \"\");\n+\t\treturn -4;\n+\t} else {\n+\t\ttodo_list_write_to_file(r, todo_list, todo_backup, shortrevisions, shortonto,\n+\t\t\t\t\t-1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP);\n+\t}\n \n \treturn 0;\n }\n@@ -189,11 +209,27 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \t\t\"the level of warnings.\\n\"\n \t\t\"The possible behaviours are: ignore, warn, error.\\n\\n\"));\n \n+\tfprintf(stderr, _(edit_todo_list_advice));\n+\n leave_check:\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n \n+int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_list)\n+{\n+\tstruct todo_list backup = TODO_LIST_INIT;\n+\tint res = 0;\n+\n+\tif (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {\n+\t\ttodo_list_parse_insn_buffer(r, backup.buf.buf, &backup);\n+\t\tres = todo_list_check(&backup, todo_list);\n+\t}\n+\n+\ttodo_list_release(&backup);\n+\treturn res;\n+}\n+\n int check_todo_list_from_file(struct repository *r)\n {\n \tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n@@ -212,10 +248,10 @@ int check_todo_list_from_file(struct repository *r)\n \tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n \tif (!res)\n \t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n-\tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n \tif (res)\n \t\tfprintf(stderr, _(edit_todo_list_advice));\n+\tif (!res)\n+\t\tres = todo_list_check(&old_todo, &new_todo);\n out:\n \ttodo_list_release(&old_todo);\n \ttodo_list_release(&new_todo);\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 5f41bf5a28..4af0c1fcc7 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -11,7 +11,10 @@ void append_todo_help(unsigned keep_empty, int command_count,\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags);\n+\n int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n+int todo_list_check_against_backup(struct repository *r,\n+\t\t\t\t   struct todo_list *todo_list);\n \n int check_todo_list_from_file(struct repository *r);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 181bb35f5f..2ff18943fb 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -57,6 +57,8 @@ static GIT_PATH_FUNC(rebase_path, \"rebase-merge\")\n GIT_PATH_FUNC(rebase_path_todo, \"rebase-merge/git-rebase-todo\")\n GIT_PATH_FUNC(rebase_path_todo_backup, \"rebase-merge/git-rebase-todo.backup\")\n \n+GIT_PATH_FUNC(rebase_path_dropped, \"rebase-merge/dropped\")\n+\n /*\n  * The rebase command lines that have already been processed. A line\n  * is moved here when it is first handled, before any associated user\n@@ -4273,6 +4275,14 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n \tif (is_rebase_i(opts)) {\n \t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n \t\t\tgoto release_todo_list;\n+\n+\t\tif (file_exists(rebase_path_dropped())) {\n+\t\t\tif ((res = todo_list_check_against_backup(r, &todo_list)))\n+\t\t\t\tgoto release_todo_list;\n+\n+\t\t\tunlink(rebase_path_dropped());\n+\t\t}\n+\n \t\tif (commit_staged_changes(r, opts, &todo_list))\n \t\t\treturn -1;\n \t} else if (!file_exists(get_todo_path(opts)))\n@@ -4986,12 +4996,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \treturn res;\n }\n \n-static const char edit_todo_list_advice[] =\n-N_(\"You can fix this with 'git rebase --edit-todo' \"\n-\"and then run 'git rebase --continue'.\\n\"\n-\"Or you can abort the rebase with 'git rebase\"\n-\" --abort'.\\n\");\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\n@@ -5089,11 +5093,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\ttodo_list_release(&new_todo);\n \n \t\treturn error(_(\"nothing to do\"));\n-\t}\n-\n-\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n-\t    todo_list_check(todo_list, &new_todo)) {\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t} else if (res == -4) {\n \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n \t\ttodo_list_release(&new_todo);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 75ddc5db3a..00debf5107 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -11,6 +11,7 @@ const char *git_path_commit_editmsg(void);\n const char *git_path_seq_dir(void);\n const char *rebase_path_todo(void);\n const char *rebase_path_todo_backup(void);\n+const char *rebase_path_dropped(void);\n \n #define APPEND_SIGNOFF_DEDUP (1u << 0)\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 29a35840ed..f5c3da33bf 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1343,6 +1343,115 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n+\ttest_config rebase.missingCommitsCheck ignore &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\tEOF\n+\thead -n4 expect >expect.2 &&\n+\ttail -n1 expect >>expect.2 &&\n+\ttail -n4 expect.2 >expect.3 &&\n+\ttest_config rebase.missingCommitsCheck warn &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n6 actual.2 >actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n4 actual.2 >actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\n+\tUse '\\''git config rebase.missingCommitsCheck'\\'' to change the level of warnings.\n+\tThe possible behaviours are: ignore, warn, error.\n+\n+\tYou can fix this with '\\''git rebase --edit-todo'\\'' and then run '\\''git rebase --continue'\\''.\n+\tOr you can abort the rebase with '\\''git rebase --abort'\\''.\n+\tEOF\n+\ttail -n11 expect >expect.2 &&\n+\thead -n3 expect.2 >expect.3 &&\n+\ttail -n7 expect.2 >>expect.3 &&\n+\ttest_config rebase.missingCommitsCheck error &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect.2 actual &&\n+\t\ttest_must_fail git rebase --edit-todo &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase.missingCommitsCheck = error after resolving conflicts' '\n+\ttest_config rebase.missingCommitsCheck error &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"drop 1 break 2 3 4\" git rebase -i A E\n+\t) &&\n+\tgit rebase --edit-todo &&\n+\ttest_must_fail git rebase --continue &&\n+\techo x >file1 &&\n+\tgit add file1 &&\n+\tgit rebase --continue\n+'\n+\n test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n \trebase_setup_and_clean abbrevcmd &&\n \ttest_commit \"first\" file1.txt \"first line\" first &&\n-- \n2.24.1\n\n"},{"id":"390067","messageId":"fa4cecb1-4b42-d75b-e1e4-e0fa3bd13c6a@gmail.com","threadId":"51492","inReplyTo":"20200111173917.15690-3-alban.gruin@gmail.com","subject":"Re: [PATCH v4 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-01-19T16:28:49Z","receivedAt":"2020-01-19T16:28:57Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Alban\n\nOn 11/01/2020 17:39, Alban Gruin wrote:\n> When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n> `rebase -i' warn if the user removed commits from the todo list to\n> prevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n> --continue' don't take it into account.\n> \n> This adds the ability for `rebase --edit-todo' and `rebase --continue'\n> to check if commits were dropped by the user.  As both edit_todo_list()\n> and complete_action() parse the todo list and check for dropped commits,\n> the code doing so in the latter is removed to reduce duplication.\n> `edit_todo_list_advice' is removed from sequencer.c as it is no longer\n> used there.\n> \n> This changes when a backup of the todo list is made.  Until now, it\n> was saved only once, before the initial edit.  Now, it is also made if\n> after the user edited the list, if it has no errors or if no commits\n> were dropped and `rebase.missingCommitsCheck' is set.  Thus, the\n> backup should be error-free.  Without this, sequencer_continue()\n> (`rebase --continue') could only compare the current todo list against\n> the original, unedited list.  Before this change, this file was only\n> used by edit_todo_list() and `rebase -p' to create the backup before\n> the initial edit, and check_todo_list_from_file(), only used by\n> `rebase -p' to check for dropped commits after its own initial edit.\n> \n> If the edited list has an error, a file, `dropped', is created to\n> report the issue.  Otherwise, it is deleted.  Usually, the edited list\n> is compared against the list before editing, but if this file exists,\n> it will be compared to the backup.  Also, if the file exists,\n> sequencer_continue() checks the list for dropped commits.  If the\n> check was performed every time, it would fail when resuming a rebase\n> after resolving a conflict, as the backup will contain commits that\n> were picked, but they will not be in the new list.  It's safe to\n> ignore this check if `dropped' does not exist, because that means that\n> no errors were found at the last edition, so any missing commits here\n> have already been picked.\n> \n> Four tests are added to t3404.  The tests for\n> `rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\n> error' have a similar structure.  First, we start a rebase with an\n> incorrect command on the first line.  Then, we edit the todo list,\n> removing the first and the last lines.  This demonstrates that\n> `--edit-todo' notices dropped commits, but not when the command is\n> incorrect.  Then, we restore the original todo list, and edit it to\n> remove the last line.  This demonstrates that if we add a commit after\n> the initial edit, then remove it, `--edit-todo' will notice that it\n> has been dropped.  Then, the actual rebase takes place.  In the third\n> test, it is also checked that `--continue' will refuse to resume the\n> rebase if commits were dropped.  The fourth test checks that no errors\n> are raised when resuming a rebase after resolving a conflict.\n> \n> Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n> ---\n> \n> Notes:\n>      I don't think the way I create `expect.3' files in \"rebase --edit-todo\n>      respects rebase.missingCommitsCheck = warning\" & \"... = error\" is the\n>      best practice.  Perhaps I should create a new file from scratch instead\n>      of calling `head' and `tail' successively?\n> \n>   rebase-interactive.c          |  58 ++++++++++++++----\n>   rebase-interactive.h          |   3 +\n>   sequencer.c                   |  22 +++----\n>   sequencer.h                   |   1 +\n>   t/t3404-rebase-interactive.sh | 109 ++++++++++++++++++++++++++++++++++\n>   5 files changed, 171 insertions(+), 22 deletions(-)\n> \n> diff --git a/rebase-interactive.c b/rebase-interactive.c\n> index ad5dd49c31..36b08a55ef 100644\n> --- a/rebase-interactive.c\n> +++ b/rebase-interactive.c\n> @@ -5,6 +5,7 @@\n>   #include \"strbuf.h\"\n>   #include \"commit-slab.h\"\n>   #include \"config.h\"\n> +#include \"dir.h\"\n>   \n>   static const char edit_todo_list_advice[] =\n>   N_(\"You can fix this with 'git rebase --edit-todo' \"\n> @@ -97,22 +98,25 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>   \t\t   struct todo_list *new_todo, const char *shortrevisions,\n>   \t\t   const char *shortonto, unsigned flags)\n>   {\n> -\tconst char *todo_file = rebase_path_todo();\n> +\tconst char *todo_file = rebase_path_todo(),\n> +\t\t*todo_backup = rebase_path_todo_backup();\n>   \tunsigned initial = shortrevisions && shortonto;\n> +\tint incorrect = 1;\n>   \n>   \t/* If the user is editing the todo list, we first try to parse\n>   \t * it.  If there is an error, we do not return, because the user\n>   \t * might want to fix it in the first place. */\n>   \tif (!initial)\n> -\t\ttodo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n> +\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n> +\n> +\tincorrect |= file_exists(rebase_path_dropped());\n>   \n>   \tif (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,\n>   \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n>   \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n>   \n> -\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n> -\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n> -\t\t\t     rebase_path_todo_backup());\n> +\tif (initial && copy_file(todo_backup, todo_file, 0666))\n> +\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n>   \n>   \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n>   \t\treturn -2;\n> @@ -121,10 +125,26 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>   \tif (initial && new_todo->buf.len == 0)\n>   \t\treturn -3;\n>   \n> -\t/* For the initial edit, the todo list gets parsed in\n> -\t * complete_action(). */\n> -\tif (!initial)\n> -\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n> +\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {\n> +\t\tfprintf(stderr, _(edit_todo_list_advice));\n> +\t\treturn -4;\n> +\t}\n> +\n> +\tif (incorrect) {\n> +\t\tif (todo_list_check_against_backup(r, new_todo)) {\n> +\t\t\twrite_file(rebase_path_dropped(), \"\");\n> +\t\t\treturn -4;\n> +\t\t}\n> +\n> +\t\tif (incorrect > 0)\n> +\t\t\tunlink(rebase_path_dropped());\n> +\t} else if (todo_list_check(todo_list, new_todo)) {\n> +\t\twrite_file(rebase_path_dropped(), \"\");\n\nWe also need to create a backup from todo_list otherwise we'll be using\na stale backup file for the in the clause above when the user edits the\ntodo list to fix it. This test fails\n\ntest_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '\n\ttest_config rebase.missingCommitsCheck error &&\n\t(\n\t\tset_fake_editor &&\n\t\tFAKE_LINES=\"1 break 2 3\" git rebase -i A D &&\n\t\tcp .git/rebase-merge/git-rebase-todo todo &&\n\t\ttest_must_fail env FAKE_LINES=2 git rebase --edit-todo &&\n\t\tGIT_SEQUENCE_EDITOR=\"cp todo\" git rebase --edit-todo &&\n\t\tgit rebase --continue\n\t)\n'\nThe second edit complains that commit B is missing even though we have\nalready pick it. This is because we're using a stale backup file.\n\nSorry to be the bearer of bad tidings\n\nPhillip\n\n> +\t\treturn -4;\n> +\t} else {\n> +\t\ttodo_list_write_to_file(r, todo_list, todo_backup, shortrevisions, shortonto,\n> +\t\t\t\t\t-1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP);\n> +\t}\n>   \n>   \treturn 0;\n>   }\n> @@ -189,11 +209,27 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n>   \t\t\"the level of warnings.\\n\"\n>   \t\t\"The possible behaviours are: ignore, warn, error.\\n\\n\"));\n>   \n> +\tfprintf(stderr, _(edit_todo_list_advice));\n> +\n>   leave_check:\n>   \tclear_commit_seen(&commit_seen);\n>   \treturn res;\n>   }\n>   \n> +int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_list)\n> +{\n> +\tstruct todo_list backup = TODO_LIST_INIT;\n> +\tint res = 0;\n> +\n> +\tif (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {\n> +\t\ttodo_list_parse_insn_buffer(r, backup.buf.buf, &backup);\n> +\t\tres = todo_list_check(&backup, todo_list);\n> +\t}\n> +\n> +\ttodo_list_release(&backup);\n> +\treturn res;\n> +}\n> +\n>   int check_todo_list_from_file(struct repository *r)\n>   {\n>   \tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n> @@ -212,10 +248,10 @@ int check_todo_list_from_file(struct repository *r)\n>   \tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n>   \tif (!res)\n>   \t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n> -\tif (!res)\n> -\t\tres = todo_list_check(&old_todo, &new_todo);\n>   \tif (res)\n>   \t\tfprintf(stderr, _(edit_todo_list_advice));\n> +\tif (!res)\n> +\t\tres = todo_list_check(&old_todo, &new_todo);\n>   out:\n>   \ttodo_list_release(&old_todo);\n>   \ttodo_list_release(&new_todo);\n> diff --git a/rebase-interactive.h b/rebase-interactive.h\n> index 5f41bf5a28..4af0c1fcc7 100644\n> --- a/rebase-interactive.h\n> +++ b/rebase-interactive.h\n> @@ -11,7 +11,10 @@ void append_todo_help(unsigned keep_empty, int command_count,\n>   int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>   \t\t   struct todo_list *new_todo, const char *shortrevisions,\n>   \t\t   const char *shortonto, unsigned flags);\n> +\n>   int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n> +int todo_list_check_against_backup(struct repository *r,\n> +\t\t\t\t   struct todo_list *todo_list);\n>   \n>   int check_todo_list_from_file(struct repository *r);\n>   \n> diff --git a/sequencer.c b/sequencer.c\n> index 181bb35f5f..2ff18943fb 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -57,6 +57,8 @@ static GIT_PATH_FUNC(rebase_path, \"rebase-merge\")\n>   GIT_PATH_FUNC(rebase_path_todo, \"rebase-merge/git-rebase-todo\")\n>   GIT_PATH_FUNC(rebase_path_todo_backup, \"rebase-merge/git-rebase-todo.backup\")\n>   \n> +GIT_PATH_FUNC(rebase_path_dropped, \"rebase-merge/dropped\")\n> +\n>   /*\n>    * The rebase command lines that have already been processed. A line\n>    * is moved here when it is first handled, before any associated user\n> @@ -4273,6 +4275,14 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n>   \tif (is_rebase_i(opts)) {\n>   \t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n>   \t\t\tgoto release_todo_list;\n> +\n> +\t\tif (file_exists(rebase_path_dropped())) {\n> +\t\t\tif ((res = todo_list_check_against_backup(r, &todo_list)))\n> +\t\t\t\tgoto release_todo_list;\n> +\n> +\t\t\tunlink(rebase_path_dropped());\n> +\t\t}\n> +\n>   \t\tif (commit_staged_changes(r, opts, &todo_list))\n>   \t\t\treturn -1;\n>   \t} else if (!file_exists(get_todo_path(opts)))\n> @@ -4986,12 +4996,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n>   \treturn res;\n>   }\n>   \n> -static const char edit_todo_list_advice[] =\n> -N_(\"You can fix this with 'git rebase --edit-todo' \"\n> -\"and then run 'git rebase --continue'.\\n\"\n> -\"Or you can abort the rebase with 'git rebase\"\n> -\" --abort'.\\n\");\n> -\n>   /* skip picking commits whose parents are unchanged */\n>   static int skip_unnecessary_picks(struct repository *r,\n>   \t\t\t\t  struct todo_list *todo_list,\n> @@ -5089,11 +5093,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n>   \t\ttodo_list_release(&new_todo);\n>   \n>   \t\treturn error(_(\"nothing to do\"));\n> -\t}\n> -\n> -\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n> -\t    todo_list_check(todo_list, &new_todo)) {\n> -\t\tfprintf(stderr, _(edit_todo_list_advice));\n> +\t} else if (res == -4) {\n>   \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n>   \t\ttodo_list_release(&new_todo);\n>   \n> diff --git a/sequencer.h b/sequencer.h\n> index 75ddc5db3a..00debf5107 100644\n> --- a/sequencer.h\n> +++ b/sequencer.h\n> @@ -11,6 +11,7 @@ const char *git_path_commit_editmsg(void);\n>   const char *git_path_seq_dir(void);\n>   const char *rebase_path_todo(void);\n>   const char *rebase_path_todo_backup(void);\n> +const char *rebase_path_dropped(void);\n>   \n>   #define APPEND_SIGNOFF_DEDUP (1u << 0)\n>   \n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 29a35840ed..f5c3da33bf 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1343,6 +1343,115 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n>   \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n>   '\n>   \n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n> +\ttest_config rebase.missingCommitsCheck ignore &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\t(\n> +\t\tset_fake_editor &&\n> +\t\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n> +\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo &&\n> +\t\tgit rebase --continue 2>actual\n> +\t) &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n> +\tcat >expect <<-EOF &&\n> +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n> +\tWarning: some commits may have been dropped accidentally.\n> +\tDropped commits (newer to older):\n> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n> +\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n> +\tEOF\n> +\thead -n4 expect >expect.2 &&\n> +\ttail -n1 expect >>expect.2 &&\n> +\ttail -n4 expect.2 >expect.3 &&\n> +\ttest_config rebase.missingCommitsCheck warn &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\t(\n> +\t\tset_fake_editor &&\n> +\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n> +\t\t\tgit rebase -i --root &&\n> +\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n> +\t\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n> +\t\thead -n6 actual.2 >actual &&\n> +\t\ttest_i18ncmp expect actual &&\n> +\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n> +\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n> +\t\thead -n4 actual.2 >actual &&\n> +\t\ttest_i18ncmp expect.3 actual &&\n> +\t\tgit rebase --continue 2>actual\n> +\t) &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n> +test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n> +\tcat >expect <<-EOF &&\n> +\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n> +\tWarning: some commits may have been dropped accidentally.\n> +\tDropped commits (newer to older):\n> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n> +\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n> +\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n> +\n> +\tUse '\\''git config rebase.missingCommitsCheck'\\'' to change the level of warnings.\n> +\tThe possible behaviours are: ignore, warn, error.\n> +\n> +\tYou can fix this with '\\''git rebase --edit-todo'\\'' and then run '\\''git rebase --continue'\\''.\n> +\tOr you can abort the rebase with '\\''git rebase --abort'\\''.\n> +\tEOF\n> +\ttail -n11 expect >expect.2 &&\n> +\thead -n3 expect.2 >expect.3 &&\n> +\ttail -n7 expect.2 >>expect.3 &&\n> +\ttest_config rebase.missingCommitsCheck error &&\n> +\trebase_setup_and_clean missing-commit &&\n> +\t(\n> +\t\tset_fake_editor &&\n> +\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n> +\t\t\tgit rebase -i --root &&\n> +\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n> +\t\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n> +\t\t\tgit rebase --edit-todo 2>actual &&\n> +\t\ttest_i18ncmp expect actual &&\n> +\t\ttest_must_fail git rebase --continue 2>actual &&\n> +\t\ttest_i18ncmp expect.2 actual &&\n> +\t\ttest_must_fail git rebase --edit-todo &&\n> +\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n> +\t\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n> +\t\t\tgit rebase --edit-todo 2>actual &&\n> +\t\ttest_i18ncmp expect.3 actual &&\n> +\t\ttest_must_fail git rebase --continue 2>actual &&\n> +\t\ttest_i18ncmp expect.3 actual &&\n> +\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n> +\t\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n> +\t\tgit rebase --continue 2>actual\n> +\t) &&\n> +\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n> +\ttest_i18ngrep \\\n> +\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n> +\t\tactual\n> +'\n> +\n> +test_expect_success 'rebase.missingCommitsCheck = error after resolving conflicts' '\n> +\ttest_config rebase.missingCommitsCheck error &&\n> +\t(\n> +\t\tset_fake_editor &&\n> +\t\tFAKE_LINES=\"drop 1 break 2 3 4\" git rebase -i A E\n> +\t) &&\n> +\tgit rebase --edit-todo &&\n> +\ttest_must_fail git rebase --continue &&\n> +\techo x >file1 &&\n> +\tgit add file1 &&\n> +\tgit rebase --continue\n> +'\n> +\n>   test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n>   \trebase_setup_and_clean abbrevcmd &&\n>   \ttest_commit \"first\" file1.txt \"first line\" first &&\n> \n"},{"id":"390456","messageId":"19b94763-0072-ba1b-c0a0-300ccf6a94df@gmail.com","threadId":"51492","inReplyTo":"fa4cecb1-4b42-d75b-e1e4-e0fa3bd13c6a@gmail.com","subject":"Re: [PATCH v4 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-25T15:17:01Z","receivedAt":"2020-01-25T15:17:11Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Phillip,\n\nLe 19/01/2020 à 17:28, Phillip Wood a écrit :\n> Hi Alban\n> \n> On 11/01/2020 17:39, Alban Gruin wrote:\n\n>> @@ -121,10 +125,26 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n>>   \tif (initial && new_todo->buf.len == 0)\n>>   \t\treturn -3;\n>>   \n>> -\t/* For the initial edit, the todo list gets parsed in\n>> -\t * complete_action(). */\n>> -\tif (!initial)\n>> -\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n>> +\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {\n>> +\t\tfprintf(stderr, _(edit_todo_list_advice));\n>> +\t\treturn -4;\n>> +\t}\n>> +\n>> +\tif (incorrect) {\n>> +\t\tif (todo_list_check_against_backup(r, new_todo)) {\n>> +\t\t\twrite_file(rebase_path_dropped(), \"\");\n>> +\t\t\treturn -4;\n>> +\t\t}\n>> +\n>> +\t\tif (incorrect > 0)\n>> +\t\t\tunlink(rebase_path_dropped());\n>> +\t} else if (todo_list_check(todo_list, new_todo)) {\n>> +\t\twrite_file(rebase_path_dropped(), \"\");\n> \n> We also need to create a backup from todo_list otherwise we'll be using\n> a stale backup file for the in the clause above when the user edits the\n> todo list to fix it. This test fails\n> \n> test_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '\n> \ttest_config rebase.missingCommitsCheck error &&\n> \t(\n> \t\tset_fake_editor &&\n> \t\tFAKE_LINES=\"1 break 2 3\" git rebase -i A D &&\n> \t\tcp .git/rebase-merge/git-rebase-todo todo &&\n> \t\ttest_must_fail env FAKE_LINES=2 git rebase --edit-todo &&\n> \t\tGIT_SEQUENCE_EDITOR=\"cp todo\" git rebase --edit-todo &&\n> \t\tgit rebase --continue\n> \t)\n> '\n> The second edit complains that commit B is missing even though we have\n> already pick it. This is because we're using a stale backup file.\n> \n\nGood catch.  Fortunately this time, the fix is trivial, and the reroll\nwill come very soon.\n\n> Sorry to be the bearer of bad tidings\n> \n\nNo worries, this series wouldn't get very far without your feedback.\n\n> Phillip\n> \n>> +\t\treturn -4;\n>> +\t} else {\n>> +\t\ttodo_list_write_to_file(r, todo_list, todo_backup, shortrevisions, shortonto,\n>> +\t\t\t\t\t-1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP);\n>> +\t}\n>>   \n>>   \treturn 0;\n>>   }\n\nThank you,\nAlban\n\n"},{"id":"390461","messageId":"20200125175436.29786-1-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200111173917.15690-1-alban.gruin@gmail.com","subject":"[PATCH v5 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-25T17:54:34Z","receivedAt":"2020-01-25T17:57:31Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"To prevent mistakes when editing a branch, rebase features a knob,\nrebase.missingCommitsCheck, to warn the user if a commit was dropped.\nUnfortunately, this check is only effective for the initial edit, which\nmeans that if you edit the todo list at a later point of the rebase and\ndrop a commit, no warnings or errors would be issued.\n\nThis adds the ability to check if commits were dropped when editing the\ntodo list with `--edit-todo', and when resuming a rebase.\n\nThe first patch moves moves check_todo_list_from_file() and\n`edit_todo_list_advice' from sequencer.c to rebase-interactive.c so the\nlatter can be used by edit_todo_list() and todo_list_check().  The\nsecond patch adds the check to `--edit-todo' and `--continue' and tests.\n\nThis is based on 393adf7a6f (\"sequencer: directly call pick_commits()\nfrom complete_action()\", 2019-11-24).\n\nThe tip of this series is tagged as \"edit-todo-drop-v5\" at\nhttps://github.com/agrn/git.\n\nChanges since v4:\n\n - Added a test to check that no errors are raised when editing the todo\n   list after pausing the rebase; it was provided by Phillip Wood.\n\n - Fixed the issue pointed out by this test by creating a backup of the\n   todo list even if the edited list is incorrect.  This step was moved\n   before the user edits the list, so the backup can be created with\n   copy_file() instead of todo_list_write_to_file().\n\n - In edit_todo_list(), `incorrect' is set to 0 by default.  This only\n   concerns the initial edit case; instead of opening and parsing the\n   backup, we use directly `old_todo' as a reference.\n\n - Don't check if the \"dropped\" file flag exists at the initial edit.\n\nAlban Gruin (2):\n  sequencer: move check_todo_list_from_file() to rebase-interactive.c\n  rebase-interactive: warn if commit is dropped with `rebase\n    --edit-todo'\n\n rebase-interactive.c          |  90 ++++++++++++++++++++++---\n rebase-interactive.h          |   5 ++\n sequencer.c                   |  51 ++++----------\n sequencer.h                   |   2 +-\n t/t3404-rebase-interactive.sh | 121 ++++++++++++++++++++++++++++++++++\n 5 files changed, 219 insertions(+), 50 deletions(-)\n\nRange-diff against v4:\n1:  996045a300 = 1:  996045a300 sequencer: move check_todo_list_from_file() to rebase-interactive.c\n2:  11b0e1e78c ! 2:  6dbaa8cbe6 rebase-interactive: warn if commit is dropped with `rebase --edit-todo'\n    @@ Commit message\n         `edit_todo_list_advice' is removed from sequencer.c as it is no longer\n         used there.\n     \n    -    This changes when a backup of the todo list is made.  Until now, it\n    -    was saved only once, before the initial edit.  Now, it is also made if\n    -    after the user edited the list, if it has no errors or if no commits\n    -    were dropped and `rebase.missingCommitsCheck' is set.  Thus, the\n    +    This changes when a backup of the todo list is made.  Until now, it was\n    +    saved only once, before the initial edit.  Now, it is also made if the\n    +    original todo list has no errors or no dropped commits.  Thus, the\n         backup should be error-free.  Without this, sequencer_continue()\n         (`rebase --continue') could only compare the current todo list against\n         the original, unedited list.  Before this change, this file was only\n    @@ Commit message\n         no errors were found at the last edition, so any missing commits here\n         have already been picked.\n     \n    -    Four tests are added to t3404.  The tests for\n    +    Five tests are added to t3404.  The tests for\n         `rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\n         error' have a similar structure.  First, we start a rebase with an\n         incorrect command on the first line.  Then, we edit the todo list,\n    @@ Commit message\n         has been dropped.  Then, the actual rebase takes place.  In the third\n         test, it is also checked that `--continue' will refuse to resume the\n         rebase if commits were dropped.  The fourth test checks that no errors\n    -    are raised when resuming a rebase after resolving a conflict.\n    +    are raised when resuming a rebase after resolving a conflict, the fifth\n    +    checks that no errors are raised when editing the todo list after\n    +    pausing the rebase.\n     \n         Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n         I don't think the way I create `expect.3' files in \"rebase --edit-todo\n    @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list\n     +\tconst char *todo_file = rebase_path_todo(),\n     +\t\t*todo_backup = rebase_path_todo_backup();\n      \tunsigned initial = shortrevisions && shortonto;\n    -+\tint incorrect = 1;\n    ++\tint incorrect = 0;\n      \n      \t/* If the user is editing the todo list, we first try to parse\n      \t * it.  If there is an error, we do not return, because the user\n      \t * might want to fix it in the first place. */\n      \tif (!initial)\n     -\t\ttodo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n    -+\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n    -+\n    -+\tincorrect |= file_exists(rebase_path_dropped());\n    ++\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list) |\n    ++\t\t\tfile_exists(rebase_path_dropped());\n      \n      \tif (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,\n      \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n    @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list\n     -\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n     -\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n     -\t\t\t     rebase_path_todo_backup());\n    -+\tif (initial && copy_file(todo_backup, todo_file, 0666))\n    -+\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n    ++\tif (initial || !incorrect) {\n    ++\t\tif (!initial)\n    ++\t\t\tunlink(todo_backup);\n    ++\n    ++\t\tif (copy_file(todo_backup, todo_file, 0666))\n    ++\t\t    return error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n    ++\t}\n      \n      \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n      \t\treturn -2;\n    @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list\n     +\t} else if (todo_list_check(todo_list, new_todo)) {\n     +\t\twrite_file(rebase_path_dropped(), \"\");\n     +\t\treturn -4;\n    -+\t} else {\n    -+\t\ttodo_list_write_to_file(r, todo_list, todo_backup, shortrevisions, shortonto,\n    -+\t\t\t\t\t-1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP);\n     +\t}\n      \n      \treturn 0;\n    @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.mi\n     +\tgit add file1 &&\n     +\tgit rebase --continue\n     +'\n    ++\n    ++test_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '\n    ++\ttest_config rebase.missingCommitsCheck error &&\n    ++\t(\n    ++\t\tset_fake_editor &&\n    ++\t\tFAKE_LINES=\"1 break 2 3\" git rebase -i A D &&\n    ++\t\tcp .git/rebase-merge/git-rebase-todo todo &&\n    ++\t\ttest_must_fail env FAKE_LINES=2 git rebase --edit-todo &&\n    ++\t\tGIT_SEQUENCE_EDITOR=\"cp todo\" git rebase --edit-todo &&\n    ++\t\tgit rebase --continue\n    ++\t)\n    ++'\n     +\n      test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n      \trebase_setup_and_clean abbrevcmd &&\n-- \n2.24.1\n\n"},{"id":"390462","messageId":"20200125175436.29786-2-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200125175436.29786-1-alban.gruin@gmail.com","subject":"[PATCH v5 1/2] sequencer: move check_todo_list_from_file() to rebase-interactive.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-25T17:54:35Z","receivedAt":"2020-01-25T17:57:34Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The message contained in `edit_todo_list_advice' (sequencer.c) is\nprinted after the initial edit of the todo list if it can't be parsed or\nif commits were dropped.  This is done either in complete_action() for\n`rebase -i', or in check_todo_list_from_file() for `rebase -p'.\n\nSince we want to add this check when editing the list, we also want to\nuse this message from edit_todo_list() (rebase-interactive.c).  To this\nend, check_todo_list_from_file() is moved to rebase-interactive.c, and\n`edit_todo_list_advice' is copied there.  In the next commit,\ncomplete_action() will stop using it, and `edit_todo_list_advice' will\nbe removed from sequencer.c.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c | 35 +++++++++++++++++++++++++++++++++++\n rebase-interactive.h |  2 ++\n sequencer.c          | 29 -----------------------------\n sequencer.h          |  1 -\n 4 files changed, 37 insertions(+), 30 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex aa18ae82b7..ad5dd49c31 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -6,6 +6,12 @@\n #include \"commit-slab.h\"\n #include \"config.h\"\n \n+static const char edit_todo_list_advice[] =\n+N_(\"You can fix this with 'git rebase --edit-todo' \"\n+\"and then run 'git rebase --continue'.\\n\"\n+\"Or you can abort the rebase with 'git rebase\"\n+\" --abort'.\\n\");\n+\n enum missing_commit_check_level {\n \tMISSING_COMMIT_CHECK_IGNORE = 0,\n \tMISSING_COMMIT_CHECK_WARN,\n@@ -187,3 +193,32 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n+\n+int check_todo_list_from_file(struct repository *r)\n+{\n+\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n+\tint res = 0;\n+\n+\tif (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo());\n+\t\tgoto out;\n+\t}\n+\n+\tif (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo_backup());\n+\t\tgoto out;\n+\t}\n+\n+\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n+\tif (!res)\n+\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n+\tif (!res)\n+\t\tres = todo_list_check(&old_todo, &new_todo);\n+\tif (res)\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+out:\n+\ttodo_list_release(&old_todo);\n+\ttodo_list_release(&new_todo);\n+\n+\treturn res;\n+}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..5f41bf5a28 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -13,4 +13,6 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   const char *shortonto, unsigned flags);\n int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n \n+int check_todo_list_from_file(struct repository *r);\n+\n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex ec0b793fc5..181bb35f5f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4992,35 +4992,6 @@ N_(\"You can fix this with 'git rebase --edit-todo' \"\n \"Or you can abort the rebase with 'git rebase\"\n \" --abort'.\\n\");\n \n-int check_todo_list_from_file(struct repository *r)\n-{\n-\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n-\tint res = 0;\n-\n-\tif (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tif (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n-\tif (!res)\n-\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n-\tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n-\tif (res)\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n-out:\n-\ttodo_list_release(&old_todo);\n-\ttodo_list_release(&new_todo);\n-\n-\treturn res;\n-}\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\ndiff --git a/sequencer.h b/sequencer.h\nindex 574260f621..75ddc5db3a 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -155,7 +155,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \n void todo_list_add_exec_commands(struct todo_list *todo_list,\n \t\t\t\t struct string_list *commands);\n-int check_todo_list_from_file(struct repository *r);\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const char *orig_head, struct string_list *commands,\n-- \n2.24.1\n\n"},{"id":"390463","messageId":"20200125175436.29786-3-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200125175436.29786-1-alban.gruin@gmail.com","subject":"[PATCH v5 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-25T17:54:36Z","receivedAt":"2020-01-25T17:57:38Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n`rebase -i' warn if the user removed commits from the todo list to\nprevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n--continue' don't take it into account.\n\nThis adds the ability for `rebase --edit-todo' and `rebase --continue'\nto check if commits were dropped by the user.  As both edit_todo_list()\nand complete_action() parse the todo list and check for dropped commits,\nthe code doing so in the latter is removed to reduce duplication.\n`edit_todo_list_advice' is removed from sequencer.c as it is no longer\nused there.\n\nThis changes when a backup of the todo list is made.  Until now, it was\nsaved only once, before the initial edit.  Now, it is also made if the\noriginal todo list has no errors or no dropped commits.  Thus, the\nbackup should be error-free.  Without this, sequencer_continue()\n(`rebase --continue') could only compare the current todo list against\nthe original, unedited list.  Before this change, this file was only\nused by edit_todo_list() and `rebase -p' to create the backup before\nthe initial edit, and check_todo_list_from_file(), only used by\n`rebase -p' to check for dropped commits after its own initial edit.\n\nIf the edited list has an error, a file, `dropped', is created to\nreport the issue.  Otherwise, it is deleted.  Usually, the edited list\nis compared against the list before editing, but if this file exists,\nit will be compared to the backup.  Also, if the file exists,\nsequencer_continue() checks the list for dropped commits.  If the\ncheck was performed every time, it would fail when resuming a rebase\nafter resolving a conflict, as the backup will contain commits that\nwere picked, but they will not be in the new list.  It's safe to\nignore this check if `dropped' does not exist, because that means that\nno errors were found at the last edition, so any missing commits here\nhave already been picked.\n\nFive tests are added to t3404.  The tests for\n`rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\nerror' have a similar structure.  First, we start a rebase with an\nincorrect command on the first line.  Then, we edit the todo list,\nremoving the first and the last lines.  This demonstrates that\n`--edit-todo' notices dropped commits, but not when the command is\nincorrect.  Then, we restore the original todo list, and edit it to\nremove the last line.  This demonstrates that if we add a commit after\nthe initial edit, then remove it, `--edit-todo' will notice that it\nhas been dropped.  Then, the actual rebase takes place.  In the third\ntest, it is also checked that `--continue' will refuse to resume the\nrebase if commits were dropped.  The fourth test checks that no errors\nare raised when resuming a rebase after resolving a conflict, the fifth\nchecks that no errors are raised when editing the todo list after\npausing the rebase.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    I don't think the way I create `expect.3' files in \"rebase --edit-todo\n    respects rebase.missingCommitsCheck = warning\" & \"... = error\" is the\n    best practice.  Perhaps I should create a new file from scratch instead\n    of calling `head' and `tail' successively?\n\n rebase-interactive.c          |  59 +++++++++++++----\n rebase-interactive.h          |   3 +\n sequencer.c                   |  22 +++----\n sequencer.h                   |   1 +\n t/t3404-rebase-interactive.sh | 121 ++++++++++++++++++++++++++++++++++\n 5 files changed, 184 insertions(+), 22 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex ad5dd49c31..97f609f3ee 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n #include \"commit-slab.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n \n static const char edit_todo_list_advice[] =\n N_(\"You can fix this with 'git rebase --edit-todo' \"\n@@ -97,22 +98,29 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags)\n {\n-\tconst char *todo_file = rebase_path_todo();\n+\tconst char *todo_file = rebase_path_todo(),\n+\t\t*todo_backup = rebase_path_todo_backup();\n \tunsigned initial = shortrevisions && shortonto;\n+\tint incorrect = 0;\n \n \t/* If the user is editing the todo list, we first try to parse\n \t * it.  If there is an error, we do not return, because the user\n \t * might want to fix it in the first place. */\n \tif (!initial)\n-\t\ttodo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n+\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list) |\n+\t\t\tfile_exists(rebase_path_dropped());\n \n \tif (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,\n \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n \n-\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n-\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n-\t\t\t     rebase_path_todo_backup());\n+\tif (initial || !incorrect) {\n+\t\tif (!initial)\n+\t\t\tunlink(todo_backup);\n+\n+\t\tif (copy_file(todo_backup, todo_file, 0666))\n+\t\t    return error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n+\t}\n \n \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n \t\treturn -2;\n@@ -121,10 +129,23 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \tif (initial && new_todo->buf.len == 0)\n \t\treturn -3;\n \n-\t/* For the initial edit, the todo list gets parsed in\n-\t * complete_action(). */\n-\tif (!initial)\n-\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n+\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t\treturn -4;\n+\t}\n+\n+\tif (incorrect) {\n+\t\tif (todo_list_check_against_backup(r, new_todo)) {\n+\t\t\twrite_file(rebase_path_dropped(), \"\");\n+\t\t\treturn -4;\n+\t\t}\n+\n+\t\tif (incorrect > 0)\n+\t\t\tunlink(rebase_path_dropped());\n+\t} else if (todo_list_check(todo_list, new_todo)) {\n+\t\twrite_file(rebase_path_dropped(), \"\");\n+\t\treturn -4;\n+\t}\n \n \treturn 0;\n }\n@@ -189,11 +210,27 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \t\t\"the level of warnings.\\n\"\n \t\t\"The possible behaviours are: ignore, warn, error.\\n\\n\"));\n \n+\tfprintf(stderr, _(edit_todo_list_advice));\n+\n leave_check:\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n \n+int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_list)\n+{\n+\tstruct todo_list backup = TODO_LIST_INIT;\n+\tint res = 0;\n+\n+\tif (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {\n+\t\ttodo_list_parse_insn_buffer(r, backup.buf.buf, &backup);\n+\t\tres = todo_list_check(&backup, todo_list);\n+\t}\n+\n+\ttodo_list_release(&backup);\n+\treturn res;\n+}\n+\n int check_todo_list_from_file(struct repository *r)\n {\n \tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n@@ -212,10 +249,10 @@ int check_todo_list_from_file(struct repository *r)\n \tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n \tif (!res)\n \t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n-\tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n \tif (res)\n \t\tfprintf(stderr, _(edit_todo_list_advice));\n+\tif (!res)\n+\t\tres = todo_list_check(&old_todo, &new_todo);\n out:\n \ttodo_list_release(&old_todo);\n \ttodo_list_release(&new_todo);\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 5f41bf5a28..4af0c1fcc7 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -11,7 +11,10 @@ void append_todo_help(unsigned keep_empty, int command_count,\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags);\n+\n int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n+int todo_list_check_against_backup(struct repository *r,\n+\t\t\t\t   struct todo_list *todo_list);\n \n int check_todo_list_from_file(struct repository *r);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 181bb35f5f..2ff18943fb 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -57,6 +57,8 @@ static GIT_PATH_FUNC(rebase_path, \"rebase-merge\")\n GIT_PATH_FUNC(rebase_path_todo, \"rebase-merge/git-rebase-todo\")\n GIT_PATH_FUNC(rebase_path_todo_backup, \"rebase-merge/git-rebase-todo.backup\")\n \n+GIT_PATH_FUNC(rebase_path_dropped, \"rebase-merge/dropped\")\n+\n /*\n  * The rebase command lines that have already been processed. A line\n  * is moved here when it is first handled, before any associated user\n@@ -4273,6 +4275,14 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n \tif (is_rebase_i(opts)) {\n \t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n \t\t\tgoto release_todo_list;\n+\n+\t\tif (file_exists(rebase_path_dropped())) {\n+\t\t\tif ((res = todo_list_check_against_backup(r, &todo_list)))\n+\t\t\t\tgoto release_todo_list;\n+\n+\t\t\tunlink(rebase_path_dropped());\n+\t\t}\n+\n \t\tif (commit_staged_changes(r, opts, &todo_list))\n \t\t\treturn -1;\n \t} else if (!file_exists(get_todo_path(opts)))\n@@ -4986,12 +4996,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \treturn res;\n }\n \n-static const char edit_todo_list_advice[] =\n-N_(\"You can fix this with 'git rebase --edit-todo' \"\n-\"and then run 'git rebase --continue'.\\n\"\n-\"Or you can abort the rebase with 'git rebase\"\n-\" --abort'.\\n\");\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\n@@ -5089,11 +5093,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\ttodo_list_release(&new_todo);\n \n \t\treturn error(_(\"nothing to do\"));\n-\t}\n-\n-\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n-\t    todo_list_check(todo_list, &new_todo)) {\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t} else if (res == -4) {\n \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n \t\ttodo_list_release(&new_todo);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 75ddc5db3a..00debf5107 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -11,6 +11,7 @@ const char *git_path_commit_editmsg(void);\n const char *git_path_seq_dir(void);\n const char *rebase_path_todo(void);\n const char *rebase_path_todo_backup(void);\n+const char *rebase_path_dropped(void);\n \n #define APPEND_SIGNOFF_DEDUP (1u << 0)\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 29a35840ed..14b8c3ee4f 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1343,6 +1343,127 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n+\ttest_config rebase.missingCommitsCheck ignore &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\tEOF\n+\thead -n4 expect >expect.2 &&\n+\ttail -n1 expect >>expect.2 &&\n+\ttail -n4 expect.2 >expect.3 &&\n+\ttest_config rebase.missingCommitsCheck warn &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n6 actual.2 >actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n4 actual.2 >actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\n+\tUse '\\''git config rebase.missingCommitsCheck'\\'' to change the level of warnings.\n+\tThe possible behaviours are: ignore, warn, error.\n+\n+\tYou can fix this with '\\''git rebase --edit-todo'\\'' and then run '\\''git rebase --continue'\\''.\n+\tOr you can abort the rebase with '\\''git rebase --abort'\\''.\n+\tEOF\n+\ttail -n11 expect >expect.2 &&\n+\thead -n3 expect.2 >expect.3 &&\n+\ttail -n7 expect.2 >>expect.3 &&\n+\ttest_config rebase.missingCommitsCheck error &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect.2 actual &&\n+\t\ttest_must_fail git rebase --edit-todo &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase.missingCommitsCheck = error after resolving conflicts' '\n+\ttest_config rebase.missingCommitsCheck error &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"drop 1 break 2 3 4\" git rebase -i A E\n+\t) &&\n+\tgit rebase --edit-todo &&\n+\ttest_must_fail git rebase --continue &&\n+\techo x >file1 &&\n+\tgit add file1 &&\n+\tgit rebase --continue\n+'\n+\n+test_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '\n+\ttest_config rebase.missingCommitsCheck error &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"1 break 2 3\" git rebase -i A D &&\n+\t\tcp .git/rebase-merge/git-rebase-todo todo &&\n+\t\ttest_must_fail env FAKE_LINES=2 git rebase --edit-todo &&\n+\t\tGIT_SEQUENCE_EDITOR=\"cp todo\" git rebase --edit-todo &&\n+\t\tgit rebase --continue\n+\t)\n+'\n+\n test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n \trebase_setup_and_clean abbrevcmd &&\n \ttest_commit \"first\" file1.txt \"first line\" first &&\n-- \n2.24.1\n\n"},{"id":"390507","messageId":"nycvar.QRO.7.76.6.2001261043540.46@tvgsbejvaqbjf.bet","threadId":"51492","inReplyTo":"20200125175436.29786-1-alban.gruin@gmail.com","subject":"Re: [PATCH v5 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-01-26T10:04:11Z","receivedAt":"2020-01-26T10:04:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Sat, 25 Jan 2020, Alban Gruin wrote:\n\n> Changes since v4:\n>\n>  - Added a test to check that no errors are raised when editing the todo\n>    list after pausing the rebase; it was provided by Phillip Wood.\n>\n>  - Fixed the issue pointed out by this test by creating a backup of the\n>    todo list even if the edited list is incorrect.  This step was moved\n>    before the user edits the list, so the backup can be created with\n>    copy_file() instead of todo_list_write_to_file().\n\nPlease note that I changed this to always use `todo_list_write_to_file()`\nin `js/rebase-i-with-colliding-hash` (see e.g.\nhttps://github.com/gitgitgadget/git/commit/26027625).\n\nThe reason is that the copy would be made with _abbreviated_ commit IDs.\nAnd during the rebase, those abbreviated IDs could become non-unique.\n\nI have to admit that I did not follow the evolution of your patch series\nterribly closely, but from your description I gather that we want to be\ncareful to ensure that the `.backup` file is written with non-abbreviated\ncommit IDs always.\n\nThe way I read your patches, they will conflict with\n`js/rebase-i-with-colliding-hash`, so maybe it would be a good idea to\nbase your patches on that branch?\n\nThanks,\nDscho\n\n>\n>  - In edit_todo_list(), `incorrect' is set to 0 by default.  This only\n>    concerns the initial edit case; instead of opening and parsing the\n>    backup, we use directly `old_todo' as a reference.\n>\n>  - Don't check if the \"dropped\" file flag exists at the initial edit.\n>\n> Alban Gruin (2):\n>   sequencer: move check_todo_list_from_file() to rebase-interactive.c\n>   rebase-interactive: warn if commit is dropped with `rebase\n>     --edit-todo'\n>\n>  rebase-interactive.c          |  90 ++++++++++++++++++++++---\n>  rebase-interactive.h          |   5 ++\n>  sequencer.c                   |  51 ++++----------\n>  sequencer.h                   |   2 +-\n>  t/t3404-rebase-interactive.sh | 121 ++++++++++++++++++++++++++++++++++\n>  5 files changed, 219 insertions(+), 50 deletions(-)\n>\n> Range-diff against v4:\n> 1:  996045a300 = 1:  996045a300 sequencer: move check_todo_list_from_file() to rebase-interactive.c\n> 2:  11b0e1e78c ! 2:  6dbaa8cbe6 rebase-interactive: warn if commit is dropped with `rebase --edit-todo'\n>     @@ Commit message\n>          `edit_todo_list_advice' is removed from sequencer.c as it is no longer\n>          used there.\n>\n>     -    This changes when a backup of the todo list is made.  Until now, it\n>     -    was saved only once, before the initial edit.  Now, it is also made if\n>     -    after the user edited the list, if it has no errors or if no commits\n>     -    were dropped and `rebase.missingCommitsCheck' is set.  Thus, the\n>     +    This changes when a backup of the todo list is made.  Until now, it was\n>     +    saved only once, before the initial edit.  Now, it is also made if the\n>     +    original todo list has no errors or no dropped commits.  Thus, the\n>          backup should be error-free.  Without this, sequencer_continue()\n>          (`rebase --continue') could only compare the current todo list against\n>          the original, unedited list.  Before this change, this file was only\n>     @@ Commit message\n>          no errors were found at the last edition, so any missing commits here\n>          have already been picked.\n>\n>     -    Four tests are added to t3404.  The tests for\n>     +    Five tests are added to t3404.  The tests for\n>          `rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\n>          error' have a similar structure.  First, we start a rebase with an\n>          incorrect command on the first line.  Then, we edit the todo list,\n>     @@ Commit message\n>          has been dropped.  Then, the actual rebase takes place.  In the third\n>          test, it is also checked that `--continue' will refuse to resume the\n>          rebase if commits were dropped.  The fourth test checks that no errors\n>     -    are raised when resuming a rebase after resolving a conflict.\n>     +    are raised when resuming a rebase after resolving a conflict, the fifth\n>     +    checks that no errors are raised when editing the todo list after\n>     +    pausing the rebase.\n>\n>          Signed-off-by: Alban Gruin <alban.gruin@gmail.com>\n>          I don't think the way I create `expect.3' files in \"rebase --edit-todo\n>     @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list\n>      +\tconst char *todo_file = rebase_path_todo(),\n>      +\t\t*todo_backup = rebase_path_todo_backup();\n>       \tunsigned initial = shortrevisions && shortonto;\n>     -+\tint incorrect = 1;\n>     ++\tint incorrect = 0;\n>\n>       \t/* If the user is editing the todo list, we first try to parse\n>       \t * it.  If there is an error, we do not return, because the user\n>       \t * might want to fix it in the first place. */\n>       \tif (!initial)\n>      -\t\ttodo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n>     -+\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n>     -+\n>     -+\tincorrect |= file_exists(rebase_path_dropped());\n>     ++\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list) |\n>     ++\t\t\tfile_exists(rebase_path_dropped());\n>\n>       \tif (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,\n>       \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n>     @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list\n>      -\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n>      -\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n>      -\t\t\t     rebase_path_todo_backup());\n>     -+\tif (initial && copy_file(todo_backup, todo_file, 0666))\n>     -+\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n>     ++\tif (initial || !incorrect) {\n>     ++\t\tif (!initial)\n>     ++\t\t\tunlink(todo_backup);\n>     ++\n>     ++\t\tif (copy_file(todo_backup, todo_file, 0666))\n>     ++\t\t    return error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n>     ++\t}\n>\n>       \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n>       \t\treturn -2;\n>     @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list\n>      +\t} else if (todo_list_check(todo_list, new_todo)) {\n>      +\t\twrite_file(rebase_path_dropped(), \"\");\n>      +\t\treturn -4;\n>     -+\t} else {\n>     -+\t\ttodo_list_write_to_file(r, todo_list, todo_backup, shortrevisions, shortonto,\n>     -+\t\t\t\t\t-1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP);\n>      +\t}\n>\n>       \treturn 0;\n>     @@ t/t3404-rebase-interactive.sh: test_expect_success 'rebase -i respects rebase.mi\n>      +\tgit add file1 &&\n>      +\tgit rebase --continue\n>      +'\n>     ++\n>     ++test_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '\n>     ++\ttest_config rebase.missingCommitsCheck error &&\n>     ++\t(\n>     ++\t\tset_fake_editor &&\n>     ++\t\tFAKE_LINES=\"1 break 2 3\" git rebase -i A D &&\n>     ++\t\tcp .git/rebase-merge/git-rebase-todo todo &&\n>     ++\t\ttest_must_fail env FAKE_LINES=2 git rebase --edit-todo &&\n>     ++\t\tGIT_SEQUENCE_EDITOR=\"cp todo\" git rebase --edit-todo &&\n>     ++\t\tgit rebase --continue\n>     ++\t)\n>     ++'\n>      +\n>       test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n>       \trebase_setup_and_clean abbrevcmd &&\n> --\n> 2.24.1\n>\n>\n"},{"id":"390584","messageId":"5f9b02f2-6b77-91da-2af1-d36cb6b137a3@gmail.com","threadId":"51492","inReplyTo":"nycvar.QRO.7.76.6.2001261043540.46@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-27T21:39:45Z","receivedAt":"2020-01-27T21:39:59Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"Hi Johannes,\n\nLe 26/01/2020 à 11:04, Johannes Schindelin a écrit :\n> Hi Alban,\n> \n> On Sat, 25 Jan 2020, Alban Gruin wrote:\n> \n>> Changes since v4:\n>>\n>>  - Added a test to check that no errors are raised when editing the todo\n>>    list after pausing the rebase; it was provided by Phillip Wood.\n>>\n>>  - Fixed the issue pointed out by this test by creating a backup of the\n>>    todo list even if the edited list is incorrect.  This step was moved\n>>    before the user edits the list, so the backup can be created with\n>>    copy_file() instead of todo_list_write_to_file().\n> \n> Please note that I changed this to always use `todo_list_write_to_file()`\n> in `js/rebase-i-with-colliding-hash` (see e.g.\n> https://github.com/gitgitgadget/git/commit/26027625).\n> \n> The reason is that the copy would be made with _abbreviated_ commit IDs.\n> And during the rebase, those abbreviated IDs could become non-unique.\n> \n> I have to admit that I did not follow the evolution of your patch series\n> terribly closely, but from your description I gather that we want to be\n> careful to ensure that the `.backup` file is written with non-abbreviated\n> commit IDs always.\n> \n> The way I read your patches, they will conflict with\n> `js/rebase-i-with-colliding-hash`, so maybe it would be a good idea to\n> base your patches on that branch?\n> \n\nLet's do this.  This will also solve the conflict this series already\nhas with master.\n\n> Thanks,\n> Dscho\n> \n\nCheers,\nAlban\n\n"},{"id":"390667","messageId":"20200128211246.10855-1-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200125175436.29786-1-alban.gruin@gmail.com","subject":"[PATCH v6 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-28T21:12:44Z","receivedAt":"2020-01-28T21:17:44Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"To prevent mistakes when editing a branch, rebase features a knob,\nrebase.missingCommitsCheck, to warn the user if a commit was dropped.\nUnfortunately, this check is only effective for the initial edit, which\nmeans that if you edit the todo list at a later point of the rebase and\ndrop a commit, no warnings or errors would be issued.\n\nThis adds the ability to check if commits were dropped when editing the\ntodo list with `--edit-todo', and when resuming a rebase.\n\nThe first patch moves moves check_todo_list_from_file() and\n`edit_todo_list_advice' from sequencer.c to rebase-interactive.c so the\nlatter can be used by edit_todo_list() and todo_list_check().  The\nsecond patch adds the check to `--edit-todo' and `--continue' and tests.\n\nThis is based on 26027625dd (\"rebase -i: also avoid SHA-1 collisions\nwith missingCommitsCheck\", 2020-01-23).\n\nThe tip of this series is tagged as \"edit-todo-drop-v6\" at\nhttps://github.com/agrn/git.\n\nChanges since v5:\n\n - Rebased onto js/rebase-i-with-colliding-hash.  This means the backup\n   is once again created with todo_list_write_to_file(), but without the\n   flag `TODO_LIST_SHORTEN_IDS' this time.\n\nAlban Gruin (2):\n  sequencer: move check_todo_list_from_file() to rebase-interactive.c\n  rebase-interactive: warn if commit is dropped with `rebase\n    --edit-todo'\n\n rebase-interactive.c          |  84 ++++++++++++++++++++---\n rebase-interactive.h          |   5 ++\n sequencer.c                   |  51 ++++----------\n sequencer.h                   |   2 +-\n t/t3404-rebase-interactive.sh | 121 ++++++++++++++++++++++++++++++++++\n 5 files changed, 214 insertions(+), 49 deletions(-)\n\nRange-diff against v5:\n1:  996045a300 = 1:  4f07ce94a8 sequencer: move check_todo_list_from_file() to rebase-interactive.c\n2:  6dbaa8cbe6 ! 2:  68481d89b0 rebase-interactive: warn if commit is dropped with `rebase --edit-todo'\n    @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list\n      \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n      \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n      \n    --\tif (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666))\n    --\t\treturn error(_(\"could not copy '%s' to '%s'.\"), todo_file,\n    --\t\t\t     rebase_path_todo_backup());\n    -+\tif (initial || !incorrect) {\n    -+\t\tif (!initial)\n    -+\t\t\tunlink(todo_backup);\n    -+\n    -+\t\tif (copy_file(todo_backup, todo_file, 0666))\n    -+\t\t    return error(_(\"could not copy '%s' to '%s'.\"), todo_file, todo_backup);\n    -+\t}\n    - \n    - \tif (launch_sequence_editor(todo_file, &new_todo->buf, NULL))\n    - \t\treturn -2;\n    +-\tif (initial &&\n    +-\t    todo_list_write_to_file(r, todo_list, rebase_path_todo_backup(),\n    ++\tif (!incorrect &&\n    ++\t    todo_list_write_to_file(r, todo_list, todo_backup,\n    + \t\t\t\t    shortrevisions, shortonto, -1,\n    + \t\t\t\t    (flags | TODO_LIST_APPEND_TODO_HELP) & ~TODO_LIST_SHORTEN_IDS) < 0)\n    + \t\treturn error(_(\"could not write '%s'.\"), rebase_path_todo_backup());\n     @@ rebase-interactive.c: int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n      \tif (initial && new_todo->buf.len == 0)\n      \t\treturn -3;\n    @@ sequencer.c: int sequencer_continue(struct repository *r, struct replay_opts *op\n     +\t\t\tunlink(rebase_path_dropped());\n     +\t\t}\n     +\n    - \t\tif (commit_staged_changes(r, opts, &todo_list))\n    - \t\t\treturn -1;\n    - \t} else if (!file_exists(get_todo_path(opts)))\n    + \t\tif (commit_staged_changes(r, opts, &todo_list)) {\n    + \t\t\tres = -1;\n    + \t\t\tgoto release_todo_list;\n     @@ sequencer.c: int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n      \treturn res;\n      }\n-- \n2.24.1\n\n"},{"id":"390668","messageId":"20200128211246.10855-2-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200128211246.10855-1-alban.gruin@gmail.com","subject":"[PATCH v6 1/2] sequencer: move check_todo_list_from_file() to rebase-interactive.c","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-28T21:12:45Z","receivedAt":"2020-01-28T21:17:45Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"The message contained in `edit_todo_list_advice' (sequencer.c) is\nprinted after the initial edit of the todo list if it can't be parsed or\nif commits were dropped.  This is done either in complete_action() for\n`rebase -i', or in check_todo_list_from_file() for `rebase -p'.\n\nSince we want to add this check when editing the list, we also want to\nuse this message from edit_todo_list() (rebase-interactive.c).  To this\nend, check_todo_list_from_file() is moved to rebase-interactive.c, and\n`edit_todo_list_advice' is copied there.  In the next commit,\ncomplete_action() will stop using it, and `edit_todo_list_advice' will\nbe removed from sequencer.c.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n rebase-interactive.c | 35 +++++++++++++++++++++++++++++++++++\n rebase-interactive.h |  2 ++\n sequencer.c          | 29 -----------------------------\n sequencer.h          |  1 -\n 4 files changed, 37 insertions(+), 30 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 1259adc8ea..45f29c28a8 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -6,6 +6,12 @@\n #include \"commit-slab.h\"\n #include \"config.h\"\n \n+static const char edit_todo_list_advice[] =\n+N_(\"You can fix this with 'git rebase --edit-todo' \"\n+\"and then run 'git rebase --continue'.\\n\"\n+\"Or you can abort the rebase with 'git rebase\"\n+\" --abort'.\\n\");\n+\n enum missing_commit_check_level {\n \tMISSING_COMMIT_CHECK_IGNORE = 0,\n \tMISSING_COMMIT_CHECK_WARN,\n@@ -189,3 +195,32 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n+\n+int check_todo_list_from_file(struct repository *r)\n+{\n+\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n+\tint res = 0;\n+\n+\tif (strbuf_read_file(&new_todo.buf, rebase_path_todo(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo());\n+\t\tgoto out;\n+\t}\n+\n+\tif (strbuf_read_file(&old_todo.buf, rebase_path_todo_backup(), 0) < 0) {\n+\t\tres = error(_(\"could not read '%s'.\"), rebase_path_todo_backup());\n+\t\tgoto out;\n+\t}\n+\n+\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n+\tif (!res)\n+\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n+\tif (!res)\n+\t\tres = todo_list_check(&old_todo, &new_todo);\n+\tif (res)\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+out:\n+\ttodo_list_release(&old_todo);\n+\ttodo_list_release(&new_todo);\n+\n+\treturn res;\n+}\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 44dbb06311..5f41bf5a28 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -13,4 +13,6 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   const char *shortonto, unsigned flags);\n int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n \n+int check_todo_list_from_file(struct repository *r);\n+\n #endif\ndiff --git a/sequencer.c b/sequencer.c\nindex 5f69b47e32..9365fc3e5a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4991,35 +4991,6 @@ N_(\"You can fix this with 'git rebase --edit-todo' \"\n \"Or you can abort the rebase with 'git rebase\"\n \" --abort'.\\n\");\n \n-int check_todo_list_from_file(struct repository *r)\n-{\n-\tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n-\tint res = 0;\n-\n-\tif (strbuf_read_file_or_whine(&new_todo.buf, rebase_path_todo()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tif (strbuf_read_file_or_whine(&old_todo.buf, rebase_path_todo_backup()) < 0) {\n-\t\tres = -1;\n-\t\tgoto out;\n-\t}\n-\n-\tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n-\tif (!res)\n-\t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n-\tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n-\tif (res)\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n-out:\n-\ttodo_list_release(&old_todo);\n-\ttodo_list_release(&new_todo);\n-\n-\treturn res;\n-}\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\ndiff --git a/sequencer.h b/sequencer.h\nindex 9f9ae291e3..5d4df0807c 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -155,7 +155,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,\n \n void todo_list_add_exec_commands(struct todo_list *todo_list,\n \t\t\t\t struct string_list *commands);\n-int check_todo_list_from_file(struct repository *r);\n int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,\n \t\t    const char *shortrevisions, const char *onto_name,\n \t\t    struct commit *onto, const char *orig_head, struct string_list *commands,\n-- \n2.24.1\n\n"},{"id":"390669","messageId":"20200128211246.10855-3-alban.gruin@gmail.com","threadId":"51492","inReplyTo":"20200128211246.10855-1-alban.gruin@gmail.com","subject":"[PATCH v6 2/2] rebase-interactive: warn if commit is dropped with `rebase --edit-todo'","fromName":"Alban Gruin","fromEmail":"alban.gruin@gmail.com","sentAt":"2020-01-28T21:12:46Z","receivedAt":"2020-01-28T21:17:48Z","isPatch":true,"sender":{"key":"alban.gruin@gmail.com","avatar":"https://avatars.githubusercontent.com/u/6310153?v=4"},"body":"When set to \"warn\" or \"error\", `rebase.missingCommitsCheck' would make\n`rebase -i' warn if the user removed commits from the todo list to\nprevent mistakes.  Unfortunately, `rebase --edit-todo' and `rebase\n--continue' don't take it into account.\n\nThis adds the ability for `rebase --edit-todo' and `rebase --continue'\nto check if commits were dropped by the user.  As both edit_todo_list()\nand complete_action() parse the todo list and check for dropped commits,\nthe code doing so in the latter is removed to reduce duplication.\n`edit_todo_list_advice' is removed from sequencer.c as it is no longer\nused there.\n\nThis changes when a backup of the todo list is made.  Until now, it was\nsaved only once, before the initial edit.  Now, it is also made if the\noriginal todo list has no errors or no dropped commits.  Thus, the\nbackup should be error-free.  Without this, sequencer_continue()\n(`rebase --continue') could only compare the current todo list against\nthe original, unedited list.  Before this change, this file was only\nused by edit_todo_list() and `rebase -p' to create the backup before\nthe initial edit, and check_todo_list_from_file(), only used by\n`rebase -p' to check for dropped commits after its own initial edit.\n\nIf the edited list has an error, a file, `dropped', is created to\nreport the issue.  Otherwise, it is deleted.  Usually, the edited list\nis compared against the list before editing, but if this file exists,\nit will be compared to the backup.  Also, if the file exists,\nsequencer_continue() checks the list for dropped commits.  If the\ncheck was performed every time, it would fail when resuming a rebase\nafter resolving a conflict, as the backup will contain commits that\nwere picked, but they will not be in the new list.  It's safe to\nignore this check if `dropped' does not exist, because that means that\nno errors were found at the last edition, so any missing commits here\nhave already been picked.\n\nFive tests are added to t3404.  The tests for\n`rebase.missingCommitsCheck = warn' and `rebase.missingCommitsCheck =\nerror' have a similar structure.  First, we start a rebase with an\nincorrect command on the first line.  Then, we edit the todo list,\nremoving the first and the last lines.  This demonstrates that\n`--edit-todo' notices dropped commits, but not when the command is\nincorrect.  Then, we restore the original todo list, and edit it to\nremove the last line.  This demonstrates that if we add a commit after\nthe initial edit, then remove it, `--edit-todo' will notice that it\nhas been dropped.  Then, the actual rebase takes place.  In the third\ntest, it is also checked that `--continue' will refuse to resume the\nrebase if commits were dropped.  The fourth test checks that no errors\nare raised when resuming a rebase after resolving a conflict, the fifth\nchecks that no errors are raised when editing the todo list after\npausing the rebase.\n\nSigned-off-by: Alban Gruin <alban.gruin@gmail.com>\n---\n\nNotes:\n    I don't think the way I create `expect.3' files in \"rebase --edit-todo\n    respects rebase.missingCommitsCheck = warning\" & \"... = error\" is the\n    best practice.  Perhaps I should create a new file from scratch instead\n    of calling `head' and `tail' successively?\n\n rebase-interactive.c          |  53 ++++++++++++---\n rebase-interactive.h          |   3 +\n sequencer.c                   |  22 +++----\n sequencer.h                   |   1 +\n t/t3404-rebase-interactive.sh | 121 ++++++++++++++++++++++++++++++++++\n 5 files changed, 179 insertions(+), 21 deletions(-)\n\ndiff --git a/rebase-interactive.c b/rebase-interactive.c\nindex 45f29c28a8..ac001dea58 100644\n--- a/rebase-interactive.c\n+++ b/rebase-interactive.c\n@@ -5,6 +5,7 @@\n #include \"strbuf.h\"\n #include \"commit-slab.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n \n static const char edit_todo_list_advice[] =\n N_(\"You can fix this with 'git rebase --edit-todo' \"\n@@ -97,21 +98,24 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags)\n {\n-\tconst char *todo_file = rebase_path_todo();\n+\tconst char *todo_file = rebase_path_todo(),\n+\t\t*todo_backup = rebase_path_todo_backup();\n \tunsigned initial = shortrevisions && shortonto;\n+\tint incorrect = 0;\n \n \t/* If the user is editing the todo list, we first try to parse\n \t * it.  If there is an error, we do not return, because the user\n \t * might want to fix it in the first place. */\n \tif (!initial)\n-\t\ttodo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list);\n+\t\tincorrect = todo_list_parse_insn_buffer(r, todo_list->buf.buf, todo_list) |\n+\t\t\tfile_exists(rebase_path_dropped());\n \n \tif (todo_list_write_to_file(r, todo_list, todo_file, shortrevisions, shortonto,\n \t\t\t\t    -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP))\n \t\treturn error_errno(_(\"could not write '%s'\"), todo_file);\n \n-\tif (initial &&\n-\t    todo_list_write_to_file(r, todo_list, rebase_path_todo_backup(),\n+\tif (!incorrect &&\n+\t    todo_list_write_to_file(r, todo_list, todo_backup,\n \t\t\t\t    shortrevisions, shortonto, -1,\n \t\t\t\t    (flags | TODO_LIST_APPEND_TODO_HELP) & ~TODO_LIST_SHORTEN_IDS) < 0)\n \t\treturn error(_(\"could not write '%s'.\"), rebase_path_todo_backup());\n@@ -123,10 +127,23 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \tif (initial && new_todo->buf.len == 0)\n \t\treturn -3;\n \n-\t/* For the initial edit, the todo list gets parsed in\n-\t * complete_action(). */\n-\tif (!initial)\n-\t\treturn todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo);\n+\tif (todo_list_parse_insn_buffer(r, new_todo->buf.buf, new_todo)) {\n+\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t\treturn -4;\n+\t}\n+\n+\tif (incorrect) {\n+\t\tif (todo_list_check_against_backup(r, new_todo)) {\n+\t\t\twrite_file(rebase_path_dropped(), \"\");\n+\t\t\treturn -4;\n+\t\t}\n+\n+\t\tif (incorrect > 0)\n+\t\t\tunlink(rebase_path_dropped());\n+\t} else if (todo_list_check(todo_list, new_todo)) {\n+\t\twrite_file(rebase_path_dropped(), \"\");\n+\t\treturn -4;\n+\t}\n \n \treturn 0;\n }\n@@ -191,11 +208,27 @@ int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo)\n \t\t\"the level of warnings.\\n\"\n \t\t\"The possible behaviours are: ignore, warn, error.\\n\\n\"));\n \n+\tfprintf(stderr, _(edit_todo_list_advice));\n+\n leave_check:\n \tclear_commit_seen(&commit_seen);\n \treturn res;\n }\n \n+int todo_list_check_against_backup(struct repository *r, struct todo_list *todo_list)\n+{\n+\tstruct todo_list backup = TODO_LIST_INIT;\n+\tint res = 0;\n+\n+\tif (strbuf_read_file(&backup.buf, rebase_path_todo_backup(), 0) > 0) {\n+\t\ttodo_list_parse_insn_buffer(r, backup.buf.buf, &backup);\n+\t\tres = todo_list_check(&backup, todo_list);\n+\t}\n+\n+\ttodo_list_release(&backup);\n+\treturn res;\n+}\n+\n int check_todo_list_from_file(struct repository *r)\n {\n \tstruct todo_list old_todo = TODO_LIST_INIT, new_todo = TODO_LIST_INIT;\n@@ -214,10 +247,10 @@ int check_todo_list_from_file(struct repository *r)\n \tres = todo_list_parse_insn_buffer(r, old_todo.buf.buf, &old_todo);\n \tif (!res)\n \t\tres = todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo);\n-\tif (!res)\n-\t\tres = todo_list_check(&old_todo, &new_todo);\n \tif (res)\n \t\tfprintf(stderr, _(edit_todo_list_advice));\n+\tif (!res)\n+\t\tres = todo_list_check(&old_todo, &new_todo);\n out:\n \ttodo_list_release(&old_todo);\n \ttodo_list_release(&new_todo);\ndiff --git a/rebase-interactive.h b/rebase-interactive.h\nindex 5f41bf5a28..4af0c1fcc7 100644\n--- a/rebase-interactive.h\n+++ b/rebase-interactive.h\n@@ -11,7 +11,10 @@ void append_todo_help(unsigned keep_empty, int command_count,\n int edit_todo_list(struct repository *r, struct todo_list *todo_list,\n \t\t   struct todo_list *new_todo, const char *shortrevisions,\n \t\t   const char *shortonto, unsigned flags);\n+\n int todo_list_check(struct todo_list *old_todo, struct todo_list *new_todo);\n+int todo_list_check_against_backup(struct repository *r,\n+\t\t\t\t   struct todo_list *todo_list);\n \n int check_todo_list_from_file(struct repository *r);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 9365fc3e5a..638098b936 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -57,6 +57,8 @@ static GIT_PATH_FUNC(rebase_path, \"rebase-merge\")\n GIT_PATH_FUNC(rebase_path_todo, \"rebase-merge/git-rebase-todo\")\n GIT_PATH_FUNC(rebase_path_todo_backup, \"rebase-merge/git-rebase-todo.backup\")\n \n+GIT_PATH_FUNC(rebase_path_dropped, \"rebase-merge/dropped\")\n+\n /*\n  * The rebase command lines that have already been processed. A line\n  * is moved here when it is first handled, before any associated user\n@@ -4239,6 +4241,14 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts)\n \tif (is_rebase_i(opts)) {\n \t\tif ((res = read_populate_todo(r, &todo_list, opts)))\n \t\t\tgoto release_todo_list;\n+\n+\t\tif (file_exists(rebase_path_dropped())) {\n+\t\t\tif ((res = todo_list_check_against_backup(r, &todo_list)))\n+\t\t\t\tgoto release_todo_list;\n+\n+\t\t\tunlink(rebase_path_dropped());\n+\t\t}\n+\n \t\tif (commit_staged_changes(r, opts, &todo_list)) {\n \t\t\tres = -1;\n \t\t\tgoto release_todo_list;\n@@ -4985,12 +4995,6 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,\n \treturn res;\n }\n \n-static const char edit_todo_list_advice[] =\n-N_(\"You can fix this with 'git rebase --edit-todo' \"\n-\"and then run 'git rebase --continue'.\\n\"\n-\"Or you can abort the rebase with 'git rebase\"\n-\" --abort'.\\n\");\n-\n /* skip picking commits whose parents are unchanged */\n static int skip_unnecessary_picks(struct repository *r,\n \t\t\t\t  struct todo_list *todo_list,\n@@ -5088,11 +5092,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla\n \t\ttodo_list_release(&new_todo);\n \n \t\treturn error(_(\"nothing to do\"));\n-\t}\n-\n-\tif (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) ||\n-\t    todo_list_check(todo_list, &new_todo)) {\n-\t\tfprintf(stderr, _(edit_todo_list_advice));\n+\t} else if (res == -4) {\n \t\tcheckout_onto(r, opts, onto_name, &onto->object.oid, orig_head);\n \t\ttodo_list_release(&new_todo);\n \ndiff --git a/sequencer.h b/sequencer.h\nindex 5d4df0807c..84dc4160d5 100644\n--- a/sequencer.h\n+++ b/sequencer.h\n@@ -11,6 +11,7 @@ const char *git_path_commit_editmsg(void);\n const char *git_path_seq_dir(void);\n const char *rebase_path_todo(void);\n const char *rebase_path_todo_backup(void);\n+const char *rebase_path_dropped(void);\n \n #define APPEND_SIGNOFF_DEDUP (1u << 0)\n \ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex b90ea0fe44..d79a3ef505 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1463,6 +1463,127 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '\n \ttest B = $(git cat-file commit HEAD^ | sed -ne \\$p)\n '\n \n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = ignore' '\n+\ttest_config rebase.missingCommitsCheck ignore &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"break 1 2 3 4 5\" git rebase -i --root &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = warn' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\tEOF\n+\thead -n4 expect >expect.2 &&\n+\ttail -n1 expect >>expect.2 &&\n+\ttail -n4 expect.2 >expect.3 &&\n+\ttest_config rebase.missingCommitsCheck warn &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\tFAKE_LINES=\"2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n6 actual.2 >actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4\" git rebase --edit-todo 2>actual.2 &&\n+\t\thead -n4 actual.2 >actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase --edit-todo respects rebase.missingCommitsCheck = error' '\n+\tcat >expect <<-EOF &&\n+\terror: invalid line 1: badcmd $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tWarning: some commits may have been dropped accidentally.\n+\tDropped commits (newer to older):\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master)\n+\t - $(git rev-list --pretty=oneline --abbrev-commit -1 master~4)\n+\tTo avoid this message, use \"drop\" to explicitly remove a commit.\n+\n+\tUse '\\''git config rebase.missingCommitsCheck'\\'' to change the level of warnings.\n+\tThe possible behaviours are: ignore, warn, error.\n+\n+\tYou can fix this with '\\''git rebase --edit-todo'\\'' and then run '\\''git rebase --continue'\\''.\n+\tOr you can abort the rebase with '\\''git rebase --abort'\\''.\n+\tEOF\n+\ttail -n11 expect >expect.2 &&\n+\thead -n3 expect.2 >expect.3 &&\n+\ttail -n7 expect.2 >>expect.3 &&\n+\ttest_config rebase.missingCommitsCheck error &&\n+\trebase_setup_and_clean missing-commit &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\ttest_must_fail env FAKE_LINES=\"bad 1 2 3 4 5\" \\\n+\t\t\tgit rebase -i --root &&\n+\t\tcp .git/rebase-merge/git-rebase-todo.backup orig &&\n+\t\ttest_must_fail env FAKE_LINES=\"2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect.2 actual &&\n+\t\ttest_must_fail git rebase --edit-todo &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\ttest_must_fail env FAKE_LINES=\"1 2 3 4\" \\\n+\t\t\tgit rebase --edit-todo 2>actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\ttest_must_fail git rebase --continue 2>actual &&\n+\t\ttest_i18ncmp expect.3 actual &&\n+\t\tcp orig .git/rebase-merge/git-rebase-todo &&\n+\t\tFAKE_LINES=\"1 2 3 4 drop 5\" git rebase --edit-todo &&\n+\t\tgit rebase --continue 2>actual\n+\t) &&\n+\ttest D = $(git cat-file commit HEAD | sed -ne \\$p) &&\n+\ttest_i18ngrep \\\n+\t\t\"Successfully rebased and updated refs/heads/missing-commit\" \\\n+\t\tactual\n+'\n+\n+test_expect_success 'rebase.missingCommitsCheck = error after resolving conflicts' '\n+\ttest_config rebase.missingCommitsCheck error &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"drop 1 break 2 3 4\" git rebase -i A E\n+\t) &&\n+\tgit rebase --edit-todo &&\n+\ttest_must_fail git rebase --continue &&\n+\techo x >file1 &&\n+\tgit add file1 &&\n+\tgit rebase --continue\n+'\n+\n+test_expect_success 'rebase.missingCommitsCheck = error when editing for a second time' '\n+\ttest_config rebase.missingCommitsCheck error &&\n+\t(\n+\t\tset_fake_editor &&\n+\t\tFAKE_LINES=\"1 break 2 3\" git rebase -i A D &&\n+\t\tcp .git/rebase-merge/git-rebase-todo todo &&\n+\t\ttest_must_fail env FAKE_LINES=2 git rebase --edit-todo &&\n+\t\tGIT_SEQUENCE_EDITOR=\"cp todo\" git rebase --edit-todo &&\n+\t\tgit rebase --continue\n+\t)\n+'\n+\n test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' '\n \trebase_setup_and_clean abbrevcmd &&\n \ttest_commit \"first\" file1.txt \"first line\" first &&\n-- \n2.24.1\n\n"},{"id":"390674","messageId":"nycvar.QRO.7.76.6.2001282346180.46@tvgsbejvaqbjf.bet","threadId":"51492","inReplyTo":"5f9b02f2-6b77-91da-2af1-d36cb6b137a3@gmail.com","subject":"Re: [PATCH v5 0/2] rebase -i: extend rebase.missingCommitsCheck","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-01-28T22:46:51Z","receivedAt":"2020-01-28T22:47:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Alban,\n\nOn Mon, 27 Jan 2020, Alban Gruin wrote:\n\n> Le 26/01/2020 à 11:04, Johannes Schindelin a écrit :\n>\n> > The way I read your patches, they will conflict with\n> > `js/rebase-i-with-colliding-hash`, so maybe it would be a good idea to\n> > base your patches on that branch?\n> >\n>\n> Let's do this.  This will also solve the conflict this series already\n> has with master.\n\nThank you!\nDscho\n"}]}