{"thread":{"id":"51020","subject":"[PATCH 0/2] Add \"git merge --quit\"","startedAt":"2019-05-01T13:12:02Z","lastAt":"2019-05-20T18:06:12Z","messageCount":20,"participants":["Nguyễn Thái Ngọc Duy","Phillip Wood","Emily Shaffer","Johannes Schindelin","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"374761","messageId":"20190501131152.314-1-pclouds@gmail.com","threadId":"51020","inReplyTo":null,"subject":"[PATCH 0/2] Add \"git merge --quit\"","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-01T13:11:50Z","receivedAt":"2019-05-01T13:12:02Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"nd/switch-and-restore suggests 'git merge --quit' to get out of a merge\neven though this option is not implemented [1]. It's a soft dependency, no\nactual functionality is broken by the lack of --quit, so I'm sending\nit separately.\n\n[1] https://public-inbox.org/git/78c7c281-82ec-2ba9-a607-dd2ecba54945@gmail.com/\n\nNguyễn Thái Ngọc Duy (2):\n  merge: remove drop_save() in favor of remove_merge_branch_state()\n  merge: add --quit\n\n Documentation/git-merge.txt |  4 ++++\n branch.c                    | 11 ++++++++---\n branch.h                    |  6 ++++++\n builtin/merge.c             | 30 ++++++++++++++++++------------\n t/t7600-merge.sh            | 14 ++++++++++++++\n 5 files changed, 50 insertions(+), 15 deletions(-)\n\n-- \n2.21.0.1110.g9614c01b33\n\n"},{"id":"374762","messageId":"20190501131152.314-2-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190501131152.314-1-pclouds@gmail.com","subject":"[PATCH 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-01T13:11:51Z","receivedAt":"2019-05-01T13:12:07Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Both remove_branch_state() and drop_save() delete almost the same set of\nfiles about the current merge state. The only difference is MERGE_RR but\nit should also be cleaned up after a successful merge, which is what\ndrop_save() is for.\n\nMake a new function that deletes all merge-related state files and use\nit instead of drop_save(). This function will also be used in the next\npatch that introduces --quit.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n branch.c        | 11 ++++++++---\n branch.h        |  6 ++++++\n builtin/merge.c | 17 +++++------------\n 3 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 28b81a7e02..1db0601a11 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -337,15 +337,20 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n-void remove_branch_state(struct repository *r)\n+void remove_merge_branch_state(struct repository *r)\n {\n-\tunlink(git_path_cherry_pick_head(r));\n-\tunlink(git_path_revert_head(r));\n \tunlink(git_path_merge_head(r));\n \tunlink(git_path_merge_rr(r));\n \tunlink(git_path_merge_msg(r));\n \tunlink(git_path_merge_mode(r));\n+}\n+\n+void remove_branch_state(struct repository *r)\n+{\n+\tunlink(git_path_cherry_pick_head(r));\n+\tunlink(git_path_revert_head(r));\n \tunlink(git_path_squash_msg(r));\n+\tremove_merge_branch_state(r);\n }\n \n void die_if_checked_out(const char *branch, int ignore_current_worktree)\ndiff --git a/branch.h b/branch.h\nindex 29c1afa4d0..c90ba9d7bf 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref);\n  */\n extern int validate_new_branchname(const char *name, struct strbuf *ref, int force);\n \n+/*\n+ * Remove information about the merge state on the current\n+ * branch. (E.g., MERGE_HEAD)\n+ */\n+void remove_merge_branch_state(struct repository *r);\n+\n /*\n  * Remove information about the state of working on the current\n  * branch. (E.g., MERGE_HEAD)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 5ce8946d39..0fd448b403 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -37,6 +37,7 @@\n #include \"packfile.h\"\n #include \"tag.h\"\n #include \"alias.h\"\n+#include \"branch.h\"\n #include \"commit-reach.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n@@ -282,14 +283,6 @@ static struct option builtin_merge_options[] = {\n \tOPT_END()\n };\n \n-/* Cleans up metadata that is uninteresting after a succeeded merge. */\n-static void drop_save(void)\n-{\n-\tunlink(git_path_merge_head(the_repository));\n-\tunlink(git_path_merge_msg(the_repository));\n-\tunlink(git_path_merge_mode(the_repository));\n-}\n-\n static int save_state(struct object_id *stash)\n {\n \tint len;\n@@ -383,7 +376,7 @@ static void finish_up_to_date(const char *msg)\n {\n \tif (verbosity >= 0)\n \t\tprintf(\"%s%s\\n\", squash ? _(\" (nothing to squash)\") : \"\", msg);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n }\n \n static void squash_message(struct commit *commit, struct commit_list *remoteheads)\n@@ -861,7 +854,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n \t\t\t&result_commit, NULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \tfinish(head, remoteheads, &result_commit, \"In-index merge\");\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -888,7 +881,7 @@ static int finish_automerge(struct commit *head,\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(head, remoteheads, &result_commit, buf.buf);\n \tstrbuf_release(&buf);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -1466,7 +1459,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t}\n \n \t\tfinish(head_commit, remoteheads, &commit->object.oid, msg.buf);\n-\t\tdrop_save();\n+\t\tremove_merge_branch_state(the_repository);\n \t\tgoto done;\n \t} else if (!remoteheads->next && common->next)\n \t\t;\n-- \n2.21.0.1110.g9614c01b33\n\n"},{"id":"374763","messageId":"20190501131152.314-3-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190501131152.314-1-pclouds@gmail.com","subject":"[PATCH 2/2] merge: add --quit","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-01T13:11:52Z","receivedAt":"2019-05-01T13:12:12Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows to cancel the current merge without reseting worktree/index,\nwhich is what --abort is for. Like other --quit(s), this is often used\nwhen you forgot that you're in the middle of a merge and already\nswitched away, doing different things. By the time you're realize, you\ncan't even continue the merge anymore.\n\nThis also makes all in-progress commands, am, merge, rebase, revert and\ncherry-pick, take all three --abort, --continue and --quit (bisect has a\ndifferent UI).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-merge.txt |  4 ++++\n builtin/merge.c             | 13 +++++++++++++\n t/t7600-merge.sh            | 14 ++++++++++++++\n 3 files changed, 31 insertions(+)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 6294dbc09d..c01cfa6595 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -100,6 +100,10 @@ commit or stash your changes before running 'git merge'.\n 'git merge --abort' is equivalent to 'git reset --merge' when\n `MERGE_HEAD` is present.\n \n+--quit::\n+\tForget about the current merge in progress. Leave the index\n+\tand the working tree as-is.\n+\n --continue::\n \tAfter a 'git merge' stops due to conflicts you can conclude the\n \tmerge by running 'git merge --continue' (see \"HOW TO RESOLVE\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 0fd448b403..13392ba1cf 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -73,6 +73,7 @@ static int option_renormalize;\n static int verbosity;\n static int allow_rerere_auto;\n static int abort_current_merge;\n+static int quit_current_merge;\n static int continue_current_merge;\n static int allow_unrelated_histories;\n static int show_progress = -1;\n@@ -270,6 +271,8 @@ static struct option builtin_merge_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"abort\", &abort_current_merge,\n \t\tN_(\"abort the current in-progress merge\")),\n+\tOPT_BOOL(0, \"quit\", &quit_current_merge,\n+\t\tN_(\"--abort but leave index and working tree alone\")),\n \tOPT_BOOL(0, \"continue\", &continue_current_merge,\n \t\tN_(\"continue the current in-progress merge\")),\n \tOPT_BOOL(0, \"allow-unrelated-histories\", &allow_unrelated_histories,\n@@ -1255,6 +1258,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tgoto done;\n \t}\n \n+\tif (quit_current_merge) {\n+\t\tif (orig_argc != 2)\n+\t\t\tusage_msg_opt(_(\"--quit expects no arguments\"),\n+\t\t\t\t      builtin_merge_usage,\n+\t\t\t\t      builtin_merge_options);\n+\n+\t\tremove_merge_branch_state(the_repository);\n+\t\tgoto done;\n+\t}\n+\n \tif (continue_current_merge) {\n \t\tint nargc = 1;\n \t\tconst char *nargv[] = {\"commit\", NULL};\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 106148254d..ea82cb744b 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n \tverify_parents $c0 $c1\n '\n \n+test_expect_success 'merge --quit' '\n+\tgit reset --hard side &&\n+\ttest_must_fail git -c rerere.enabled=true merge master &&\n+\ttest_path_is_file .git/MERGE_HEAD &&\n+\ttest_path_is_file .git/MERGE_MODE &&\n+\ttest_path_is_file .git/MERGE_MSG &&\n+\ttest_path_is_file .git/MERGE_RR &&\n+\tgit merge --quit &&\n+\ttest_path_is_missing .git/MERGE_HEAD &&\n+\ttest_path_is_missing .git/MERGE_MODE &&\n+\ttest_path_is_missing .git/MERGE_MSG &&\n+\ttest_path_is_missing .git/MERGE_RR\n+'\n+\n test_done\n-- \n2.21.0.1110.g9614c01b33\n\n"},{"id":"374808","messageId":"5c62bf2a-5bf1-6aff-6343-cd8471e662c2@gmail.com","threadId":"51020","inReplyTo":"20190501131152.314-1-pclouds@gmail.com","subject":"Re: [PATCH 0/2] Add \"git merge --quit\"","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-05-02T10:13:23Z","receivedAt":"2019-05-02T10:13:28Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Duy\n\nOn 01/05/2019 14:11, Nguyễn Thái Ngọc Duy wrote:\n> nd/switch-and-restore suggests 'git merge --quit' to get out of a merge\n> even though this option is not implemented [1]. It's a soft dependency, no\n> actual functionality is broken by the lack of --quit, so I'm sending\n> it separately.\n\nBoth patches look good to me\n\nBest Wishes\n\nPhillip\n\n> \n> [1] https://public-inbox.org/git/78c7c281-82ec-2ba9-a607-dd2ecba54945@gmail.com/\n> \n> Nguyễn Thái Ngọc Duy (2):\n>   merge: remove drop_save() in favor of remove_merge_branch_state()\n>   merge: add --quit\n> \n>  Documentation/git-merge.txt |  4 ++++\n>  branch.c                    | 11 ++++++++---\n>  branch.h                    |  6 ++++++\n>  builtin/merge.c             | 30 ++++++++++++++++++------------\n>  t/t7600-merge.sh            | 14 ++++++++++++++\n>  5 files changed, 50 insertions(+), 15 deletions(-)\n> \n\n"},{"id":"374847","messageId":"20190502214935.GA116416@google.com","threadId":"51020","inReplyTo":"20190501131152.314-3-pclouds@gmail.com","subject":"Re: [PATCH 2/2] merge: add --quit","fromName":"Emily Shaffer","fromEmail":"emilyshaffer@google.com","sentAt":"2019-05-02T21:49:35Z","receivedAt":"2019-05-02T21:49:42Z","isPatch":true,"sender":{"key":"nasamuffin@google.com","avatar":"https://avatars.githubusercontent.com/u/1606826?v=4"},"body":"They both look fine to me, besides a couple typos in the commit message\nin this one.\n\nOn Wed, May 01, 2019 at 08:11:52PM +0700, Nguyễn Thái Ngọc Duy wrote:\n> This allows to cancel the current merge without reseting worktree/index,\n\n\"resetting\".\n\n> which is what --abort is for. Like other --quit(s), this is often used\n> when you forgot that you're in the middle of a merge and already\n> switched away, doing different things. By the time you're realize, you\n\"By the time you've realized\" ?\n\n> can't even continue the merge anymore.\n\nThanks,\nEmily\n"},{"id":"375211","messageId":"20190509101028.24656-1-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190501131152.314-1-pclouds@gmail.com","subject":"[PATCH v2 0/2] nd/merge-quit update","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-09T10:10:26Z","receivedAt":"2019-05-09T10:10:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"A couple typos in the commit message. No code change.\n\nNguyễn Thái Ngọc Duy (2):\n  merge: remove drop_save() in favor of remove_merge_branch_state()\n  merge: add --quit\n\n Documentation/git-merge.txt |  4 ++++\n branch.c                    | 11 ++++++++---\n branch.h                    |  6 ++++++\n builtin/merge.c             | 30 ++++++++++++++++++------------\n t/t7600-merge.sh            | 14 ++++++++++++++\n 5 files changed, 50 insertions(+), 15 deletions(-)\n\nRange-diff dựa trên v1:\n1:  a87e56a43a ! 1:  51710c4c6c merge: add --quit\n    @@ -2,10 +2,10 @@\n     \n         merge: add --quit\n     \n    -    This allows to cancel the current merge without reseting worktree/index,\n    +    This allows to cancel the current merge without resetting worktree/index,\n         which is what --abort is for. Like other --quit(s), this is often used\n         when you forgot that you're in the middle of a merge and already\n    -    switched away, doing different things. By the time you're realize, you\n    +    switched away, doing different things. By the time you've realized, you\n         can't even continue the merge anymore.\n     \n         This also makes all in-progress commands, am, merge, rebase, revert and\n-- \n2.21.0.1141.gd54ac2cb17\n\n"},{"id":"375212","messageId":"20190509101028.24656-2-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190509101028.24656-1-pclouds@gmail.com","subject":"[PATCH v2 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-09T10:10:27Z","receivedAt":"2019-05-09T10:10:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Both remove_branch_state() and drop_save() delete almost the same set of\nfiles about the current merge state. The only difference is MERGE_RR but\nit should also be cleaned up after a successful merge, which is what\ndrop_save() is for.\n\nMake a new function that deletes all merge-related state files and use\nit instead of drop_save(). This function will also be used in the next\npatch that introduces --quit.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n branch.c        | 11 ++++++++---\n branch.h        |  6 ++++++\n builtin/merge.c | 17 +++++------------\n 3 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 28b81a7e02..1db0601a11 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -337,15 +337,20 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n-void remove_branch_state(struct repository *r)\n+void remove_merge_branch_state(struct repository *r)\n {\n-\tunlink(git_path_cherry_pick_head(r));\n-\tunlink(git_path_revert_head(r));\n \tunlink(git_path_merge_head(r));\n \tunlink(git_path_merge_rr(r));\n \tunlink(git_path_merge_msg(r));\n \tunlink(git_path_merge_mode(r));\n+}\n+\n+void remove_branch_state(struct repository *r)\n+{\n+\tunlink(git_path_cherry_pick_head(r));\n+\tunlink(git_path_revert_head(r));\n \tunlink(git_path_squash_msg(r));\n+\tremove_merge_branch_state(r);\n }\n \n void die_if_checked_out(const char *branch, int ignore_current_worktree)\ndiff --git a/branch.h b/branch.h\nindex 29c1afa4d0..c90ba9d7bf 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref);\n  */\n extern int validate_new_branchname(const char *name, struct strbuf *ref, int force);\n \n+/*\n+ * Remove information about the merge state on the current\n+ * branch. (E.g., MERGE_HEAD)\n+ */\n+void remove_merge_branch_state(struct repository *r);\n+\n /*\n  * Remove information about the state of working on the current\n  * branch. (E.g., MERGE_HEAD)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e47d77baee..e9663f027a 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -37,6 +37,7 @@\n #include \"packfile.h\"\n #include \"tag.h\"\n #include \"alias.h\"\n+#include \"branch.h\"\n #include \"commit-reach.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n@@ -279,14 +280,6 @@ static struct option builtin_merge_options[] = {\n \tOPT_END()\n };\n \n-/* Cleans up metadata that is uninteresting after a succeeded merge. */\n-static void drop_save(void)\n-{\n-\tunlink(git_path_merge_head(the_repository));\n-\tunlink(git_path_merge_msg(the_repository));\n-\tunlink(git_path_merge_mode(the_repository));\n-}\n-\n static int save_state(struct object_id *stash)\n {\n \tint len;\n@@ -380,7 +373,7 @@ static void finish_up_to_date(const char *msg)\n {\n \tif (verbosity >= 0)\n \t\tprintf(\"%s%s\\n\", squash ? _(\" (nothing to squash)\") : \"\", msg);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n }\n \n static void squash_message(struct commit *commit, struct commit_list *remoteheads)\n@@ -858,7 +851,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n \t\t\t&result_commit, NULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \tfinish(head, remoteheads, &result_commit, \"In-index merge\");\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -885,7 +878,7 @@ static int finish_automerge(struct commit *head,\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(head, remoteheads, &result_commit, buf.buf);\n \tstrbuf_release(&buf);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -1463,7 +1456,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t}\n \n \t\tfinish(head_commit, remoteheads, &commit->object.oid, msg.buf);\n-\t\tdrop_save();\n+\t\tremove_merge_branch_state(the_repository);\n \t\tgoto done;\n \t} else if (!remoteheads->next && common->next)\n \t\t;\n-- \n2.21.0.1141.gd54ac2cb17\n\n"},{"id":"375213","messageId":"20190509101028.24656-3-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190509101028.24656-1-pclouds@gmail.com","subject":"[PATCH v2 2/2] merge: add --quit","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-09T10:10:28Z","receivedAt":"2019-05-09T10:10:57Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows to cancel the current merge without resetting worktree/index,\nwhich is what --abort is for. Like other --quit(s), this is often used\nwhen you forgot that you're in the middle of a merge and already\nswitched away, doing different things. By the time you've realized, you\ncan't even continue the merge anymore.\n\nThis also makes all in-progress commands, am, merge, rebase, revert and\ncherry-pick, take all three --abort, --continue and --quit (bisect has a\ndifferent UI).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-merge.txt |  4 ++++\n builtin/merge.c             | 13 +++++++++++++\n t/t7600-merge.sh            | 14 ++++++++++++++\n 3 files changed, 31 insertions(+)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 4cc86469f3..b7d581fc76 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -99,6 +99,10 @@ commit or stash your changes before running 'git merge'.\n 'git merge --abort' is equivalent to 'git reset --merge' when\n `MERGE_HEAD` is present.\n \n+--quit::\n+\tForget about the current merge in progress. Leave the index\n+\tand the working tree as-is.\n+\n --continue::\n \tAfter a 'git merge' stops due to conflicts you can conclude the\n \tmerge by running 'git merge --continue' (see \"HOW TO RESOLVE\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e9663f027a..598d56edfe 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -73,6 +73,7 @@ static int option_renormalize;\n static int verbosity;\n static int allow_rerere_auto;\n static int abort_current_merge;\n+static int quit_current_merge;\n static int continue_current_merge;\n static int allow_unrelated_histories;\n static int show_progress = -1;\n@@ -267,6 +268,8 @@ static struct option builtin_merge_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"abort\", &abort_current_merge,\n \t\tN_(\"abort the current in-progress merge\")),\n+\tOPT_BOOL(0, \"quit\", &quit_current_merge,\n+\t\tN_(\"--abort but leave index and working tree alone\")),\n \tOPT_BOOL(0, \"continue\", &continue_current_merge,\n \t\tN_(\"continue the current in-progress merge\")),\n \tOPT_BOOL(0, \"allow-unrelated-histories\", &allow_unrelated_histories,\n@@ -1252,6 +1255,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tgoto done;\n \t}\n \n+\tif (quit_current_merge) {\n+\t\tif (orig_argc != 2)\n+\t\t\tusage_msg_opt(_(\"--quit expects no arguments\"),\n+\t\t\t\t      builtin_merge_usage,\n+\t\t\t\t      builtin_merge_options);\n+\n+\t\tremove_merge_branch_state(the_repository);\n+\t\tgoto done;\n+\t}\n+\n \tif (continue_current_merge) {\n \t\tint nargc = 1;\n \t\tconst char *nargv[] = {\"commit\", NULL};\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 106148254d..ea82cb744b 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n \tverify_parents $c0 $c1\n '\n \n+test_expect_success 'merge --quit' '\n+\tgit reset --hard side &&\n+\ttest_must_fail git -c rerere.enabled=true merge master &&\n+\ttest_path_is_file .git/MERGE_HEAD &&\n+\ttest_path_is_file .git/MERGE_MODE &&\n+\ttest_path_is_file .git/MERGE_MSG &&\n+\ttest_path_is_file .git/MERGE_RR &&\n+\tgit merge --quit &&\n+\ttest_path_is_missing .git/MERGE_HEAD &&\n+\ttest_path_is_missing .git/MERGE_MODE &&\n+\ttest_path_is_missing .git/MERGE_MSG &&\n+\ttest_path_is_missing .git/MERGE_RR\n+'\n+\n test_done\n-- \n2.21.0.1141.gd54ac2cb17\n\n"},{"id":"375478","messageId":"20190514091322.12427-1-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190509101028.24656-1-pclouds@gmail.com","subject":"[PATCH v3 0/2] nd/merge-quit updates","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-14T09:13:20Z","receivedAt":"2019-05-14T09:13:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"v3 fixes the test breakage when GPG tests are skipped ('side' branch is\naffected by these skipped tests)\n\nNguyễn Thái Ngọc Duy (2):\n  merge: remove drop_save() in favor of remove_merge_branch_state()\n  merge: add --quit\n\n Documentation/git-merge.txt |  4 ++++\n branch.c                    | 11 ++++++++---\n branch.h                    |  6 ++++++\n builtin/merge.c             | 30 ++++++++++++++++++------------\n t/t7600-merge.sh            | 14 ++++++++++++++\n 5 files changed, 50 insertions(+), 15 deletions(-)\n\nRange-diff dựa trên v2:\n1:  324d237f0c ! 1:  86dd0fd99c merge: add --quit\n    @@ -13,7 +13,6 @@\n         different UI).\n     \n         Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\n      --- a/Documentation/git-merge.txt\n    @@ -76,7 +75,7 @@\n      '\n      \n     +test_expect_success 'merge --quit' '\n    -+\tgit reset --hard side &&\n    ++\tgit reset --hard c2 &&\n     +\ttest_must_fail git -c rerere.enabled=true merge master &&\n     +\ttest_path_is_file .git/MERGE_HEAD &&\n     +\ttest_path_is_file .git/MERGE_MODE &&\n-- \n2.21.0.1141.gd54ac2cb17\n\n"},{"id":"375479","messageId":"20190514091322.12427-2-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190514091322.12427-1-pclouds@gmail.com","subject":"[PATCH v3 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-14T09:13:21Z","receivedAt":"2019-05-14T09:13:42Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Both remove_branch_state() and drop_save() delete almost the same set of\nfiles about the current merge state. The only difference is MERGE_RR but\nit should also be cleaned up after a successful merge, which is what\ndrop_save() is for.\n\nMake a new function that deletes all merge-related state files and use\nit instead of drop_save(). This function will also be used in the next\npatch that introduces --quit.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n branch.c        | 11 ++++++++---\n branch.h        |  6 ++++++\n builtin/merge.c | 17 +++++------------\n 3 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 28b81a7e02..1db0601a11 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -337,15 +337,20 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n-void remove_branch_state(struct repository *r)\n+void remove_merge_branch_state(struct repository *r)\n {\n-\tunlink(git_path_cherry_pick_head(r));\n-\tunlink(git_path_revert_head(r));\n \tunlink(git_path_merge_head(r));\n \tunlink(git_path_merge_rr(r));\n \tunlink(git_path_merge_msg(r));\n \tunlink(git_path_merge_mode(r));\n+}\n+\n+void remove_branch_state(struct repository *r)\n+{\n+\tunlink(git_path_cherry_pick_head(r));\n+\tunlink(git_path_revert_head(r));\n \tunlink(git_path_squash_msg(r));\n+\tremove_merge_branch_state(r);\n }\n \n void die_if_checked_out(const char *branch, int ignore_current_worktree)\ndiff --git a/branch.h b/branch.h\nindex 29c1afa4d0..c90ba9d7bf 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref);\n  */\n extern int validate_new_branchname(const char *name, struct strbuf *ref, int force);\n \n+/*\n+ * Remove information about the merge state on the current\n+ * branch. (E.g., MERGE_HEAD)\n+ */\n+void remove_merge_branch_state(struct repository *r);\n+\n /*\n  * Remove information about the state of working on the current\n  * branch. (E.g., MERGE_HEAD)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e47d77baee..e9663f027a 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -37,6 +37,7 @@\n #include \"packfile.h\"\n #include \"tag.h\"\n #include \"alias.h\"\n+#include \"branch.h\"\n #include \"commit-reach.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n@@ -279,14 +280,6 @@ static struct option builtin_merge_options[] = {\n \tOPT_END()\n };\n \n-/* Cleans up metadata that is uninteresting after a succeeded merge. */\n-static void drop_save(void)\n-{\n-\tunlink(git_path_merge_head(the_repository));\n-\tunlink(git_path_merge_msg(the_repository));\n-\tunlink(git_path_merge_mode(the_repository));\n-}\n-\n static int save_state(struct object_id *stash)\n {\n \tint len;\n@@ -380,7 +373,7 @@ static void finish_up_to_date(const char *msg)\n {\n \tif (verbosity >= 0)\n \t\tprintf(\"%s%s\\n\", squash ? _(\" (nothing to squash)\") : \"\", msg);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n }\n \n static void squash_message(struct commit *commit, struct commit_list *remoteheads)\n@@ -858,7 +851,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n \t\t\t&result_commit, NULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \tfinish(head, remoteheads, &result_commit, \"In-index merge\");\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -885,7 +878,7 @@ static int finish_automerge(struct commit *head,\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(head, remoteheads, &result_commit, buf.buf);\n \tstrbuf_release(&buf);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -1463,7 +1456,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t}\n \n \t\tfinish(head_commit, remoteheads, &commit->object.oid, msg.buf);\n-\t\tdrop_save();\n+\t\tremove_merge_branch_state(the_repository);\n \t\tgoto done;\n \t} else if (!remoteheads->next && common->next)\n \t\t;\n-- \n2.21.0.1141.gd54ac2cb17\n\n"},{"id":"375480","messageId":"20190514091322.12427-3-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190514091322.12427-1-pclouds@gmail.com","subject":"[PATCH v3 2/2] merge: add --quit","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-14T09:13:22Z","receivedAt":"2019-05-14T09:13:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows to cancel the current merge without resetting worktree/index,\nwhich is what --abort is for. Like other --quit(s), this is often used\nwhen you forgot that you're in the middle of a merge and already\nswitched away, doing different things. By the time you've realized, you\ncan't even continue the merge anymore.\n\nThis also makes all in-progress commands, am, merge, rebase, revert and\ncherry-pick, take all three --abort, --continue and --quit (bisect has a\ndifferent UI).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-merge.txt |  4 ++++\n builtin/merge.c             | 13 +++++++++++++\n t/t7600-merge.sh            | 14 ++++++++++++++\n 3 files changed, 31 insertions(+)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 4cc86469f3..b7d581fc76 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -99,6 +99,10 @@ commit or stash your changes before running 'git merge'.\n 'git merge --abort' is equivalent to 'git reset --merge' when\n `MERGE_HEAD` is present.\n \n+--quit::\n+\tForget about the current merge in progress. Leave the index\n+\tand the working tree as-is.\n+\n --continue::\n \tAfter a 'git merge' stops due to conflicts you can conclude the\n \tmerge by running 'git merge --continue' (see \"HOW TO RESOLVE\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e9663f027a..598d56edfe 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -73,6 +73,7 @@ static int option_renormalize;\n static int verbosity;\n static int allow_rerere_auto;\n static int abort_current_merge;\n+static int quit_current_merge;\n static int continue_current_merge;\n static int allow_unrelated_histories;\n static int show_progress = -1;\n@@ -267,6 +268,8 @@ static struct option builtin_merge_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"abort\", &abort_current_merge,\n \t\tN_(\"abort the current in-progress merge\")),\n+\tOPT_BOOL(0, \"quit\", &quit_current_merge,\n+\t\tN_(\"--abort but leave index and working tree alone\")),\n \tOPT_BOOL(0, \"continue\", &continue_current_merge,\n \t\tN_(\"continue the current in-progress merge\")),\n \tOPT_BOOL(0, \"allow-unrelated-histories\", &allow_unrelated_histories,\n@@ -1252,6 +1255,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tgoto done;\n \t}\n \n+\tif (quit_current_merge) {\n+\t\tif (orig_argc != 2)\n+\t\t\tusage_msg_opt(_(\"--quit expects no arguments\"),\n+\t\t\t\t      builtin_merge_usage,\n+\t\t\t\t      builtin_merge_options);\n+\n+\t\tremove_merge_branch_state(the_repository);\n+\t\tgoto done;\n+\t}\n+\n \tif (continue_current_merge) {\n \t\tint nargc = 1;\n \t\tconst char *nargv[] = {\"commit\", NULL};\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 106148254d..d453710ef6 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n \tverify_parents $c0 $c1\n '\n \n+test_expect_success 'merge --quit' '\n+\tgit reset --hard c2 &&\n+\ttest_must_fail git -c rerere.enabled=true merge master &&\n+\ttest_path_is_file .git/MERGE_HEAD &&\n+\ttest_path_is_file .git/MERGE_MODE &&\n+\ttest_path_is_file .git/MERGE_MSG &&\n+\ttest_path_is_file .git/MERGE_RR &&\n+\tgit merge --quit &&\n+\ttest_path_is_missing .git/MERGE_HEAD &&\n+\ttest_path_is_missing .git/MERGE_MODE &&\n+\ttest_path_is_missing .git/MERGE_MSG &&\n+\ttest_path_is_missing .git/MERGE_RR\n+'\n+\n test_done\n-- \n2.21.0.1141.gd54ac2cb17\n\n"},{"id":"375535","messageId":"nycvar.QRO.7.76.6.1905141540300.44@tvgsbejvaqbjf.bet","threadId":"51020","inReplyTo":"20190514091322.12427-3-pclouds@gmail.com","subject":"Re: [PATCH v3 2/2] merge: add --quit","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-14T13:44:26Z","receivedAt":"2019-05-14T13:44:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Tue, 14 May 2019, Nguyễn Thái Ngọc Duy wrote:\n\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index 106148254d..d453710ef6 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n>  \tverify_parents $c0 $c1\n>  '\n>\n> +test_expect_success 'merge --quit' '\n> +\tgit reset --hard c2 &&\n> +\ttest_must_fail git -c rerere.enabled=true merge master &&\n\nThis makes me really worried. It is the same `master` (i.e. *not* a tag)\nthat broke this test case in the previous round.\n\n> +\ttest_path_is_file .git/MERGE_HEAD &&\n> +\ttest_path_is_file .git/MERGE_MODE &&\n> +\ttest_path_is_file .git/MERGE_MSG &&\n> +\ttest_path_is_file .git/MERGE_RR &&\n\nIsn't this a clear implementation details of `git rerere` that you just\ntaught `git merge`'s regression test?\n\nThat's *prone* to become a test failure without a bug.\n\nIt would probably make a ton more sense to look at the output of `git\nrerere status` instead.\n\nCiao,\nJohannes\n\n> +\tgit merge --quit &&\n> +\ttest_path_is_missing .git/MERGE_HEAD &&\n> +\ttest_path_is_missing .git/MERGE_MODE &&\n> +\ttest_path_is_missing .git/MERGE_MSG &&\n> +\ttest_path_is_missing .git/MERGE_RR\n> +'\n> +\n>  test_done\n> --\n> 2.21.0.1141.gd54ac2cb17\n>\n>\n"},{"id":"375591","messageId":"xmqqpnok77lb.fsf@gitster-ct.c.googlers.com","threadId":"51020","inReplyTo":"20190514091322.12427-3-pclouds@gmail.com","subject":"Re: [PATCH v3 2/2] merge: add --quit","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-05-15T02:52:00Z","receivedAt":"2019-05-15T02:52:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index 106148254d..d453710ef6 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n>  \tverify_parents $c0 $c1\n>  '\n>  \n> +test_expect_success 'merge --quit' '\n> +\tgit reset --hard c2 &&\n> +\ttest_must_fail git -c rerere.enabled=true merge master &&\n> +\ttest_path_is_file .git/MERGE_HEAD &&\n> +\ttest_path_is_file .git/MERGE_MODE &&\n> +\ttest_path_is_file .git/MERGE_MSG &&\n> +\ttest_path_is_file .git/MERGE_RR &&\n> +\tgit merge --quit &&\n> +\ttest_path_is_missing .git/MERGE_HEAD &&\n> +\ttest_path_is_missing .git/MERGE_MODE &&\n> +\ttest_path_is_missing .git/MERGE_MSG &&\n> +\ttest_path_is_missing .git/MERGE_RR\n> +'\n\nI'd appreciate to see test_when_finished used in this test so that\nlater tests, possibly added by another topic in parallel, would\nstart in a more-or-less reasonable state.\n\n\n"},{"id":"375592","messageId":"xmqqlfz877ba.fsf@gitster-ct.c.googlers.com","threadId":"51020","inReplyTo":"nycvar.QRO.7.76.6.1905141540300.44@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 2/2] merge: add --quit","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-05-15T02:58:01Z","receivedAt":"2019-05-15T02:58:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> +test_expect_success 'merge --quit' '\n>> +\tgit reset --hard c2 &&\n>> +\ttest_must_fail git -c rerere.enabled=true merge master &&\n>\n> This makes me really worried. It is the same `master` (i.e. *not* a tag)\n> that broke this test case in the previous round.\n\nI'll let you two figure this out, but I tend to agree.\n\n>> +\ttest_path_is_file .git/MERGE_HEAD &&\n>> +\ttest_path_is_file .git/MERGE_MODE &&\n>> +\ttest_path_is_file .git/MERGE_MSG &&\n>> +\ttest_path_is_file .git/MERGE_RR &&\n>\n> Isn't this a clear implementation details of `git rerere` that you just\n> taught `git merge`'s regression test?\n> ...\n> It would probably make a ton more sense to look at the output of `git\n> rerere status` instead.\n\nWhile I understand your concern, it is not the business of this test\nto detect a bug in \"git rerere status\", either.  The safest thing to\ndo would be to test both ;-)\n\nt4151 that tests \"am --abort\" already looks at MERGE_RR for the same\n\"we want to make sure that the rerere state is cleared\" purpose, so\nI'd not be worried too much about this particular test.\n\nThanks.\n"},{"id":"375631","messageId":"nycvar.QRO.7.76.6.1905151658090.46@tvgsbejvaqbjf.bet","threadId":"51020","inReplyTo":"xmqqlfz877ba.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 2/2] merge: add --quit","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-15T15:00:17Z","receivedAt":"2019-05-15T15:00:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Wed, 15 May 2019, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> >> +\ttest_path_is_file .git/MERGE_HEAD &&\n> >> +\ttest_path_is_file .git/MERGE_MODE &&\n> >> +\ttest_path_is_file .git/MERGE_MSG &&\n> >> +\ttest_path_is_file .git/MERGE_RR &&\n> >\n> > Isn't this a clear implementation details of `git rerere` that you just\n> > taught `git merge`'s regression test?\n> > ...\n> > It would probably make a ton more sense to look at the output of `git\n> > rerere status` instead.\n>\n> While I understand your concern, it is not the business of this test\n> to detect a bug in \"git rerere status\", either.  The safest thing to\n> do would be to test both ;-)\n>\n> t4151 that tests \"am --abort\" already looks at MERGE_RR for the same\n> \"we want to make sure that the rerere state is cleared\" purpose, so\n> I'd not be worried too much about this particular test.\n\nI spend *way* too much time chasing regression test failures that turn out\nnot to show any bugs in the code they want to safeguard, but instead the\nbugs are found in the *assumptions* of the regression tests. So much time,\nin fact, that I have to disagree with you here. t4151 is just as wrong.\n\nCiao,\nDscho\n"},{"id":"375844","messageId":"20190518113043.18389-1-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190514091322.12427-1-pclouds@gmail.com","subject":"[PATCH v4 0/2] nd/merge-quit updates","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-18T11:30:41Z","receivedAt":"2019-05-18T11:30:53Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Another round because apparently the test case is not perfect.\n\nNguyễn Thái Ngọc Duy (2):\n  merge: remove drop_save() in favor of remove_merge_branch_state()\n  merge: add --quit\n\n Documentation/git-merge.txt |  4 ++++\n branch.c                    | 11 ++++++++---\n branch.h                    |  6 ++++++\n builtin/merge.c             | 30 ++++++++++++++++++------------\n t/t7600-merge.sh            | 26 ++++++++++++++++++++++++++\n 5 files changed, 62 insertions(+), 15 deletions(-)\n\nInterdiff dựa trên v3:\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex d453710ef6..625a24a980 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -823,17 +823,29 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n '\n \n test_expect_success 'merge --quit' '\n-\tgit reset --hard c2 &&\n-\ttest_must_fail git -c rerere.enabled=true merge master &&\n-\ttest_path_is_file .git/MERGE_HEAD &&\n-\ttest_path_is_file .git/MERGE_MODE &&\n-\ttest_path_is_file .git/MERGE_MSG &&\n-\ttest_path_is_file .git/MERGE_RR &&\n-\tgit merge --quit &&\n-\ttest_path_is_missing .git/MERGE_HEAD &&\n-\ttest_path_is_missing .git/MERGE_MODE &&\n-\ttest_path_is_missing .git/MERGE_MSG &&\n-\ttest_path_is_missing .git/MERGE_RR\n+\tgit init merge-quit &&\n+\t(\n+\t\tcd merge-quit &&\n+\t\ttest_commit base &&\n+\t\techo one >>base.t &&\n+\t\tgit commit -am one &&\n+\t\tgit branch one &&\n+\t\tgit checkout base &&\n+\t\techo two >>base.t &&\n+\t\tgit commit -am two &&\n+\t\ttest_must_fail git -c rerere.enabled=true merge one &&\n+\t\ttest_path_is_file .git/MERGE_HEAD &&\n+\t\ttest_path_is_file .git/MERGE_MODE &&\n+\t\ttest_path_is_file .git/MERGE_MSG &&\n+\t\tgit rerere status >rerere.before &&\n+\t\tgit merge --quit &&\n+\t\ttest_path_is_missing .git/MERGE_HEAD &&\n+\t\ttest_path_is_missing .git/MERGE_MODE &&\n+\t\ttest_path_is_missing .git/MERGE_MSG &&\n+\t\tgit rerere status >rerere.after &&\n+\t\ttest_must_be_empty rerere.after &&\n+\t\t! test_cmp rerere.after rerere.before\n+\t)\n '\n \n test_done\n-- \n2.22.0.rc0.322.g2b0371e29a\n\n"},{"id":"375845","messageId":"20190518113043.18389-2-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190518113043.18389-1-pclouds@gmail.com","subject":"[PATCH v4 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-18T11:30:42Z","receivedAt":"2019-05-18T11:30:58Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Both remove_branch_state() and drop_save() delete almost the same set of\nfiles about the current merge state. The only difference is MERGE_RR but\nit should also be cleaned up after a successful merge, which is what\ndrop_save() is for.\n\nMake a new function that deletes all merge-related state files and use\nit instead of drop_save(). This function will also be used in the next\npatch that introduces --quit.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n branch.c        | 11 ++++++++---\n branch.h        |  6 ++++++\n builtin/merge.c | 17 +++++------------\n 3 files changed, 19 insertions(+), 15 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 28b81a7e02..1db0601a11 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -337,15 +337,20 @@ void create_branch(struct repository *r,\n \tfree(real_ref);\n }\n \n-void remove_branch_state(struct repository *r)\n+void remove_merge_branch_state(struct repository *r)\n {\n-\tunlink(git_path_cherry_pick_head(r));\n-\tunlink(git_path_revert_head(r));\n \tunlink(git_path_merge_head(r));\n \tunlink(git_path_merge_rr(r));\n \tunlink(git_path_merge_msg(r));\n \tunlink(git_path_merge_mode(r));\n+}\n+\n+void remove_branch_state(struct repository *r)\n+{\n+\tunlink(git_path_cherry_pick_head(r));\n+\tunlink(git_path_revert_head(r));\n \tunlink(git_path_squash_msg(r));\n+\tremove_merge_branch_state(r);\n }\n \n void die_if_checked_out(const char *branch, int ignore_current_worktree)\ndiff --git a/branch.h b/branch.h\nindex 29c1afa4d0..c90ba9d7bf 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref);\n  */\n extern int validate_new_branchname(const char *name, struct strbuf *ref, int force);\n \n+/*\n+ * Remove information about the merge state on the current\n+ * branch. (E.g., MERGE_HEAD)\n+ */\n+void remove_merge_branch_state(struct repository *r);\n+\n /*\n  * Remove information about the state of working on the current\n  * branch. (E.g., MERGE_HEAD)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e47d77baee..e9663f027a 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -37,6 +37,7 @@\n #include \"packfile.h\"\n #include \"tag.h\"\n #include \"alias.h\"\n+#include \"branch.h\"\n #include \"commit-reach.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n@@ -279,14 +280,6 @@ static struct option builtin_merge_options[] = {\n \tOPT_END()\n };\n \n-/* Cleans up metadata that is uninteresting after a succeeded merge. */\n-static void drop_save(void)\n-{\n-\tunlink(git_path_merge_head(the_repository));\n-\tunlink(git_path_merge_msg(the_repository));\n-\tunlink(git_path_merge_mode(the_repository));\n-}\n-\n static int save_state(struct object_id *stash)\n {\n \tint len;\n@@ -380,7 +373,7 @@ static void finish_up_to_date(const char *msg)\n {\n \tif (verbosity >= 0)\n \t\tprintf(\"%s%s\\n\", squash ? _(\" (nothing to squash)\") : \"\", msg);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n }\n \n static void squash_message(struct commit *commit, struct commit_list *remoteheads)\n@@ -858,7 +851,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n \t\t\t&result_commit, NULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \tfinish(head, remoteheads, &result_commit, \"In-index merge\");\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -885,7 +878,7 @@ static int finish_automerge(struct commit *head,\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(head, remoteheads, &result_commit, buf.buf);\n \tstrbuf_release(&buf);\n-\tdrop_save();\n+\tremove_merge_branch_state(the_repository);\n \treturn 0;\n }\n \n@@ -1463,7 +1456,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t}\n \n \t\tfinish(head_commit, remoteheads, &commit->object.oid, msg.buf);\n-\t\tdrop_save();\n+\t\tremove_merge_branch_state(the_repository);\n \t\tgoto done;\n \t} else if (!remoteheads->next && common->next)\n \t\t;\n-- \n2.22.0.rc0.322.g2b0371e29a\n\n"},{"id":"375846","messageId":"20190518113043.18389-3-pclouds@gmail.com","threadId":"51020","inReplyTo":"20190518113043.18389-1-pclouds@gmail.com","subject":"[PATCH v4 2/2] merge: add --quit","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2019-05-18T11:30:43Z","receivedAt":"2019-05-18T11:31:03Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This allows to cancel the current merge without resetting worktree/index,\nwhich is what --abort is for. Like other --quit(s), this is often used\nwhen you forgot that you're in the middle of a merge and already\nswitched away, doing different things. By the time you've realized, you\ncan't even continue the merge anymore.\n\nThis also makes all in-progress commands, am, merge, rebase, revert and\ncherry-pick, take all three --abort, --continue and --quit (bisect has a\ndifferent UI).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/git-merge.txt |  4 ++++\n builtin/merge.c             | 13 +++++++++++++\n t/t7600-merge.sh            | 26 ++++++++++++++++++++++++++\n 3 files changed, 43 insertions(+)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 4cc86469f3..b7d581fc76 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -99,6 +99,10 @@ commit or stash your changes before running 'git merge'.\n 'git merge --abort' is equivalent to 'git reset --merge' when\n `MERGE_HEAD` is present.\n \n+--quit::\n+\tForget about the current merge in progress. Leave the index\n+\tand the working tree as-is.\n+\n --continue::\n \tAfter a 'git merge' stops due to conflicts you can conclude the\n \tmerge by running 'git merge --continue' (see \"HOW TO RESOLVE\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e9663f027a..598d56edfe 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -73,6 +73,7 @@ static int option_renormalize;\n static int verbosity;\n static int allow_rerere_auto;\n static int abort_current_merge;\n+static int quit_current_merge;\n static int continue_current_merge;\n static int allow_unrelated_histories;\n static int show_progress = -1;\n@@ -267,6 +268,8 @@ static struct option builtin_merge_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"abort\", &abort_current_merge,\n \t\tN_(\"abort the current in-progress merge\")),\n+\tOPT_BOOL(0, \"quit\", &quit_current_merge,\n+\t\tN_(\"--abort but leave index and working tree alone\")),\n \tOPT_BOOL(0, \"continue\", &continue_current_merge,\n \t\tN_(\"continue the current in-progress merge\")),\n \tOPT_BOOL(0, \"allow-unrelated-histories\", &allow_unrelated_histories,\n@@ -1252,6 +1255,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tgoto done;\n \t}\n \n+\tif (quit_current_merge) {\n+\t\tif (orig_argc != 2)\n+\t\t\tusage_msg_opt(_(\"--quit expects no arguments\"),\n+\t\t\t\t      builtin_merge_usage,\n+\t\t\t\t      builtin_merge_options);\n+\n+\t\tremove_merge_branch_state(the_repository);\n+\t\tgoto done;\n+\t}\n+\n \tif (continue_current_merge) {\n \t\tint nargc = 1;\n \t\tconst char *nargv[] = {\"commit\", NULL};\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 106148254d..625a24a980 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -822,4 +822,30 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n \tverify_parents $c0 $c1\n '\n \n+test_expect_success 'merge --quit' '\n+\tgit init merge-quit &&\n+\t(\n+\t\tcd merge-quit &&\n+\t\ttest_commit base &&\n+\t\techo one >>base.t &&\n+\t\tgit commit -am one &&\n+\t\tgit branch one &&\n+\t\tgit checkout base &&\n+\t\techo two >>base.t &&\n+\t\tgit commit -am two &&\n+\t\ttest_must_fail git -c rerere.enabled=true merge one &&\n+\t\ttest_path_is_file .git/MERGE_HEAD &&\n+\t\ttest_path_is_file .git/MERGE_MODE &&\n+\t\ttest_path_is_file .git/MERGE_MSG &&\n+\t\tgit rerere status >rerere.before &&\n+\t\tgit merge --quit &&\n+\t\ttest_path_is_missing .git/MERGE_HEAD &&\n+\t\ttest_path_is_missing .git/MERGE_MODE &&\n+\t\ttest_path_is_missing .git/MERGE_MSG &&\n+\t\tgit rerere status >rerere.after &&\n+\t\ttest_must_be_empty rerere.after &&\n+\t\t! test_cmp rerere.after rerere.before\n+\t)\n+'\n+\n test_done\n-- \n2.22.0.rc0.322.g2b0371e29a\n\n"},{"id":"375937","messageId":"nycvar.QRO.7.76.6.1905201857120.46@tvgsbejvaqbjf.bet","threadId":"51020","inReplyTo":"20190518113043.18389-1-pclouds@gmail.com","subject":"Re: [PATCH v4 0/2] nd/merge-quit updates","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-20T17:01:07Z","receivedAt":"2019-05-20T17:01:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Sat, 18 May 2019, Nguyễn Thái Ngọc Duy wrote:\n\n> Another round because apparently the test case is not perfect.\n\nTest cases are never perfect. But at least the good ones are \"actionable\",\ni.e. when they fail, you know there is a regression, it helps you figure\nout easily why it broke, and it helps you fix the regression.\n\nMany, many test cases are bad: they break for all kinds of reasons\n*except* for a regression. They break because an error message was\nchanged. They break because of timing issues. They break because somebody\ninserted another test case. They break because a missing prereq caused a\nprevious test case not to run, removing a side effect on which the\nbreaking test case relied.\n\nThose bad test cases are very frustrating for people who actually look at\nthem. I am one of those people.\n\nAnd if I did not know delightful test suites that are populated with\n\"good\" test cases, I would not point out when a \"bad\" one is contributed.\n\nSo: it *is* possible to have a good test suite. Let's make ours better\nthan it is right now.\n\nThanks,\nJohannes\n"},{"id":"375943","messageId":"nycvar.QRO.7.76.6.1905201904240.46@tvgsbejvaqbjf.bet","threadId":"51020","inReplyTo":"20190518113043.18389-3-pclouds@gmail.com","subject":"Re: [PATCH v4 2/2] merge: add --quit","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-20T18:05:48Z","receivedAt":"2019-05-20T18:06:12Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Duy,\n\nOn Sat, 18 May 2019, Nguyễn Thái Ngọc Duy wrote:\n\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index 106148254d..625a24a980 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -822,4 +822,30 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue'\n>  \tverify_parents $c0 $c1\n>  '\n>\n> +test_expect_success 'merge --quit' '\n> +\tgit init merge-quit &&\n> +\t(\n> +\t\tcd merge-quit &&\n> +\t\ttest_commit base &&\n> +\t\techo one >>base.t &&\n> +\t\tgit commit -am one &&\n> +\t\tgit branch one &&\n> +\t\tgit checkout base &&\n> +\t\techo two >>base.t &&\n> +\t\tgit commit -am two &&\n> +\t\ttest_must_fail git -c rerere.enabled=true merge one &&\n> +\t\ttest_path_is_file .git/MERGE_HEAD &&\n> +\t\ttest_path_is_file .git/MERGE_MODE &&\n> +\t\ttest_path_is_file .git/MERGE_MSG &&\n> +\t\tgit rerere status >rerere.before &&\n> +\t\tgit merge --quit &&\n> +\t\ttest_path_is_missing .git/MERGE_HEAD &&\n> +\t\ttest_path_is_missing .git/MERGE_MODE &&\n> +\t\ttest_path_is_missing .git/MERGE_MSG &&\n> +\t\tgit rerere status >rerere.after &&\n> +\t\ttest_must_be_empty rerere.after &&\n> +\t\t! test_cmp rerere.after rerere.before\n> +\t)\n> +'\n\nGood test cases do not *need* to be excessively long. Something like this\nshould be conciser, and more importantly, less inviting to typos or other\nbugs:\n\n\ttest_commit quit-test &&\n\ttest_commit quit-one quit-test.t one &&\n\tgit reset --hard HEAD^ &&\n\ttest_commit quit-two quit-test.t two &&\n\n\ttest_must_fail git -c rerere.enabled=true merge one &&\n\ttest_path_is_file .git/MERGE_HEAD &&\n\tgit rerere status >rerere &&\n\ttest -s rerere &&\n\n\tgit merge --quit &&\n\ttest_path_is_missing .git/MERGE_HEAD &&\n\tgit rerere status >rerere &&\n\ttest_must_be_empty rerere\n\nNote that this does not do an exhaustive test for all the .git/MERGE_*\nfiles: this test regression promises to verify that `git merge --quit`\nworks, it does not promise to verify that a failed `git merge` leaves all\nof those files! Using just `MERGE_HEAD` as a tell-tale for that is plenty\nsufficient.\n\nLikewise, this test case does not verify that the output of `git rerere\nstatus` is different before and after the `git merge --quit`. That is not\nthe point of the test, to make sure that they are different. The point is\nto make sure that it is empty afterwards, but not empty beforehand.\n\nTechnically, your version of the test case verifies the same (if a file's\ncontents differ from an empty file's, then necessarily it is not empty,\nbut it requires some gymnastics to come to that conclusion). There is no\nneed for convoluted thinking in regression test cases. In fact, the easier\nit is to understand the *intent* of a test case, the quicker the\ninvestigation of any future bug, and consequently the faster the bug fix.\n\nAlso, the less you execute, the quicker the test case runs. That might not\nsound like much, but we have over 20,000 test cases in our test suite.\nThat multiplication is really easy to compute in your head. If all of them\nwere written succinctly, I bet you would find it much less taxing on your\npatience to actually run the full test suite from time to time ;-)\n\nAnd this illustrates a very real cost of a slow test suite in addition to\nthe time: developers will run it less often, causing more regressions,\nwasting even more time in the long run.\n\nCiao,\nJohannes\n"}]}