{"thread":{"id":"34373","subject":"[PATCH 0/3] merge -Xindex-only","startedAt":"2013-07-07T18:02:37Z","lastAt":"2013-10-26T14:43:18Z","messageCount":15,"participants":["Thomas Rast","Junio C Hamano","Michael Haggerty"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"222758","messageId":"cover.1373219466.git.trast@inf.ethz.ch","threadId":"34373","inReplyTo":null,"subject":"[PATCH 0/3] merge -Xindex-only","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-07-07T18:02:37Z","receivedAt":"2013-07-07T18:02:37Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"[Michael, sorry for the double mail -- I typoed the list address on\nthe first round.]\n\nI recently looked into making merge-recursive more useful as a modular\npiece in various tasks, e.g. Michael's git-imerge and the experiments\nI made in showing evil merges.\n\nThis miniseries is the extremely low-hanging fruit.  If it makes a\ngood first step for git-imerge, perhaps it can go in like this.  It's\nnot a big speedup (about 2.2s vs 2.4s in a sample conflicting merge in\ngit.git), but it does feel much cleaner to avoid touching the worktree\nunless actually necessary.\n\nOtherwise it's probably not worth it just yet; for what I want to do\nwith it, we need some more reshuffling of things.\n\n\n\nThomas Rast (3):\n  merge-recursive: remove dead conditional in update_stages()\n  merge-recursive: untangle double meaning of o->call_depth\n  merge-recursive: -Xindex-only to leave worktree unchanged\n\n Documentation/merge-strategies.txt |  4 ++++\n merge-recursive.c                  | 46 +++++++++++++++++++++-----------------\n merge-recursive.h                  |  1 +\n t/t3030-merge-recursive.sh         | 13 +++++++++++\n 4 files changed, 43 insertions(+), 21 deletions(-)\n\n-- \n1.8.3.2.908.gbd0dbd0\n"},{"id":"222759","messageId":"ed085382b2966c91b59234537564e881ea9e6e4d.1373219466.git.trast@inf.ethz.ch","threadId":"34373","inReplyTo":"cover.1373219466.git.trast@inf.ethz.ch","subject":"[PATCH 1/3] merge-recursive: remove dead conditional in update_stages()","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-07-07T18:02:38Z","receivedAt":"2013-07-07T18:02:38Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"650467c (merge-recursive: Consolidate different update_stages\nfunctions, 2011-08-11) changed the former argument 'clear' to always\nbe true.  Remove the useless conditional.\n\nSigned-off-by: Thomas Rast <trast@inf.ethz.ch>\n---\n merge-recursive.c | 6 ++----\n 1 file changed, 2 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ea9dbd3..0b9cafb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -544,11 +544,9 @@ static int update_stages(const char *path, const struct diff_filespec *o,\n \t * would_lose_untracked).  Instead, reverse the order of the calls\n \t * (executing update_file first and then update_stages).\n \t */\n-\tint clear = 1;\n \tint options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;\n-\tif (clear)\n-\t\tif (remove_file_from_cache(path))\n-\t\t\treturn -1;\n+\tif (remove_file_from_cache(path))\n+\t\treturn -1;\n \tif (o)\n \t\tif (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))\n \t\t\treturn -1;\n-- \n1.8.3.2.908.gbd0dbd0\n"},{"id":"222760","messageId":"7e823a9faedf1980912628c3a3f083e26fb0c650.1373219466.git.trast@inf.ethz.ch","threadId":"34373","inReplyTo":"cover.1373219466.git.trast@inf.ethz.ch","subject":"[PATCH 2/3] merge-recursive: untangle double meaning of o->call_depth","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-07-07T18:02:39Z","receivedAt":"2013-07-07T18:02:39Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"o->call_depth has a double function: a nonzero call_depth means we\nwant to construct virtual merge bases, but it also means we want to\navoid touching the worktree.  Introduce a new flag o->no_worktree for\nthe latter.\n\nSigned-off-by: Thomas Rast <trast@inf.ethz.ch>\n---\n merge-recursive.c | 38 +++++++++++++++++++++-----------------\n merge-recursive.h |  1 +\n 2 files changed, 22 insertions(+), 17 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0b9cafb..b93b762 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -407,10 +407,10 @@ static void record_df_conflict_files(struct merge_options *o,\n \tint i;\n \n \t/*\n-\t * If we're merging merge-bases, we don't want to bother with\n-\t * any working directory changes.\n+\t * If we're working in-core only (e.g., merging merge-bases),\n+\t * we don't want to bother with any working directory changes.\n \t */\n-\tif (o->call_depth)\n+\tif (o->no_worktree)\n \t\treturn;\n \n \t/* Ensure D/F conflicts are adjacent in the entries list. */\n@@ -723,7 +723,7 @@ static void update_file_flags(struct merge_options *o,\n \t\t\t      int update_cache,\n \t\t\t      int update_wd)\n {\n-\tif (o->call_depth)\n+\tif (o->no_worktree)\n \t\tupdate_wd = 0;\n \n \tif (update_wd) {\n@@ -930,7 +930,7 @@ static struct merge_file_info merge_file_1(struct merge_options *o,\n \t\t\tresult.clean = merge_submodule(result.sha,\n \t\t\t\t\t\t       one->path, one->sha1,\n \t\t\t\t\t\t       a->sha1, b->sha1,\n-\t\t\t\t\t\t       !o->call_depth);\n+\t\t\t\t\t\t       !o->no_worktree);\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\thashcpy(result.sha, a->sha1);\n \n@@ -1002,7 +1002,7 @@ static void handle_change_delete(struct merge_options *o,\n \t\t\t\t const char *change, const char *change_past)\n {\n \tchar *renamed = NULL;\n-\tif (dir_in_way(path, !o->call_depth)) {\n+\tif (dir_in_way(path, !o->no_worktree)) {\n \t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n \t}\n \n@@ -1127,10 +1127,10 @@ static void handle_file(struct merge_options *o,\n \t\tchar *add_name = unique_path(o, rename->path, other_branch);\n \t\tupdate_file(o, 0, add->sha1, add->mode, add_name);\n \n-\t\tremove_file(o, 0, rename->path, 0);\n+\t\tremove_file(o, 0, rename->path, o->no_worktree);\n \t\tdst_name = unique_path(o, rename->path, cur_branch);\n \t} else {\n-\t\tif (dir_in_way(rename->path, !o->call_depth)) {\n+\t\tif (dir_in_way(rename->path, !o->no_worktree)) {\n \t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n \t\t\toutput(o, 1, _(\"%s is a directory in %s adding as %s instead\"),\n \t\t\t       rename->path, other_branch, dst_name);\n@@ -1237,7 +1237,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \t\t * merge base just undo the renames; they can be detected\n \t\t * again later for the non-recursive merge.\n \t\t */\n-\t\tremove_file(o, 0, path, 0);\n+\t\tremove_file(o, 0, path, o->no_worktree);\n \t\tupdate_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);\n \t\tupdate_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);\n \t} else {\n@@ -1245,7 +1245,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n \t\toutput(o, 1, _(\"Renaming %s to %s and %s to %s instead\"),\n \t\t       a->path, new_path1, b->path, new_path2);\n-\t\tremove_file(o, 0, path, 0);\n+\t\tremove_file(o, 0, path, o->no_worktree);\n \t\tupdate_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);\n \t\tupdate_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);\n \t\tfree(new_path2);\n@@ -1404,7 +1404,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t * add-source case).\n \t\t\t */\n \t\t\tremove_file(o, 1, ren1_src,\n-\t\t\t\t    renamed_stage == 2 || !was_tracked(ren1_src));\n+\t\t\t\t    o->no_worktree || renamed_stage == 2 || !was_tracked(ren1_src));\n \n \t\t\thashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);\n \t\t\tsrc_other.mode = ren1->src_entry->stages[other_stage].mode;\n@@ -1600,7 +1600,7 @@ static int merge_content(struct merge_options *o,\n \t\t\t o->branch2 == rename_conflict_info->branch1) ?\n \t\t\tpair1->two->path : pair1->one->path;\n \n-\t\tif (dir_in_way(path, !o->call_depth))\n+\t\tif (dir_in_way(path, !o->no_worktree))\n \t\t\tdf_conflict_remains = 1;\n \t}\n \tmfi = merge_file_special_markers(o, &one, &a, &b,\n@@ -1620,7 +1620,7 @@ static int merge_content(struct merge_options *o,\n \t\tpath_renamed_outside_HEAD = !path2 || !strcmp(path, path2);\n \t\tif (!path_renamed_outside_HEAD) {\n \t\t\tadd_cacheinfo(mfi.mode, mfi.sha, path,\n-\t\t\t\t      0, (!o->call_depth), 0);\n+\t\t\t\t      0, (!o->no_worktree), 0);\n \t\t\treturn mfi.clean;\n \t\t}\n \t} else\n@@ -1721,7 +1721,7 @@ static int process_entry(struct merge_options *o,\n \t\t\tif (a_sha)\n \t\t\t\toutput(o, 2, _(\"Removing %s\"), path);\n \t\t\t/* do not touch working file if it did not exist */\n-\t\t\tremove_file(o, 1, path, !a_sha);\n+\t\t\tremove_file(o, 1, path, o->no_worktree || !a_sha);\n \t\t} else {\n \t\t\t/* Modify/delete; deleted side may have put a directory in the way */\n \t\t\tclean_merge = 0;\n@@ -1752,7 +1752,7 @@ static int process_entry(struct merge_options *o,\n \t\t\tsha = b_sha;\n \t\t\tconf = _(\"directory/file\");\n \t\t}\n-\t\tif (dir_in_way(path, !o->call_depth)) {\n+\t\tif (dir_in_way(path, !o->no_worktree)) {\n \t\t\tchar *new_path = unique_path(o, path, add_branch);\n \t\t\tclean_merge = 0;\n \t\t\toutput(o, 1, _(\"CONFLICT (%s): There is a directory with name %s in %s. \"\n@@ -1780,7 +1780,7 @@ static int process_entry(struct merge_options *o,\n \t\t * this entry was deleted altogether. a_mode == 0 means\n \t\t * we had that path and want to actively remove it.\n \t\t */\n-\t\tremove_file(o, 1, path, !a_mode);\n+\t\tremove_file(o, 1, path, o->no_worktree || !a_mode);\n \t} else\n \t\tdie(_(\"Fatal merge failure, shouldn't happen.\"));\n \n@@ -1806,7 +1806,7 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o->call_depth, common, head, merge);\n+\tcode = git_merge_trees(o->no_worktree, common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\n@@ -1915,6 +1915,7 @@ int merge_recursive(struct merge_options *o,\n \n \tfor (iter = ca; iter; iter = iter->next) {\n \t\tconst char *saved_b1, *saved_b2;\n+\t\tunsigned saved_no_worktree;\n \t\to->call_depth++;\n \t\t/*\n \t\t * When the merge fails, the result contains files\n@@ -1924,6 +1925,8 @@ int merge_recursive(struct merge_options *o,\n \t\t * \"conflicts\" were already resolved.\n \t\t */\n \t\tdiscard_cache();\n+\t\tsaved_no_worktree = o->no_worktree;\n+\t\to->no_worktree = 1;\n \t\tsaved_b1 = o->branch1;\n \t\tsaved_b2 = o->branch2;\n \t\to->branch1 = \"Temporary merge branch 1\";\n@@ -1932,6 +1935,7 @@ int merge_recursive(struct merge_options *o,\n \t\t\t\tNULL, &merged_common_ancestors);\n \t\to->branch1 = saved_b1;\n \t\to->branch2 = saved_b2;\n+\t\to->no_worktree = saved_no_worktree;\n \t\to->call_depth--;\n \n \t\tif (!merged_common_ancestors)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 9e090a3..d8dd7a1 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -15,6 +15,7 @@ struct merge_options {\n \tconst char *subtree_shift;\n \tunsigned buffer_output : 1;\n \tunsigned renormalize : 1;\n+\tunsigned no_worktree : 1; /* do not touch worktree */\n \tlong xdl_opts;\n \tint verbosity;\n \tint diff_rename_limit;\n-- \n1.8.3.2.908.gbd0dbd0\n"},{"id":"222761","messageId":"9733c853e2f674380c64e9b3a6f319414816ef28.1373219466.git.trast@inf.ethz.ch","threadId":"34373","inReplyTo":"cover.1373219466.git.trast@inf.ethz.ch","subject":"[PATCH 3/3] merge-recursive: -Xindex-only to leave worktree unchanged","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-07-07T18:02:40Z","receivedAt":"2013-07-07T18:02:40Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Using the new no_worktree flag from the previous commit, we can teach\nmerge-recursive to leave the worktree untouched.  Expose this with a\nnew strategy option so that scripts can use it.\n---\n Documentation/merge-strategies.txt |  4 ++++\n merge-recursive.c                  |  2 ++\n t/t3030-merge-recursive.sh         | 13 +++++++++++++\n 3 files changed, 19 insertions(+)\n\ndiff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt\nindex 49a9a7d..b663a2e 100644\n--- a/Documentation/merge-strategies.txt\n+++ b/Documentation/merge-strategies.txt\n@@ -92,6 +92,10 @@ subtree[=<path>];;\n \tis prefixed (or stripped from the beginning) to make the shape of\n \ttwo trees to match.\n \n+index-only;;\n+\tWrite the merge result only to the index; do not touch the\n+\tworktree.\n+\n octopus::\n \tThis resolves cases with more than two heads, but refuses to do\n \ta complex merge that needs manual resolution.  It is\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b93b762..a58d691 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -2094,6 +2094,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)\n \t\tif ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)\n \t\t\treturn -1;\n \t}\n+\telse if (!strcmp(s, \"index-only\"))\n+\t\to->no_worktree = 1;\n \telse\n \t\treturn -1;\n \treturn 0;\ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex 2f96100..2f3a16c 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -296,6 +296,19 @@ test_expect_success 'merge-recursive result' '\n \n '\n \n+test_expect_success 'merge-recursive --index-only' '\n+\n+\trm -fr [abcd] &&\n+\tgit checkout -f \"$c2\" &&\n+\ttest_expect_code 1 git merge-recursive --index-only \"$c0\" -- \"$c2\" \"$c1\" &&\n+\tgit ls-files -s >actual &&\n+\t# reuses \"expected\" from previous test!\n+\ttest_cmp expected actual &&\n+\tgit diff HEAD >actual-diff &&\n+\t: >expected-diff &&\n+\ttest_cmp expected-diff actual-diff\n+'\n+\n test_expect_success 'fail if the index has unresolved entries' '\n \n \trm -fr [abcd] &&\n-- \n1.8.3.2.908.gbd0dbd0\n"},{"id":"222768","messageId":"7v1u7a43or.fsf@alter.siamese.dyndns.org","threadId":"34373","inReplyTo":"7e823a9faedf1980912628c3a3f083e26fb0c650.1373219466.git.trast@inf.ethz.ch","subject":"Re: [PATCH 2/3] merge-recursive: untangle double meaning of o->call_depth","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2013-07-07T18:37:08Z","receivedAt":"2013-07-07T18:37:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@inf.ethz.ch> writes:\n\n> o->call_depth has a double function: a nonzero call_depth means we\n> want to construct virtual merge bases, but it also means we want to\n> avoid touching the worktree.  Introduce a new flag o->no_worktree for\n> the latter.\n\nI do remember discussing this with you the other day, and while I do\nagree that we may not want to touch the working tree in the\noutermost merge (i.e. o->call_depth is zero) in some applications, I\ndo not think of a situation where you _do_ want to touch working\ntree while performing the inner merge.  I'd feel safer if the code\nsaid that \"no matter what no-worktree option says, we won't touch\nthe working tree if o->call_depth is not zero\" clearly in some way,\ne.g. \n\n\tif (o->call_depth || o->in_index_merge)\n        \treturn; /* leave without touching working tree */\n\nIn other words, I do not like the part of the code that pretends\nthese two are independent options, when what we really want is to\nhave two modes for the outermost (o->call_depth == 0) case.\n"},{"id":"222837","messageId":"51DAD8F2.5070008@alum.mit.edu","threadId":"34373","inReplyTo":"cover.1373219466.git.trast@inf.ethz.ch","subject":"Re: [PATCH 0/3] merge -Xindex-only","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-07-08T15:21:22Z","receivedAt":"2013-07-08T15:21:22Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"[Resend because of address confusion in replied-to email.]\n\nOn 07/07/2013 08:00 PM, Thomas Rast wrote:\n> I recently looked into making merge-recursive more useful as a modular\n> piece in various tasks, e.g. Michael's git-imerge and the experiments\n> I made in showing evil merges.\n> \n> This miniseries is the extremely low-hanging fruit.  If it makes a\n> good first step for git-imerge, perhaps it can go in like this.  It's\n> not a big speedup (about 2.2s vs 2.4s in a sample conflicting merge in\n> git.git), but it does feel much cleaner to avoid touching the worktree\n> unless actually necessary.\n> \n> Otherwise it's probably not worth it just yet; for what I want to do\n> with it, we need some more reshuffling of things.\n\nInteresting.\n\nFor git-imerge, it would be nice to speed up merges by skipping the\nworking tree updates.  10% might not be so noticeable, but every little\nbit helps :-)\n\nBut the killer benefit would be if git-imerge could do some of its\nautomatic merge-testing and autofilling in the background while the user\nis resolving conflicts in the main index and working tree.\n\nIs it possible to use this option with an alternate index file (e.g.,\nvia the GIT_INDEX_FILE environment variable)?  Can it be made to leave\nother shared state (e.g., MERGE_HEAD) alone?  If so, maybe it's already\nthere.\n\nFor this feature to be useful for test merges, it would be enough to\njust get a retcode saying whether a given merge would succeed or conflict.\n\nFor it to be used for autofilling, it would also be necessary to be able\nto create a commit from the merge result in the alternate index (this\nwould only be done when there are no conflicts).  I assume this part can\nbe done in the usual way using \"git commit-tree\".\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"222839","messageId":"87k3l1gip1.fsf@linux-k42r.v.cablecom.net","threadId":"34373","inReplyTo":"51DAD8F2.5070008@alum.mit.edu","subject":"Re: [PATCH 0/3] merge -Xindex-only","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-07-08T15:44:26Z","receivedAt":"2013-07-08T15:44:26Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Michael Haggerty <mhagger@alum.mit.edu> writes:\n\n> [Resend because of address confusion in replied-to email.]\n>\n> On 07/07/2013 08:00 PM, Thomas Rast wrote:\n>> I recently looked into making merge-recursive more useful as a modular\n>> piece in various tasks, e.g. Michael's git-imerge and the experiments\n>> I made in showing evil merges.\n>> \n>> This miniseries is the extremely low-hanging fruit.  If it makes a\n>> good first step for git-imerge, perhaps it can go in like this.  It's\n>> not a big speedup (about 2.2s vs 2.4s in a sample conflicting merge in\n>> git.git), but it does feel much cleaner to avoid touching the worktree\n>> unless actually necessary.\n>> \n>> Otherwise it's probably not worth it just yet; for what I want to do\n>> with it, we need some more reshuffling of things.\n>\n> Interesting.\n>\n> For git-imerge, it would be nice to speed up merges by skipping the\n> working tree updates.  10% might not be so noticeable, but every little\n> bit helps :-)\n>\n> But the killer benefit would be if git-imerge could do some of its\n> automatic merge-testing and autofilling in the background while the user\n> is resolving conflicts in the main index and working tree.\n>\n> Is it possible to use this option with an alternate index file (e.g.,\n> via the GIT_INDEX_FILE environment variable)?  Can it be made to leave\n> other shared state (e.g., MERGE_HEAD) alone?  If so, maybe it's already\n> there.\n\nGIT_INDEX_FILE yes, that one works out of the box.\n\nI think for the shared state, the following is a (probably) ridiculously\nunsupported yet magic way of achieving this:\n\n  mkdir -p unshared/.git\n  cd unshared/.git\n  for f in ../../.git/*; do\n    case \"$f\" in\n      *HEAD | index)\n        cp \"$f\" .\n        ;;\n      *)\n        ln -s \"$f\" .\n        ;;\n    esac\n  done\n\nThat gives you a repository that propagates ref changes and object\nwriting, but does not propagate changes to index, HEAD, FETCH_HEAD or\nMERGE_HEAD.  Which might just be what you need?\n\nNote that as far as I'm concerned, this is a live handgrenade.  It could\nblow up in your face at any time, but it probably has its applications...\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"222894","messageId":"51DBDB6F.7090105@alum.mit.edu","threadId":"34373","inReplyTo":"87k3l1gip1.fsf@linux-k42r.v.cablecom.net","subject":"Re: [PATCH 0/3] merge -Xindex-only","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-07-09T09:44:15Z","receivedAt":"2013-07-09T09:44:15Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 07/08/2013 05:44 PM, Thomas Rast wrote:\n> Michael Haggerty <mhagger@alum.mit.edu> writes:\n> \n>> [Resend because of address confusion in replied-to email.]\n>>\n>> On 07/07/2013 08:00 PM, Thomas Rast wrote:\n>>> I recently looked into making merge-recursive more useful as a modular\n>>> piece in various tasks, e.g. Michael's git-imerge and the experiments\n>>> I made in showing evil merges.\n>>>\n>>> This miniseries is the extremely low-hanging fruit.  If it makes a\n>>> good first step for git-imerge, perhaps it can go in like this.  It's\n>>> not a big speedup (about 2.2s vs 2.4s in a sample conflicting merge in\n>>> git.git), but it does feel much cleaner to avoid touching the worktree\n>>> unless actually necessary.\n>>>\n>>> Otherwise it's probably not worth it just yet; for what I want to do\n>>> with it, we need some more reshuffling of things.\n>>\n>> Interesting.\n>>\n>> For git-imerge, it would be nice to speed up merges by skipping the\n>> working tree updates.  10% might not be so noticeable, but every little\n>> bit helps :-)\n>>\n>> But the killer benefit would be if git-imerge could do some of its\n>> automatic merge-testing and autofilling in the background while the user\n>> is resolving conflicts in the main index and working tree.\n>>\n>> Is it possible to use this option with an alternate index file (e.g.,\n>> via the GIT_INDEX_FILE environment variable)?  Can it be made to leave\n>> other shared state (e.g., MERGE_HEAD) alone?  If so, maybe it's already\n>> there.\n> \n> GIT_INDEX_FILE yes, that one works out of the box.\n> \n> I think for the shared state, the following is a (probably) ridiculously\n> unsupported yet magic way of achieving this:\n> \n>   mkdir -p unshared/.git\n>   cd unshared/.git\n>   for f in ../../.git/*; do\n>     case \"$f\" in\n>       *HEAD | index)\n>         cp \"$f\" .\n>         ;;\n>       *)\n>         ln -s \"$f\" .\n>         ;;\n>     esac\n>   done\n> \n> That gives you a repository that propagates ref changes and object\n> writing, but does not propagate changes to index, HEAD, FETCH_HEAD or\n> MERGE_HEAD.  Which might just be what you need?\n> \n> Note that as far as I'm concerned, this is a live handgrenade.  It could\n> blow up in your face at any time, but it probably has its applications...\n\nI might consider such a thing for my own use, but I don't think I'll lob\nlive hand grenades at innocent git-imerge users :-)\n\nSince you've already implemented a way to merge into the index (even an\nalternative index) without touching the working copy, I'll just cross my\nfingers and hope for the appearance of an option that makes merge leave\nHEAD, MERGE_HEAD, etc. untouched.\n\nIt's not like I have any free time to work on git-imerge anyway :-(\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"222919","messageId":"878v1gey16.fsf@linux-k42r.v.cablecom.net","threadId":"34373","inReplyTo":"51DBDB6F.7090105@alum.mit.edu","subject":"Re: [PATCH 0/3] merge -Xindex-only","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-07-09T12:08:21Z","receivedAt":"2013-07-09T12:08:21Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Michael Haggerty <mhagger@alum.mit.edu> writes:\n\n> Since you've already implemented a way to merge into the index (even an\n> alternative index) without touching the working copy, I'll just cross my\n> fingers and hope for the appearance of an option that makes merge leave\n> HEAD, MERGE_HEAD, etc. untouched.\n\nThe most annoying part is probably where to put the output, since\nmerging is more or less defined to do one of:\n\n- update HEAD and return 0\n- update MERGE_HEAD and return 1\n\nI'm not sure how much flexibility is worth having.  Would it be\nsufficient if you had an option, e.g. -Xresult-ref=refs/heads/foo, that\nchanges it to:\n\n- update refs/heads/foo and return 0\n- return 1, not updating any refs\n\nThat would mean that it would only work for noninteractive use.  In the\nconflicting case, the driving script would need to remember what it\nwanted to merge so as have the information when finally committing.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"222954","messageId":"51DC66A5.80500@alum.mit.edu","threadId":"34373","inReplyTo":"878v1gey16.fsf@linux-k42r.v.cablecom.net","subject":"Re: [PATCH 0/3] merge -Xindex-only","fromName":"Michael Haggerty","fromEmail":"mhagger@alum.mit.edu","sentAt":"2013-07-09T19:38:13Z","receivedAt":"2013-07-09T19:38:13Z","isPatch":true,"sender":{"key":"mhagger@alum.mit.edu","avatar":"https://avatars.githubusercontent.com/u/119718?v=4"},"body":"On 07/09/2013 02:08 PM, Thomas Rast wrote:\n> Michael Haggerty <mhagger@alum.mit.edu> writes:\n> \n>> Since you've already implemented a way to merge into the index (even an\n>> alternative index) without touching the working copy, I'll just cross my\n>> fingers and hope for the appearance of an option that makes merge leave\n>> HEAD, MERGE_HEAD, etc. untouched.\n> \n> The most annoying part is probably where to put the output, since\n> merging is more or less defined to do one of:\n> \n> - update HEAD and return 0\n> - update MERGE_HEAD and return 1\n\nI don't understand what you mean here.  Why does *any* reference need to\nbe updated?  Why not\n\n* load arbitrary commit-ish A into index\n\n* merge arbitrary commit-ish B into index\n\n* return error/OK depending on whether there was a conflict\n\n?  The script that started the whole process would know what A and B are\nand could create the commit itself using \"git write-tree\" and \"git\ncommit-tree -p A -p B\".  And if the index were an alternative index\nchosen via GIT_INDEX_FILE then the rest of the git repo would be none\nthe wiser.\n\n> I'm not sure how much flexibility is worth having.  Would it be\n> sufficient if you had an option, e.g. -Xresult-ref=refs/heads/foo, that\n> changes it to:\n> \n> - update refs/heads/foo and return 0\n> - return 1, not updating any refs\n> \n> That would mean that it would only work for noninteractive use.  In the\n> conflicting case, the driving script would need to remember what it\n> wanted to merge so as have the information when finally committing.\n\nThat would be fine with me.\n\nMichael\n\n-- \nMichael Haggerty\nmhagger@alum.mit.edu\nhttp://softwareswirl.blogspot.com/\n"},{"id":"222965","messageId":"87wqozbj0d.fsf@hexa.v.cablecom.net","threadId":"34373","inReplyTo":"51DC66A5.80500@alum.mit.edu","subject":"Re: [PATCH 0/3] merge -Xindex-only","fromName":"Thomas Rast","fromEmail":"trast@inf.ethz.ch","sentAt":"2013-07-09T20:01:06Z","receivedAt":"2013-07-09T20:01:06Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Michael Haggerty <mhagger@alum.mit.edu> writes:\n\n> On 07/09/2013 02:08 PM, Thomas Rast wrote:\n>> Michael Haggerty <mhagger@alum.mit.edu> writes:\n>> \n>>> Since you've already implemented a way to merge into the index (even an\n>>> alternative index) without touching the working copy, I'll just cross my\n>>> fingers and hope for the appearance of an option that makes merge leave\n>>> HEAD, MERGE_HEAD, etc. untouched.\n>> \n>> The most annoying part is probably where to put the output, since\n>> merging is more or less defined to do one of:\n>> \n>> - update HEAD and return 0\n>> - update MERGE_HEAD and return 1\n>\n> I don't understand what you mean here. [...]\n\nI was simply trying to describe what the status quo is, as a basis for\nthe next paragraph.  Does that clarify it?\n\n>> I'm not sure how much flexibility is worth having.  Would it be\n>> sufficient if you had an option, e.g. -Xresult-ref=refs/heads/foo, that\n>> changes it to:\n>> \n>> - update refs/heads/foo and return 0\n>> - return 1, not updating any refs\n>> \n>> That would mean that it would only work for noninteractive use.  In the\n>> conflicting case, the driving script would need to remember what it\n>> wanted to merge so as have the information when finally committing.\n>\n> That would be fine with me.\n\nOn IRC you said you would like a version that always acts as\n--no-commit, and simply returns the conflict/no conflict bit as usual.\nThe caller would then proceed using commit-tree itself.  I think that is\nprobably a saner solution than this \"output ref\" idea.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"229564","messageId":"87ob6cm6y1.fsf@linux-k42r.v.cablecom.net","threadId":"34373","inReplyTo":"87wqozbj0d.fsf@hexa.v.cablecom.net","subject":"Re: [PATCH 0/3] merge -Xindex-only","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2013-10-26T14:40:22Z","receivedAt":"2013-10-26T14:40:22Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Thomas Rast <trast@inf.ethz.ch> writes:\n\n> Michael Haggerty <mhagger@alum.mit.edu> writes:\n>\n> On IRC you said you would like a version that always acts as\n> --no-commit, and simply returns the conflict/no conflict bit as usual.\n> The caller would then proceed using commit-tree itself.  I think that is\n> probably a saner solution than this \"output ref\" idea.\n\nI just had a huge facepalm moment.  We already have this option.  It is\ncalled git-merge-recursive.\n\nThat is,\n\n  git merge-recursive $(git merge-base --all HEAD other) -- HEAD other\n\nwill internally do all the work that 'git merge other' would do, but not\nupdate any refs.  With this series, you can therefore say\n\n  git merge-recursive --index-only $(git merge-base --all HEAD other) -- HEAD other\n\nand get an *index-only* merge of HEAD and other.\n\nCan you see if this is enough to build git-imerge on top of it?\nOtherwise I'm glad to help with building the git-merge infrastucture to\nsupport it.\n\nI'll send v2 of the series in a minute; the only change is that I\nchanged the internal flag semantics as per Junio's comment in\n\n  http://thread.gmane.org/gmane.comp.version-control.git/229787/focus=229797\n\n-- \nThomas Rast\ntr@thomasrast.ch\n"},{"id":"229566","messageId":"262a408fc2f55df92eb2754eb1475c1af6fec19e.1382798450.git.tr@thomasrast.ch","threadId":"34373","inReplyTo":"87ob6cm6y1.fsf@linux-k42r.v.cablecom.net","subject":"[PATCH 1/3] merge-recursive: remove dead conditional in update_stages()","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2013-10-26T14:43:16Z","receivedAt":"2013-10-26T14:43:16Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Thomas Rast <trast@inf.ethz.ch>\n\n650467c (merge-recursive: Consolidate different update_stages\nfunctions, 2011-08-11) changed the former argument 'clear' to always\nbe true.  Remove the useless conditional.\n\nSigned-off-by: Thomas Rast <trast@inf.ethz.ch>\n---\n\nUnchanged.\n\n merge-recursive.c | 6 ++----\n 1 file changed, 2 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex dbb7104..bdf69cb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -545,11 +545,9 @@ static int update_stages(const char *path, const struct diff_filespec *o,\n \t * would_lose_untracked).  Instead, reverse the order of the calls\n \t * (executing update_file first and then update_stages).\n \t */\n-\tint clear = 1;\n \tint options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;\n-\tif (clear)\n-\t\tif (remove_file_from_cache(path))\n-\t\t\treturn -1;\n+\tif (remove_file_from_cache(path))\n+\t\treturn -1;\n \tif (o)\n \t\tif (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options))\n \t\t\treturn -1;\n-- \n1.8.4.1.841.gb1dcd95\n"},{"id":"229567","messageId":"fcc9f396bd221fe68ed08760b03b056159c23b4b.1382798450.git.tr@thomasrast.ch","threadId":"34373","inReplyTo":"87ob6cm6y1.fsf@linux-k42r.v.cablecom.net","subject":"[PATCH 2/3] merge-recursive: internal flag to avoid touching the worktree","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2013-10-26T14:43:17Z","receivedAt":"2013-10-26T14:43:17Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Thomas Rast <trast@inf.ethz.ch>\n\no->call_depth has a double function: a nonzero call_depth means we\nwant to construct virtual merge bases, but it also means we want to\navoid touching the worktree.  Introduce a new flag o->no_worktree to\ntrigger only the latter.\n\nSigned-off-by: Thomas Rast <trast@inf.ethz.ch>\n---\n\nAdapted as per Junio's comments in\n\n  http://thread.gmane.org/gmane.comp.version-control.git/229787/focus=229797\n\nI'm not sure which way is better; the downside of this version is that\nwe now have the burden of ensuring that o->no_worktree always remains\na strict subset of o->call_depth whenever we touch this code.\n\n\n merge-recursive.c | 37 +++++++++++++++++++++----------------\n merge-recursive.h |  1 +\n 2 files changed, 22 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex bdf69cb..922a259 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -408,10 +408,10 @@ static void record_df_conflict_files(struct merge_options *o,\n \tint i;\n \n \t/*\n-\t * If we're merging merge-bases, we don't want to bother with\n-\t * any working directory changes.\n+\t * If we're working in-core only (e.g., merging merge-bases),\n+\t * we don't want to bother with any working directory changes.\n \t */\n-\tif (o->call_depth)\n+\tif (o->call_depth || o->no_worktree)\n \t\treturn;\n \n \t/* Ensure D/F conflicts are adjacent in the entries list. */\n@@ -724,7 +724,7 @@ static void update_file_flags(struct merge_options *o,\n \t\t\t      int update_cache,\n \t\t\t      int update_wd)\n {\n-\tif (o->call_depth)\n+\tif (o->call_depth || o->no_worktree)\n \t\tupdate_wd = 0;\n \n \tif (update_wd) {\n@@ -931,7 +931,8 @@ static struct merge_file_info merge_file_1(struct merge_options *o,\n \t\t\tresult.clean = merge_submodule(result.sha,\n \t\t\t\t\t\t       one->path, one->sha1,\n \t\t\t\t\t\t       a->sha1, b->sha1,\n-\t\t\t\t\t\t       !o->call_depth);\n+\t\t\t\t\t\t       !(o->call_depth ||\n+\t\t\t\t\t\t\t o->no_worktree));\n \t\t} else if (S_ISLNK(a->mode)) {\n \t\t\thashcpy(result.sha, a->sha1);\n \n@@ -1003,7 +1004,7 @@ static void handle_change_delete(struct merge_options *o,\n \t\t\t\t const char *change, const char *change_past)\n {\n \tchar *renamed = NULL;\n-\tif (dir_in_way(path, !o->call_depth)) {\n+\tif (dir_in_way(path, !(o->call_depth || o->no_worktree))) {\n \t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n \t}\n \n@@ -1128,10 +1129,10 @@ static void handle_file(struct merge_options *o,\n \t\tchar *add_name = unique_path(o, rename->path, other_branch);\n \t\tupdate_file(o, 0, add->sha1, add->mode, add_name);\n \n-\t\tremove_file(o, 0, rename->path, 0);\n+\t\tremove_file(o, 0, rename->path, o->call_depth || o->no_worktree);\n \t\tdst_name = unique_path(o, rename->path, cur_branch);\n \t} else {\n-\t\tif (dir_in_way(rename->path, !o->call_depth)) {\n+\t\tif (dir_in_way(rename->path, !(o->call_depth || o->no_worktree))) {\n \t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n \t\t\toutput(o, 1, _(\"%s is a directory in %s adding as %s instead\"),\n \t\t\t       rename->path, other_branch, dst_name);\n@@ -1238,7 +1239,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \t\t * merge base just undo the renames; they can be detected\n \t\t * again later for the non-recursive merge.\n \t\t */\n-\t\tremove_file(o, 0, path, 0);\n+\t\tremove_file(o, 0, path, o->call_depth || o->no_worktree);\n \t\tupdate_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);\n \t\tupdate_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);\n \t} else {\n@@ -1246,7 +1247,7 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n \t\toutput(o, 1, _(\"Renaming %s to %s and %s to %s instead\"),\n \t\t       a->path, new_path1, b->path, new_path2);\n-\t\tremove_file(o, 0, path, 0);\n+\t\tremove_file(o, 0, path, o->call_depth || o->no_worktree);\n \t\tupdate_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);\n \t\tupdate_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);\n \t\tfree(new_path2);\n@@ -1405,6 +1406,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t * add-source case).\n \t\t\t */\n \t\t\tremove_file(o, 1, ren1_src,\n+\t\t\t\t    o->call_depth || o->no_worktree ||\n \t\t\t\t    renamed_stage == 2 || !was_tracked(ren1_src));\n \n \t\t\thashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);\n@@ -1601,7 +1603,7 @@ static int merge_content(struct merge_options *o,\n \t\t\t o->branch2 == rename_conflict_info->branch1) ?\n \t\t\tpair1->two->path : pair1->one->path;\n \n-\t\tif (dir_in_way(path, !o->call_depth))\n+\t\tif (dir_in_way(path, !(o->call_depth || o->no_worktree)))\n \t\t\tdf_conflict_remains = 1;\n \t}\n \tmfi = merge_file_special_markers(o, &one, &a, &b,\n@@ -1621,7 +1623,7 @@ static int merge_content(struct merge_options *o,\n \t\tpath_renamed_outside_HEAD = !path2 || !strcmp(path, path2);\n \t\tif (!path_renamed_outside_HEAD) {\n \t\t\tadd_cacheinfo(mfi.mode, mfi.sha, path,\n-\t\t\t\t      0, (!o->call_depth), 0);\n+\t\t\t\t      0, !(o->call_depth || o->no_worktree), 0);\n \t\t\treturn mfi.clean;\n \t\t}\n \t} else\n@@ -1722,7 +1724,8 @@ static int process_entry(struct merge_options *o,\n \t\t\tif (a_sha)\n \t\t\t\toutput(o, 2, _(\"Removing %s\"), path);\n \t\t\t/* do not touch working file if it did not exist */\n-\t\t\tremove_file(o, 1, path, !a_sha);\n+\t\t\tremove_file(o, 1, path,\n+\t\t\t\t    o->call_depth || o->no_worktree || !a_sha);\n \t\t} else {\n \t\t\t/* Modify/delete; deleted side may have put a directory in the way */\n \t\t\tclean_merge = 0;\n@@ -1753,7 +1756,7 @@ static int process_entry(struct merge_options *o,\n \t\t\tsha = b_sha;\n \t\t\tconf = _(\"directory/file\");\n \t\t}\n-\t\tif (dir_in_way(path, !o->call_depth)) {\n+\t\tif (dir_in_way(path, !(o->call_depth || o->no_worktree))) {\n \t\t\tchar *new_path = unique_path(o, path, add_branch);\n \t\t\tclean_merge = 0;\n \t\t\toutput(o, 1, _(\"CONFLICT (%s): There is a directory with name %s in %s. \"\n@@ -1781,7 +1784,8 @@ static int process_entry(struct merge_options *o,\n \t\t * this entry was deleted altogether. a_mode == 0 means\n \t\t * we had that path and want to actively remove it.\n \t\t */\n-\t\tremove_file(o, 1, path, !a_mode);\n+\t\tremove_file(o, 1, path,\n+\t\t\t    o->call_depth || o->no_worktree || !a_mode);\n \t} else\n \t\tdie(_(\"Fatal merge failure, shouldn't happen.\"));\n \n@@ -1807,7 +1811,8 @@ int merge_trees(struct merge_options *o,\n \t\treturn 1;\n \t}\n \n-\tcode = git_merge_trees(o->call_depth, common, head, merge);\n+\tcode = git_merge_trees(o->call_depth || o->no_worktree,\n+\t\t\t       common, head, merge);\n \n \tif (code != 0) {\n \t\tif (show(o, 4) || o->call_depth)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 9e090a3..d8dd7a1 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -15,6 +15,7 @@ struct merge_options {\n \tconst char *subtree_shift;\n \tunsigned buffer_output : 1;\n \tunsigned renormalize : 1;\n+\tunsigned no_worktree : 1; /* do not touch worktree */\n \tlong xdl_opts;\n \tint verbosity;\n \tint diff_rename_limit;\n-- \n1.8.4.1.841.gb1dcd95\n"},{"id":"229565","messageId":"1df84557649b143c4ad11a8ad39000ef253c5e8a.1382798450.git.tr@thomasrast.ch","threadId":"34373","inReplyTo":"87ob6cm6y1.fsf@linux-k42r.v.cablecom.net","subject":"[PATCH 3/3] merge-recursive: -Xindex-only to leave worktree unchanged","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2013-10-26T14:43:18Z","receivedAt":"2013-10-26T14:43:18Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"From: Thomas Rast <trast@inf.ethz.ch>\n\nUsing the new no_worktree flag from the previous commit, we can teach\nmerge-recursive to leave the worktree untouched.  Expose this with a\nnew strategy option so that scripts can use it.\n---\n\nUnchanged.\n\n Documentation/merge-strategies.txt |  4 ++++\n merge-recursive.c                  |  2 ++\n t/t3030-merge-recursive.sh         | 13 +++++++++++++\n 3 files changed, 19 insertions(+)\n\ndiff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt\nindex 49a9a7d..b663a2e 100644\n--- a/Documentation/merge-strategies.txt\n+++ b/Documentation/merge-strategies.txt\n@@ -92,6 +92,10 @@ subtree[=<path>];;\n \tis prefixed (or stripped from the beginning) to make the shape of\n \ttwo trees to match.\n \n+index-only;;\n+\tWrite the merge result only to the index; do not touch the\n+\tworktree.\n+\n octopus::\n \tThis resolves cases with more than two heads, but refuses to do\n \ta complex merge that needs manual resolution.  It is\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 922a259..addfb72 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -2096,6 +2096,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)\n \t\tif ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)\n \t\t\treturn -1;\n \t}\n+\telse if (!strcmp(s, \"index-only\"))\n+\t\to->no_worktree = 1;\n \telse\n \t\treturn -1;\n \treturn 0;\ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex 2f96100..2f3a16c 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -296,6 +296,19 @@ test_expect_success 'merge-recursive result' '\n \n '\n \n+test_expect_success 'merge-recursive --index-only' '\n+\n+\trm -fr [abcd] &&\n+\tgit checkout -f \"$c2\" &&\n+\ttest_expect_code 1 git merge-recursive --index-only \"$c0\" -- \"$c2\" \"$c1\" &&\n+\tgit ls-files -s >actual &&\n+\t# reuses \"expected\" from previous test!\n+\ttest_cmp expected actual &&\n+\tgit diff HEAD >actual-diff &&\n+\t: >expected-diff &&\n+\ttest_cmp expected-diff actual-diff\n+'\n+\n test_expect_success 'fail if the index has unresolved entries' '\n \n \trm -fr [abcd] &&\n-- \n1.8.4.1.841.gb1dcd95\n"}]}