{"thread":{"id":"61487","subject":"[PATCH] merge: avoid write merge state when unable to write index","startedAt":"2024-05-16T05:08:43Z","lastAt":"2024-06-17T09:17:32Z","messageCount":11,"participants":["Kyle Zhao via GitGitGadget","Junio C Hamano","goldofzky@gmail.com"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"494852","messageId":"pull.1731.git.1715836120584.gitgitgadget@gmail.com","threadId":"61487","inReplyTo":null,"subject":"[PATCH] merge: avoid write merge state when unable to write index","fromName":"Kyle Zhao via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-16T05:08:40Z","receivedAt":"2024-05-16T05:08:43Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"From: Kyle Zhao <kylezhao@tencent.com>\n\nCurrently, when index.lock exists, if a merge occurs, the merge state\nwill be written and the index will be unchanged.\n\nIf the user exec \"git commit\" instead of \"git merge --abort\" after this,\nthe commit will be successful and all modifications from the source\nbranch will be lost.\n\nSigned-off-by: Kyle Zhao <kylezhao@tencent.com>\n---\n    merge: avoid write merge state when unable to write index\n    \n    In some of our monorepos, code is sometimes lost after merging.\n    \n    After investigation, we discovered the problem.\n    \n    This happens if we perform \"git pull\" or \"git merge\" when another git\n    process is writing to the index, especially in a monorepo (because its\n    index will be larger).\n    \n    How to reproduce:\n    \n    git init demo\n    cd demo\n    touch 1.txt && git add . && git commit -m \"1\"\n    git checkout -b source-branch\n    touch 2.txt && git add . && git commit -m \"2\"\n    echo \"1\" >> 1.txt && git add . && git commit -m \"3\"\n    touch .git/index.lock\n    git merge source-branch\n    rm .git/index.lock\n    git commit -m \"4\"\n    \n    \n    Then the modifications from the source branch are lost.\n    \n    Regards, Kyle\n    \n    cc: Elijah Newren newren@gmail.com cc: Ævar Arnfjörð Bjarmason\n    avarab@gmail.com cc: Junio C Hamano gitster@pobox.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1731%2Fkeyu98%2Fkz%2Ffix-merge-when-index-lock-exists-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1731/keyu98/kz/fix-merge-when-index-lock-exists-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1731\n\n builtin/merge.c  |  6 ++++--\n t/t7600-merge.sh | 10 ++++++++++\n 2 files changed, 14 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 6a6d3798858..80e3438be25 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -698,8 +698,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n-\t\t\t\t\t NULL) < 0)\n-\t\treturn error(_(\"Unable to write index.\"));\n+\t\t\t\t\t NULL) < 0) {\n+\t\terror(_(\"Unable to write index.\"));\n+\t\treturn 2;\n+\t}\n \n \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n \t    !strcmp(strategy, \"ort\")) {\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099a..f03709ea4be 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n \tverify_parents $c1 $c2\n '\n \n+test_expect_success 'merge c1 with c2 when index.lock exists' '\n+\ttest_when_finished rm .git/index.lock &&\n+\tgit reset --hard c1 &&\n+\ttouch .git/index.lock &&\n+\ttest_must_fail git merge c2 &&\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+'\n+\n test_expect_success 'merge --squash c3 with c7' '\n \tgit reset --hard c3 &&\n \ttest_must_fail git merge --squash c7 &&\n\nbase-commit: 19fe900cfce8096b7645ec9611a0b981f6bbd154\n-- \ngitgitgadget\n"},{"id":"494853","messageId":"pull.1731.v2.git.1715836731784.gitgitgadget@gmail.com","threadId":"61487","inReplyTo":"pull.1731.git.1715836120584.gitgitgadget@gmail.com","subject":"[PATCH v2] merge: avoid write merge state when unable to write index","fromName":"Kyle Zhao via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-16T05:18:51Z","receivedAt":"2024-05-16T05:18:56Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"From: Kyle Zhao <kylezhao@tencent.com>\n\nCurrently, when index.lock exists, if a merge occurs, the merge state\nwill be written and the index will be unchanged.\n\nIf the user exec \"git commit\" instead of \"git merge --abort\" after this,\na merge commit will be generated and all modifications from the source\nbranch will be lost.\n\nSigned-off-by: Kyle Zhao <kylezhao@tencent.com>\n---\n    merge: avoid write merge state when unable to write index\n    \n    In some of our monorepos, code is sometimes lost after merging.\n    \n    After investigation, we discovered the problem.\n    \n    This happens if we perform \"git pull\" or \"git merge\" when another git\n    process is writing to the index, especially in a monorepo (because its\n    index will be larger).\n    \n    How to reproduce:\n    \n    git init demo\n    cd demo\n    touch 1.txt && git add . && git commit -m \"1\"\n    git checkout -b source-branch\n    touch 2.txt && git add . && git commit -m \"2\"\n    git checkout master\n    echo \"1\" >> 1.txt && git add . && git commit -m \"3\"\n    touch .git/index.lock\n    git merge source-branch\n    rm .git/index.lock\n    git commit -m \"4\"\n    \n    \n    Then the modifications from the source branch are lost.\n    \n    Regards, Kyle\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1731%2Fkeyu98%2Fkz%2Ffix-merge-when-index-lock-exists-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1731/keyu98/kz/fix-merge-when-index-lock-exists-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1731\n\nRange-diff vs v1:\n\n 1:  aab0be55989 ! 1:  86101e5b778 merge: avoid write merge state when unable to write index\n     @@ Commit message\n          will be written and the index will be unchanged.\n      \n          If the user exec \"git commit\" instead of \"git merge --abort\" after this,\n     -    the commit will be successful and all modifications from the source\n     +    a merge commit will be generated and all modifications from the source\n          branch will be lost.\n      \n          Signed-off-by: Kyle Zhao <kylezhao@tencent.com>\n\n\n builtin/merge.c  |  6 ++++--\n t/t7600-merge.sh | 10 ++++++++++\n 2 files changed, 14 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 6a6d3798858..80e3438be25 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -698,8 +698,10 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n-\t\t\t\t\t NULL) < 0)\n-\t\treturn error(_(\"Unable to write index.\"));\n+\t\t\t\t\t NULL) < 0) {\n+\t\terror(_(\"Unable to write index.\"));\n+\t\treturn 2;\n+\t}\n \n \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n \t    !strcmp(strategy, \"ort\")) {\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099a..f03709ea4be 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n \tverify_parents $c1 $c2\n '\n \n+test_expect_success 'merge c1 with c2 when index.lock exists' '\n+\ttest_when_finished rm .git/index.lock &&\n+\tgit reset --hard c1 &&\n+\ttouch .git/index.lock &&\n+\ttest_must_fail git merge c2 &&\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+'\n+\n test_expect_success 'merge --squash c3 with c7' '\n \tgit reset --hard c3 &&\n \ttest_must_fail git merge --squash c7 &&\n\nbase-commit: 19fe900cfce8096b7645ec9611a0b981f6bbd154\n-- \ngitgitgadget\n"},{"id":"494898","messageId":"xmqq7cftspap.fsf@gitster.g","threadId":"61487","inReplyTo":"pull.1731.v2.git.1715836731784.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] merge: avoid write merge state when unable to write index","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-16T16:18:54Z","receivedAt":"2024-05-16T16:18:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Kyle Zhao via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Kyle Zhao <kylezhao@tencent.com>\n>\n> Currently, when index.lock exists, if a merge occurs, the merge state\n> will be written and the index will be unchanged.\n\n\"Currently, \" is superfluous in this project, as by convention our\nproposed log message begins with an observation of the current\nstatus (and \"what's wrong in it\" follows) in the present tense.\n\nMy guess, from reading the tests, of the situation this patch\nattempts to handle is something like this?\n\n    When running a merge while the index is locked (presumably by\n    another process), the merge state is written in SUCH and SUCH\n    files, the index is not updated, and then the merge fails.  This\n    leaves the resulting state inconsistent.\n\n> If the user exec \"git commit\" instead of \"git merge --abort\" after this,\n> a merge commit will be generated and all modifications from the source\n> branch will be lost.\n\nI do not think this is accurate description of the \"an user action\ncan make it worse\".  In reality, if the user runs \"git commit\", a\nsafety kicks in and they get:\n\n    fatal: Unable to create '.../.git/index.lock': file exists.\n\nIn fact, your \"How to reproduce\" below the three-dash line removes\nthe stale index.lock file before running \"git commit\".\n\n    From this state, if the index.lock file gets removed and the\n    user runs \"git commit\", a merge commit is created, recording the\n    tree from the inconsistent state the merge left.\n\nmay be a better description of the breakage.\n\nBut stepping back a bit, I do not think this extra paragraph is\nneeded at all.  If there were a competing process holding the\nindex.lock, it were in the process of updating the index, possibly\neven to create a new commit.  If that process were indeed running\nthe \"git commit\" command, MERGE_HEAD and other state files we write\non our side will be taken into account by them and cause them to\nrecord a merge, even though they may have been trying to record\nsomething entirely different.  So regardless of what _this_ user,\nwhose merge failed due to a competing process that held the\nindex.lock file, does after the merge failure, the recorded history\ncan be hosed by the other process.\n\nIn any case, to prevent the other \"git commit\" process from using\n\"our\" MERGE_HEAD and other state files to record a wrong contents,\nthe right fix is to make sure everybody who takes the lock on the\nindex file does *not* create any other state files to be read by\nothers before it successfully takes the lock.  That will also\nprevent \"git commit\" we run after a failed merge (and removing the\nlockfile) from recording an incorrect merge.\n\nI do not offhand know if returning 2 (aka \"I am not equipped to\nhandle this merg e at all\") is a good way to do so, but if it is,\nthen the patch given here is absolutely the right thing to do.\n\n>...\n>     How to reproduce:\n>     ...\n>     git merge source-branch\n>     rm .git/index.lock\n>     git commit -m \"4\"\n\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index e5ff073099a..f03709ea4be 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n>  \tverify_parents $c1 $c2\n>  '\n>  \n> +test_expect_success 'merge c1 with c2 when index.lock exists' '\n> +\ttest_when_finished rm .git/index.lock &&\n> +\tgit reset --hard c1 &&\n> +\ttouch .git/index.lock &&\n\nDo not use \"touch\" when the timestamp is not the thing we care\nabout.  In this case, you want that file to _exist_, and the right\nway to do so is\n\n\t>.git/index.lock &&\n\nwhich already appears in t7400 (it uses a no-op \":\" command, but\nthat is not needed).\n\nOther than that, the patch looks good to me.\n\nThanks.\n"},{"id":"494920","messageId":"pull.1731.v3.git.1715917639587.gitgitgadget@gmail.com","threadId":"61487","inReplyTo":"pull.1731.v2.git.1715836731784.gitgitgadget@gmail.com","subject":"[PATCH v3] merge: avoid write merge state when unable to write index","fromName":"Kyle Zhao via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-17T03:47:19Z","receivedAt":"2024-05-17T03:47:22Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"From: Kyle Zhao <kylezhao@tencent.com>\n\nWhen running a merge while the index is locked (presumably by another\nprocess), the merge state is written, the index is not updated, and then\nthe merge fails. This can cause unexpected results.\n\ni.g. if another running process is \"git commit\", MERGE_HEAD and other state\nfiles we write on our side will be taken into account by them and cause them\nto record a merge, even though they may have been trying to record something\nentirely different.\n\nSigned-off-by: Kyle Zhao <kylezhao@tencent.com>\n---\n    merge: avoid write merge state when unable to write index\n    \n    In some of our monorepos, code is sometimes lost after merging.\n    \n    After investigation, we discovered the problem.\n    \n    This happens if we perform \"git pull\" or \"git merge\" when another git\n    process is writing to the index, especially in a monorepo (because its\n    index will be larger).\n    \n    How to reproduce:\n    \n    git init demo\n    cd demo\n    touch 1.txt && git add . && git commit -m \"1\"\n    git checkout -b source-branch\n    touch 2.txt && git add . && git commit -m \"2\"\n    git checkout master\n    echo \"1\" >> 1.txt && git add . && git commit -m \"3\"\n    # another git process runnning\n    touch .git/index.lock\n    git merge source-branch\n    # another git process finished\n    rm .git/index.lock\n    git commit -m \"4\"\n    \n    \n    Then the modifications from the source branch are lost.\n    \n    Regards, Kyle\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1731%2Fkeyu98%2Fkz%2Ffix-merge-when-index-lock-exists-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1731/keyu98/kz/fix-merge-when-index-lock-exists-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1731\n\nRange-diff vs v2:\n\n 1:  86101e5b778 ! 1:  138fe0a054d merge: avoid write merge state when unable to write index\n     @@ Metadata\n       ## Commit message ##\n          merge: avoid write merge state when unable to write index\n      \n     -    Currently, when index.lock exists, if a merge occurs, the merge state\n     -    will be written and the index will be unchanged.\n     +    When running a merge while the index is locked (presumably by another\n     +    process), the merge state is written, the index is not updated, and then\n     +    the merge fails. This can cause unexpected results.\n      \n     -    If the user exec \"git commit\" instead of \"git merge --abort\" after this,\n     -    a merge commit will be generated and all modifications from the source\n     -    branch will be lost.\n     +    i.g. if another running process is \"git commit\", MERGE_HEAD and other state\n     +    files we write on our side will be taken into account by them and cause them\n     +    to record a merge, even though they may have been trying to record something\n     +    entirely different.\n      \n          Signed-off-by: Kyle Zhao <kylezhao@tencent.com>\n      \n       ## builtin/merge.c ##\n      @@ builtin/merge.c: static int try_merge_strategy(const char *strategy, struct commit_list *common,\n     - \n       \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n       \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n     --\t\t\t\t\t NULL) < 0)\n     + \t\t\t\t\t NULL) < 0)\n      -\t\treturn error(_(\"Unable to write index.\"));\n     -+\t\t\t\t\t NULL) < 0) {\n     -+\t\terror(_(\"Unable to write index.\"));\n     -+\t\treturn 2;\n     -+\t}\n     ++\t\tdie(_(\"Unable to write index.\"));\n       \n       \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n       \t    !strcmp(strategy, \"ort\")) {\n     @@ t/t7600-merge.sh: test_expect_success 'merge c1 with c2' '\n      +test_expect_success 'merge c1 with c2 when index.lock exists' '\n      +\ttest_when_finished rm .git/index.lock &&\n      +\tgit reset --hard c1 &&\n     -+\ttouch .git/index.lock &&\n     ++\t: >.git/index.lock &&\n      +\ttest_must_fail git merge c2 &&\n      +\ttest_path_is_missing .git/MERGE_HEAD &&\n      +\ttest_path_is_missing .git/MERGE_MODE &&\n\n\n builtin/merge.c  |  2 +-\n t/t7600-merge.sh | 10 ++++++++++\n 2 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 6a6d3798858..12c1b048fe1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -699,7 +699,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n \t\t\t\t\t NULL) < 0)\n-\t\treturn error(_(\"Unable to write index.\"));\n+\t\tdie(_(\"Unable to write index.\"));\n \n \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n \t    !strcmp(strategy, \"ort\")) {\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099a..c1958095dcf 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n \tverify_parents $c1 $c2\n '\n \n+test_expect_success 'merge c1 with c2 when index.lock exists' '\n+\ttest_when_finished rm .git/index.lock &&\n+\tgit reset --hard c1 &&\n+\t: >.git/index.lock &&\n+\ttest_must_fail git merge c2 &&\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+'\n+\n test_expect_success 'merge --squash c3 with c7' '\n \tgit reset --hard c3 &&\n \ttest_must_fail git merge --squash c7 &&\n\nbase-commit: 19fe900cfce8096b7645ec9611a0b981f6bbd154\n-- \ngitgitgadget\n"},{"id":"494922","messageId":"pull.1731.v4.git.1715920862420.gitgitgadget@gmail.com","threadId":"61487","inReplyTo":"pull.1731.v3.git.1715917639587.gitgitgadget@gmail.com","subject":"[PATCH v4] merge: avoid write merge state when unable to write index","fromName":"Kyle Zhao via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-05-17T04:41:02Z","receivedAt":"2024-05-17T04:41:05Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"From: Kyle Zhao <kylezhao@tencent.com>\n\nWhen running a merge while the index is locked (presumably by another\nprocess), the merge state is written, the index is not updated, and then\nthe merge fails. This might cause unexpected results.\n\ni.g. if another running process is \"git commit\", MERGE_HEAD and other state\nfiles we write on our side will be taken into account by them and cause them\nto record a merge, even though they may have been trying to record something\nentirely different.\n\nSigned-off-by: Kyle Zhao <kylezhao@tencent.com>\n---\n    merge: avoid write merge state when unable to write index\n    \n    In some of our monorepos, code is sometimes lost after merging.\n    \n    After investigation, we discovered the problem.\n    \n    This happens if we perform \"git pull\" or \"git merge\" when another git\n    process is writing to the index, especially in a monorepo (because its\n    index will be larger).\n    \n    How to reproduce:\n    \n    git init demo\n    cd demo\n    touch 1.txt && git add . && git commit -m \"1\"\n    git checkout -b source-branch\n    touch 2.txt && git add . && git commit -m \"2\"\n    git checkout master\n    echo \"1\" >> 1.txt && git add . && git commit -m \"3\"\n    # another git process runnning\n    touch .git/index.lock\n    git merge source-branch\n    # another git process finished\n    rm .git/index.lock\n    git commit -m \"4\"\n    \n    \n    Then the modifications from the source branch are lost.\n    \n    Regards, Kyle\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1731%2Fkeyu98%2Fkz%2Ffix-merge-when-index-lock-exists-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1731/keyu98/kz/fix-merge-when-index-lock-exists-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1731\n\nRange-diff vs v3:\n\n 1:  138fe0a054d ! 1:  7055dfb82c7 merge: avoid write merge state when unable to write index\n     @@ Commit message\n      \n          When running a merge while the index is locked (presumably by another\n          process), the merge state is written, the index is not updated, and then\n     -    the merge fails. This can cause unexpected results.\n     +    the merge fails. This might cause unexpected results.\n      \n          i.g. if another running process is \"git commit\", MERGE_HEAD and other state\n          files we write on our side will be taken into account by them and cause them\n     @@ t/t7600-merge.sh: test_expect_success 'merge c1 with c2' '\n      +test_expect_success 'merge c1 with c2 when index.lock exists' '\n      +\ttest_when_finished rm .git/index.lock &&\n      +\tgit reset --hard c1 &&\n     -+\t: >.git/index.lock &&\n     ++\t>.git/index.lock &&\n      +\ttest_must_fail git merge c2 &&\n      +\ttest_path_is_missing .git/MERGE_HEAD &&\n      +\ttest_path_is_missing .git/MERGE_MODE &&\n\n\n builtin/merge.c  |  2 +-\n t/t7600-merge.sh | 10 ++++++++++\n 2 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 6a6d3798858..12c1b048fe1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -699,7 +699,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n \t\t\t\t\t NULL) < 0)\n-\t\treturn error(_(\"Unable to write index.\"));\n+\t\tdie(_(\"Unable to write index.\"));\n \n \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n \t    !strcmp(strategy, \"ort\")) {\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099a..ef54cff4faa 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n \tverify_parents $c1 $c2\n '\n \n+test_expect_success 'merge c1 with c2 when index.lock exists' '\n+\ttest_when_finished rm .git/index.lock &&\n+\tgit reset --hard c1 &&\n+\t>.git/index.lock &&\n+\ttest_must_fail git merge c2 &&\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+'\n+\n test_expect_success 'merge --squash c3 with c7' '\n \tgit reset --hard c3 &&\n \ttest_must_fail git merge --squash c7 &&\n\nbase-commit: 19fe900cfce8096b7645ec9611a0b981f6bbd154\n-- \ngitgitgadget\n"},{"id":"496974","messageId":"202406121040581668410@gmail.com","threadId":"61487","inReplyTo":"pull.1731.v4.git.1715920862420.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] merge: avoid write merge state when unable to write index","fromName":"goldofzky@gmail.com","fromEmail":"goldofzky@gmail.com","sentAt":"2024-06-12T02:40:59Z","receivedAt":"2024-06-12T02:41:01Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"ping - please let me know if I need to do anything different. Thanks!"},{"id":"496985","messageId":"pull.1731.v5.git.1718173639942.gitgitgadget@gmail.com","threadId":"61487","inReplyTo":"pull.1731.v4.git.1715920862420.gitgitgadget@gmail.com","subject":"[PATCH v5] merge: avoid write merge state when unable to write index","fromName":"Kyle Zhao via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-06-12T06:27:19Z","receivedAt":"2024-06-12T06:27:23Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"From: Kyle Zhao <kylezhao@tencent.com>\n\nWhen running a merge while the index is locked (presumably by another\nprocess), the merge state is written, the index is not updated, and then\nthe merge fails. This might cause unexpected results.\n\nE.g., if another running process is \"git commit\", MERGE_HEAD and other\nstate files we write on our side will be taken into account by them and\ncause them to record a merge, even though they may have been trying to\nrecord something entirely different.\n\nSigned-off-by: Kyle Zhao <kylezhao@tencent.com>\n---\n    merge: avoid write merge state when unable to write index\n    \n    In some of our monorepos, code is sometimes lost after merging.\n    \n    After investigation, we discovered the problem.\n    \n    This happens if we perform \"git pull\" or \"git merge\" when another git\n    process is writing to the index, especially in a monorepo (because its\n    index will be larger).\n    \n    How to reproduce:\n    \n    git init demo\n    cd demo\n    touch 1.txt && git add . && git commit -m \"1\"\n    git checkout -b source-branch\n    touch 2.txt && git add . && git commit -m \"2\"\n    git checkout master\n    echo \"1\" >> 1.txt && git add . && git commit -m \"3\"\n    # another git process runnning\n    touch .git/index.lock\n    git merge source-branch\n    # another git process finished\n    rm .git/index.lock\n    git commit -m \"4\"\n    \n    \n    Then the modifications from the source branch are lost.\n    \n    Regards, Kyle\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1731%2Fkeyu98%2Fkz%2Ffix-merge-when-index-lock-exists-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1731/keyu98/kz/fix-merge-when-index-lock-exists-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1731\n\nRange-diff vs v4:\n\n 1:  7055dfb82c7 ! 1:  a5156088514 merge: avoid write merge state when unable to write index\n     @@ Commit message\n          process), the merge state is written, the index is not updated, and then\n          the merge fails. This might cause unexpected results.\n      \n     -    i.g. if another running process is \"git commit\", MERGE_HEAD and other state\n     -    files we write on our side will be taken into account by them and cause them\n     -    to record a merge, even though they may have been trying to record something\n     -    entirely different.\n     +    E.g., if another running process is \"git commit\", MERGE_HEAD and other\n     +    state files we write on our side will be taken into account by them and\n     +    cause them to record a merge, even though they may have been trying to\n     +    record something entirely different.\n      \n          Signed-off-by: Kyle Zhao <kylezhao@tencent.com>\n      \n\n\n builtin/merge.c  |  2 +-\n t/t7600-merge.sh | 10 ++++++++++\n 2 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 6a6d3798858..12c1b048fe1 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -699,7 +699,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n \t\t\t\t\t NULL) < 0)\n-\t\treturn error(_(\"Unable to write index.\"));\n+\t\tdie(_(\"Unable to write index.\"));\n \n \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n \t    !strcmp(strategy, \"ort\")) {\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099a..ef54cff4faa 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n \tverify_parents $c1 $c2\n '\n \n+test_expect_success 'merge c1 with c2 when index.lock exists' '\n+\ttest_when_finished rm .git/index.lock &&\n+\tgit reset --hard c1 &&\n+\t>.git/index.lock &&\n+\ttest_must_fail git merge c2 &&\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+'\n+\n test_expect_success 'merge --squash c3 with c7' '\n \tgit reset --hard c3 &&\n \ttest_must_fail git merge --squash c7 &&\n\nbase-commit: 19fe900cfce8096b7645ec9611a0b981f6bbd154\n-- \ngitgitgadget\n"},{"id":"497097","messageId":"xmqqh6dwlpnb.fsf@gitster.g","threadId":"61487","inReplyTo":"pull.1731.v5.git.1718173639942.gitgitgadget@gmail.com","subject":"Re: [PATCH v5] merge: avoid write merge state when unable to write index","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-06-13T17:27:04Z","receivedAt":"2024-06-13T17:27:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Kyle Zhao via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Kyle Zhao <kylezhao@tencent.com>\n>\n> When running a merge while the index is locked (presumably by another\n> process), the merge state is written, the index is not updated, and then\n> the merge fails. This might cause unexpected results.\n\nFailing the merge is good thing.\n\n> E.g., if another running process is \"git commit\", MERGE_HEAD and other\n> state files we write on our side will be taken into account by them and\n> cause them to record a merge, even though they may have been trying to\n> record something entirely different.\n\nIf I recall the previous discussion correctly, I think the primary\nthing this change achieves is to get us closer to a state where\ncompeting commands (a \"git commit\" run while we are doing something\nelse like \"git merge\") take the index.lock as the first thing (so\nothers are blocked), before making auxiliary files like MERGE_HEAD\nthat would affect the behaviour of whoever has index.lock (and thus\nmaking a new commit).  And that is what we need to stress in the\nproposed log message, I would think.\n\nBut this is probably only half-a-solution.\n\n> Signed-off-by: Kyle Zhao <kylezhao@tencent.com>\n> ---\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index 6a6d3798858..12c1b048fe1 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -699,7 +699,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n>  \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n>  \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n>  \t\t\t\t\t NULL) < 0)\n> -\t\treturn error(_(\"Unable to write index.\"));\n> +\t\tdie(_(\"Unable to write index.\"));\n>  \n>  \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n>  \t    !strcmp(strategy, \"ort\")) {\n\nIf we fail to write the index here, even if we have other strategies\nto try after the current one fails, it probably is a good idea to die\nand stop the other ones from being tried, not because their attempt\nto write the index might fail the same way, but because it is likely\nthat we are really in a weird situation and the user would want to\ninspect the situation before this process makes too much damage to\nthe working tree and the index.\n\nBut this is probably only half-a-solution.  Because we release the\nindex.lock when the refresh-and-write call returns, and the\nindex.lock is free for the other process to grab, do whatever they\nwant to do to the index and the working tree (including making a new\ncommit out of it and update the HEAD), before or after we write out\nthe merge state files. So in that sense, this patch is *not* solving\nthe \"E.g., if another running process is ...\"  problem at all.\n\nSo ...\n\n> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> index e5ff073099a..ef54cff4faa 100755\n> --- a/t/t7600-merge.sh\n> +++ b/t/t7600-merge.sh\n> @@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n>  \tverify_parents $c1 $c2\n>  '\n>  \n> +test_expect_success 'merge c1 with c2 when index.lock exists' '\n> +\ttest_when_finished rm .git/index.lock &&\n> +\tgit reset --hard c1 &&\n> +\t>.git/index.lock &&\n> +\ttest_must_fail git merge c2 &&\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\n... I do not quite see the point of this exercise.  It is good to\nmake sure that \"git merge c2\" fails while it is clear that somebody\nelse is mucking with the same repository touching the index.  But it\ndoes not help the other process all that much if we stop only when\nthey happen to be holding lock at the point we try to refresh the\nindex.  It is making the race window smaller by a tiny bit.\n\nSo, I am not sure if this is worth doing.\n\n"},{"id":"497231","messageId":"202406171101133354904@gmail.com","threadId":"61487","inReplyTo":"xmqqh6dwlpnb.fsf@gitster.g","subject":"Re: [Internet]Re: [PATCH v5] merge: avoid write merge state when unable to write index","fromName":"goldofzky@gmail.com","fromEmail":"goldofzky@gmail.com","sentAt":"2024-06-17T03:02:02Z","receivedAt":"2024-06-17T03:02:04Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"On Fri, Jun 14, 2024 at 1:27 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Kyle Zhao via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: Kyle Zhao <kylezhao@tencent.com>\n> >\n> > When running a merge while the index is locked (presumably by another\n> > process), the merge state is written, the index is not updated, and then\n> > the merge fails. This might cause unexpected results.\n>\n> Failing the merge is good thing.\n>\n> > E.g., if another running process is \"git commit\", MERGE_HEAD and other\n> > state files we write on our side will be taken into account by them and\n> > cause them to record a merge, even though they may have been trying to\n> > record something entirely different.\n>\n> If I recall the previous discussion correctly, I think the primary\n> thing this change achieves is to get us closer to a state where\n> competing commands (a \"git commit\" run while we are doing something\n> else like \"git merge\") take the index.lock as the first thing (so\n> others are blocked), before making auxiliary files like MERGE_HEAD\n> that would affect the behaviour of whoever has index.lock (and thus\n> making a new commit).  And that is what we need to stress in the\n> proposed log message, I would think.\n\nI agree.\n\n>\n> But this is probably only half-a-solution.\n\n\n>\n> > Signed-off-by: Kyle Zhao <kylezhao@tencent.com>\n> > ---\n> > diff --git a/builtin/merge.c b/builtin/merge.c\n> > index 6a6d3798858..12c1b048fe1 100644\n> > --- a/builtin/merge.c\n> > +++ b/builtin/merge.c\n> > @@ -699,7 +699,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n> >       if (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n> >                                        SKIP_IF_UNCHANGED, 0, NULL, NULL,\n> >                                        NULL) < 0)\n> > -             return error(_(\"Unable to write index.\"));\n> > +             die(_(\"Unable to write index.\"));\n> >\n> >       if (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n> >           !strcmp(strategy, \"ort\")) {\n>\n> If we fail to write the index here, even if we have other strategies\n> to try after the current one fails, it probably is a good idea to die\n> and stop the other ones from being tried, not because their attempt\n> to write the index might fail the same way, but because it is likely\n> that we are really in a weird situation and the user would want to\n> inspect the situation before this process makes too much damage to\n> the working tree and the index.\n>\n> But this is probably only half-a-solution.  Because we release the\n> index.lock when the refresh-and-write call returns, and the\n> index.lock is free for the other process to grab, do whatever they\n> want to do to the index and the working tree (including making a new\n> commit out of it and update the HEAD), before or after we write out\n> the merge state files. So in that sense, this patch is *not* solving\n> the \"E.g., if another running process is ...\"  problem at all.\n>\n> So ...\n>\n\nOops!\nThank you for the reminder, I indeed did not consider this point. \nEven if the index is written successfully, another running \"git commit\" may \nrecord a merge (if it generates the commit before merge state is removed).\n\n> > diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\n> > index e5ff073099a..ef54cff4faa 100755\n> > --- a/t/t7600-merge.sh\n> > +++ b/t/t7600-merge.sh\n> > @@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n> >       verify_parents $c1 $c2\n> >  '\n> >\n> > +test_expect_success 'merge c1 with c2 when index.lock exists' '\n> > +     test_when_finished rm .git/index.lock &&\n> > +     git reset --hard c1 &&\n> > +     >.git/index.lock &&\n> > +     test_must_fail git merge c2 &&\n> > +     test_path_is_missing .git/MERGE_HEAD &&\n> > +     test_path_is_missing .git/MERGE_MODE &&\n> > +     test_path_is_missing .git/MERGE_MSG\n>\n> ... I do not quite see the point of this exercise.  It is good to\n> make sure that \"git merge c2\" fails while it is clear that somebody\n> else is mucking with the same repository touching the index.  But it\n> does not help the other process all that much if we stop only when\n> they happen to be holding lock at the point we try to refresh the\n> index.  It is making the race window smaller by a tiny bit.\n\nThis test is only used to verify whether the merge state is generated after a merge fails \ndue to the index writing. \nTo my knowledge, for a merge, writing the merge state and the index is not an atomic operation. \nSimply modifying the logic of merge is useless and we need to think about other solutions.\n\n>\n> So, I am not sure if this is worth doing.\n>\n\n[1] As explained in the commit message of v1, we are particularly concerned about the\nissue of source code loss. Once this problem occurs, the consequences will be immeasurable \nand detecting these abnormal merge points will be very difficult.\n\n[2] From a usability perspective, the merge state should not be written when the index is being\n written (merge conflicts are not considered failures). To avoid losing changes in the source branch, \nusers can only execute 'git merge --abort' and try 'git merge' again. However, if the merge state is\nnot written in the first place, the user only needs to retry 'git merge'.\n\nIn other words, writing the merge state after the index write fails is meaningless and could \npotentially cause Git to lose changes."},{"id":"497232","messageId":"pull.1731.v6.git.1718593717745.gitgitgadget@gmail.com","threadId":"61487","inReplyTo":"pull.1731.v5.git.1718173639942.gitgitgadget@gmail.com","subject":"[PATCH v6] merge: avoid write merge state when unable to write index","fromName":"Kyle Zhao via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-06-17T03:08:37Z","receivedAt":"2024-06-17T03:08:41Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"From: Kyle Zhao <kylezhao@tencent.com>\n\nWriting the merge state after the index write fails is meaningless and\ncould potentially cause Git to lose changes.\n\nSigned-off-by: Kyle Zhao <kylezhao@tencent.com>\n---\n    merge: avoid write merge state when unable to write index\n    \n    In some of our monorepos, code is sometimes lost after merging.\n    \n    After investigation, we discovered the problem.\n    \n    This happens if we perform \"git pull\" or \"git merge\" when another git\n    process is writing to the index, especially in a monorepo (because its\n    index will be larger).\n    \n    How to reproduce:\n    \n    git init demo\n    cd demo\n    touch 1.txt && git add . && git commit -m \"1\"\n    git checkout -b source-branch\n    touch 2.txt && git add . && git commit -m \"2\"\n    git checkout master\n    echo \"1\" >> 1.txt && git add . && git commit -m \"3\"\n    # another git process runnning\n    touch .git/index.lock\n    git merge source-branch\n    # another git process finished\n    rm .git/index.lock\n    git commit -m \"4\"\n    \n    \n    Then the modifications from the source branch are lost.\n    \n    Regards, Kyle\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1731%2Fkeyu98%2Fkz%2Ffix-merge-when-index-lock-exists-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1731/keyu98/kz/fix-merge-when-index-lock-exists-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/1731\n\nRange-diff vs v5:\n\n 1:  a5156088514 ! 1:  24150cd814a merge: avoid write merge state when unable to write index\n     @@ Metadata\n       ## Commit message ##\n          merge: avoid write merge state when unable to write index\n      \n     -    When running a merge while the index is locked (presumably by another\n     -    process), the merge state is written, the index is not updated, and then\n     -    the merge fails. This might cause unexpected results.\n     -\n     -    E.g., if another running process is \"git commit\", MERGE_HEAD and other\n     -    state files we write on our side will be taken into account by them and\n     -    cause them to record a merge, even though they may have been trying to\n     -    record something entirely different.\n     +    Writing the merge state after the index write fails is meaningless and\n     +    could potentially cause Git to lose changes.\n      \n          Signed-off-by: Kyle Zhao <kylezhao@tencent.com>\n      \n\n\n builtin/merge.c  |  2 +-\n t/t7600-merge.sh | 10 ++++++++++\n 2 files changed, 11 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e2..03c405fa5df 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -701,7 +701,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n \t\t\t\t\t NULL) < 0)\n-\t\treturn error(_(\"Unable to write index.\"));\n+\t\tdie(_(\"Unable to write index.\"));\n \n \tif (!strcmp(strategy, \"recursive\") || !strcmp(strategy, \"subtree\") ||\n \t    !strcmp(strategy, \"ort\")) {\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099a..ef54cff4faa 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -236,6 +236,16 @@ test_expect_success 'merge c1 with c2' '\n \tverify_parents $c1 $c2\n '\n \n+test_expect_success 'merge c1 with c2 when index.lock exists' '\n+\ttest_when_finished rm .git/index.lock &&\n+\tgit reset --hard c1 &&\n+\t>.git/index.lock &&\n+\ttest_must_fail git merge c2 &&\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+'\n+\n test_expect_success 'merge --squash c3 with c7' '\n \tgit reset --hard c3 &&\n \ttest_must_fail git merge --squash c7 &&\n\nbase-commit: d63586cb314731c851f28e14fc8012988467e2da\n-- \ngitgitgadget\n"},{"id":"497237","messageId":"202406171717292856283@gmail.com","threadId":"61487","inReplyTo":"202406171101133354904@gmail.com","subject":"Re: Re: [Internet]Re: [PATCH v5] merge: avoid write merge state when unable to write index","fromName":"goldofzky@gmail.com","fromEmail":"goldofzky@gmail.com","sentAt":"2024-06-17T09:17:29Z","receivedAt":"2024-06-17T09:17:32Z","isPatch":true,"sender":{"key":"goldofzky@gmail.com","avatar":"https://gravatar.com/avatar/aa4fa27f0c1bed9c986ed596415799518e03a6e71a0931308a4de87e166e183a?d=mp&s=160"},"body":"> [2] From a usability perspective, the merge state should not be written when the index is being\n> written (merge conflicts are not considered failures). To avoid losing changes in the source branch,\n\n\"the merge state should not be written when the index is being written\" should be:\nthe merge state should not be written if the index write fails.\n\n\n\n> users can only execute 'git merge --abort' and try 'git merge' again. However, if the merge state is\n> not written in the first place, the user only needs to retry 'git merge'.\n\n\n>In other words, writing the merge state after the index write fails is meaningless and could\n>potentially cause Git to lose changes.\n\n\n"}]}