{"thread":{"id":"35800","subject":"[PATCH 0/9] remerge diff proof of concept/RFC","startedAt":"2014-02-04T22:17:29Z","lastAt":"2014-02-04T23:04:35Z","messageCount":11,"participants":["Thomas Rast","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"234240","messageId":"cover.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":null,"subject":"[PATCH 0/9] remerge diff proof of concept/RFC","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:29Z","receivedAt":"2014-02-04T22:17:29Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Hi,\n\nThis may look intimidating, but it's actually 3.5 separate things:\n\n  merge-recursive: remove dead conditional in update_stages()\n  merge-recursive: internal flag to avoid touching the worktree\n  merge-recursive: -Xindex-only to leave worktree unchanged\n\nThese are unchanged from tr/merge-recursive-index-only.  I'm just\nresending them here because it's been a while, the rest depends on it,\nand it makes it more obvious how patch 8 fits in.\n\n  pretty: refactor add_merge_info() into parts\n  log: add a merge base inspection option\n\nThis seemed a good idea when I wrote it, but the more I think about\nit, the less useful it appears to me.  I left it here as a\ndemonstration that computing the merge bases as we go is feasible.\n\n  combine-diff: do not pass revs->dense_combined_merges redundantly\n  Fold all merge diff variants into an enum\n\nSome initial refactoring.\n\n  merge-recursive: allow storing conflict hunks in index\n  log --remerge-diff: show what the conflict resolution changed\n\nThe real meat.\n\nI'll only describe the last patches here.\n\nContext: last summer (yes, it's been a while) I investigated some\nangles of looking at merges that make it easier to investigate\nmismerges.  The problem space is: it is exceedingly hard to find\ninstances of\n\n  git merge -s ours foo\n  git merge -Xours foo\n  manually resolve some hunks in favor of one side\n\nafter some time has passed.  The only ways I found with existing\nmachinery involve too much manual inspection.\n\nAt the time I made some scripts that look at the problem in various\nways:\n\n  https://github.com/trast/evilmergediff\n\nThis series explores another angle, which I call \"remerge diff\".  It\nworks by re-doing the merge in core, using features from patches 1-3\nand 7.  Likely that will result in conflicts, which are formatted in\nthe usual <<<<<<< way.  Then it diffs this \"remerge\" against the\nmerge's tree that is recorded in history.\n\nThis tends to give nice results in practice; I pasted a particularly\npretty example at the bottom.\n\n\nHowever, this is RFC because nontrivial merges can easily lead to\nstates that I cannot handle with the current -- pretty neat and\nsimple, I think -- implementation.\n\nFor example, a delete/modify conflict does not result in a nice\nconflict-hunk formatted file, it just leaves 2 (out of 3) stages in\nthe index.  I can then not write a tree from that.\n\nThis could be fixed by adding a pass that turns a missing stage 2 or 3\ninto an empty file at that stage, so that the resulting file looks\nlike\n\n  <<<<<<<\n  =======\n  ... stage 3 content goes here ...\n  >>>>>>>\n\n(analogously for stage 3 missing).\n\nBut that still doesn't handle at least directory/file conflicts.\n\nSo I would welcome comments, and/or better ideas, on the following\nproposed resolution:\n\n* Implement what I described last, to take care of delete/modify\n  conflicts.\n\n* Punt on more complex conflicts, by removing those files from the\n  index, and emitting a warning about those files instead.\n\nNote that even without delete/modify handling, this covers the vast\nmajority of merges: among some 8090 merges I sampled from git.git,\nthere are only 13 that trigger the \"BUG: ...\" message, and 34\noctopuses.\n\n\nExample run:\n\n  $ git show --remerge-diff a39b15b4f6a3f08b67b17d968935d177821e680f\n  [...]\n  diff --git a/builtin/ls-files.c b/builtin/ls-files.c\n  index 0f4473e..175e6e3 100644\n  --- a/builtin/ls-files.c\n  +++ b/builtin/ls-files.c\n  @@ -490,16 +490,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)\n   \t\tOPT_BOOLEAN('u', \"unmerged\", &show_unmerged,\n   \t\t\tN_(\"show unmerged files in the output\")),\n   \t\tOPT_BOOLEAN(0, \"resolve-undo\", &show_resolve_undo,\n  -<<<<<<< f12e49ae877ad0644b9b9939b7cb742da98691d2\n   \t\t\t    N_(\"show resolve-undo information\")),\n  -\t\t{ OPTION_CALLBACK, 'x', \"exclude\", &dir.exclude_list[EXC_CMDL], N_(\"pattern\"),\n  +\t\t{ OPTION_CALLBACK, 'x', \"exclude\", &exclude_list, N_(\"pattern\"),\n   \t\t\tN_(\"skip files matching pattern\"),\n  -=======\n  -\t\t\t    \"show resolve-undo information\"),\n  -\t\t{ OPTION_CALLBACK, 'x', \"exclude\",\n  -\t\t\t&exclude_list, \"pattern\",\n  -\t\t\t\"skip files matching pattern\",\n  ->>>>>>> 72aeb18772deeb386da7dd8997b969877bd29e41\n   \t\t\t0, option_parse_exclude },\n   \t\t{ OPTION_CALLBACK, 'X', \"exclude-from\", &dir, N_(\"file\"),\n   \t\t\tN_(\"exclude patterns are read from <file>\"),\n  diff --git a/dir.c b/dir.c\n  index 8c58ce4..cf1e6b0 100644\n  --- a/dir.c\n  +++ b/dir.c\n  @@ -1674,14 +1674,14 @@ void free_pathspec(struct pathspec *pathspec)\n   \tpathspec->items = NULL;\n   }\n   \n  -<<<<<<< f12e49ae877ad0644b9b9939b7cb742da98691d2\n   int limit_pathspec_to_literal(void)\n   {\n   \tstatic int flag = -1;\n   \tif (flag < 0)\n   \t\tflag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);\n   \treturn flag;\n  -=======\n  +}\n  +\n   /*\n    * Frees memory within dir which was allocated for exclude lists and\n    * the exclude_stack.  Does not free dir itself.\n  @@ -1710,5 +1710,4 @@ void clear_directory(struct dir_struct *dir)\n   \t\tfree(stk);\n   \t\tstk = prev;\n   \t}\n  ->>>>>>> 72aeb18772deeb386da7dd8997b969877bd29e41\n   }\n\n\n\n\n Documentation/merge-strategies.txt |   9 +++\n Documentation/rev-list-options.txt |  14 ++++\n builtin/diff-files.c               |   5 +-\n builtin/diff-tree.c                |   2 +-\n builtin/diff.c                     |  12 +--\n builtin/fmt-merge-msg.c            |   2 +-\n builtin/log.c                      |   9 +--\n builtin/merge.c                    |   1 -\n combine-diff.c                     |  13 ++--\n commit.h                           |   1 +\n diff-lib.c                         |  13 ++--\n diff.h                             |   6 +-\n log-tree.c                         |  69 +++++++++++++++++-\n merge-recursive.c                  |  52 +++++++------\n merge-recursive.h                  |   3 +\n pretty.c                           |  41 +++++++----\n revision.c                         |  17 ++---\n revision.h                         |  26 ++++++-\n submodule.c                        |   3 +-\n t/t3030-merge-recursive.sh         |  33 +++++++++\n t/t4202-log.sh                     |  34 +++++++++\n t/t4213-log-remerge-diff.sh        | 145 +++++++++++++++++++++++++++++++++++++\n 22 files changed, 426 insertions(+), 84 deletions(-)\n create mode 100755 t/t4213-log-remerge-diff.sh\n\n-- \n1.9.rc2.232.gdd31389\n"},{"id":"234250","messageId":"37f625c87b3d0fd61988ee9e8e31f4c227093f5e.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[PATCH 1/9] merge-recursive: remove dead conditional in update_stages()","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:30Z","receivedAt":"2014-02-04T22:17:30Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\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 8400a8e..c36dc79 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.9.rc2.232.gdd31389\n"},{"id":"234249","messageId":"b2b2191a4099bc2635e17dbb22f1b2993f24ff05.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[PATCH 2/9] merge-recursive: internal flag to avoid touching the worktree","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:31Z","receivedAt":"2014-02-04T22:17:31Z","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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\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 c36dc79..35be144 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.9.rc2.232.gdd31389\n"},{"id":"234241","messageId":"fb913c1851cd411866d3e689eba5c49a3298a25e.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[PATCH 3/9] merge-recursive: -Xindex-only to leave worktree unchanged","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:32Z","receivedAt":"2014-02-04T22:17:32Z","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\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\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 fb6e593..2934e99 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 35be144..f59c1d3 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.9.rc2.232.gdd31389\n"},{"id":"234244","messageId":"935d776f5799f1dd9ecc0010059a379dec224172.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[POC PATCH 4/9] pretty: refactor add_merge_info() into parts","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:33Z","receivedAt":"2014-02-04T22:17:33Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"pp_commit_list() will be reused later.\n\nSigned-off-by: Thomas Rast <tr@thomasrast.ch>\n---\n\nNecessary only for the next patch, which may be of dubious value.\n\n commit.h |  1 +\n pretty.c | 40 ++++++++++++++++++++++++++--------------\n 2 files changed, 27 insertions(+), 14 deletions(-)\n\ndiff --git a/commit.h b/commit.h\nindex 16d9c43..b51817b 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -101,6 +101,7 @@ struct pretty_print_context {\n \tstruct string_list *mailmap;\n \tint color;\n \tstruct ident_split *from_ident;\n+\tstruct commit_list *merge_bases;\n \n \t/*\n \t * Fields below here are manipulated internally by pp_* functions and\ndiff --git a/pretty.c b/pretty.c\nindex 87db08b..5e44cf8 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -517,6 +517,31 @@ static const char *skip_empty_lines(const char *msg)\n \treturn msg;\n }\n \n+static const char *pp_sha1_to_hex(const struct pretty_print_context *pp,\n+\t\t\t\t  const unsigned char *sha1)\n+{\n+\tconst char *hex = NULL;\n+\tif (pp->abbrev)\n+\t\thex = find_unique_abbrev(sha1, pp->abbrev);\n+\tif (!hex)\n+\t\thex = sha1_to_hex(sha1);\n+\treturn hex;\n+}\n+\n+static void pp_commit_list(const struct pretty_print_context *pp,\n+\t\t\t   struct strbuf *sb,\n+\t\t\t   const char *prefix,\n+\t\t\t   const struct commit_list *list)\n+{\n+\tstrbuf_addstr(sb, prefix);\n+\twhile (list) {\n+\t\tstruct commit *commit = list->item;\n+\t\tstrbuf_addf(sb, \" %s\", pp_sha1_to_hex(pp, commit->object.sha1));\n+\t\tlist = list->next;\n+\t}\n+\tstrbuf_addch(sb, '\\n');\n+}\n+\n static void add_merge_info(const struct pretty_print_context *pp,\n \t\t\t   struct strbuf *sb, const struct commit *commit)\n {\n@@ -526,20 +551,7 @@ static void add_merge_info(const struct pretty_print_context *pp,\n \t    !parent || !parent->next)\n \t\treturn;\n \n-\tstrbuf_addstr(sb, \"Merge:\");\n-\n-\twhile (parent) {\n-\t\tstruct commit *p = parent->item;\n-\t\tconst char *hex = NULL;\n-\t\tif (pp->abbrev)\n-\t\t\thex = find_unique_abbrev(p->object.sha1, pp->abbrev);\n-\t\tif (!hex)\n-\t\t\thex = sha1_to_hex(p->object.sha1);\n-\t\tparent = parent->next;\n-\n-\t\tstrbuf_addf(sb, \" %s\", hex);\n-\t}\n-\tstrbuf_addch(sb, '\\n');\n+\tpp_commit_list(pp, sb, \"Merge:\", parent);\n }\n \n static char *get_header(const struct commit *commit, const char *msg,\n-- \n1.9.rc2.232.gdd31389\n"},{"id":"234243","messageId":"b21890769d387293182b74f0b06e3e19ab9cfada.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[POC PATCH 5/9] log: add a merge base inspection option","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:34Z","receivedAt":"2014-02-04T22:17:34Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"With the new --bases, print merge commits' parents' merge bases.  This\nis mostly a proof of viability, in particular wrt. revision walk\ndecoupling and speed.\n\nWe can do \"inline\" get_merge_bases() (via get_octopus_merge_bases)\nbecause the walks in get_merge_bases() only use flag bits 16-19, and\nwe reset them after use.  The get_revision()/log display walk OTOH\nuses only flag bits 0-15 (actually only 0-10 as of this commit).\n\nSpeed-wise it turns out to be better than attempting to compute merge\nbases in one go, mostly because the latter approach would require\nextensive data structures to track flags.  This commit does not have\nto: the commit graph will be loaded anyway, and the room for flags is\nalready there.  As a big plus, this approach also works in a streaming\nfashion, showing the first few commits very quickly.\n\nSigned-off-by: Thomas Rast <tr@thomasrast.ch>\n---\n\nAs indicated in the cover letter, this is cute, but I'm not married to\nit.  It's probably just a useless instance of feature creep.\n\n Documentation/rev-list-options.txt |  7 +++++++\n log-tree.c                         |  3 +++\n pretty.c                           |  3 +++\n revision.c                         |  2 ++\n revision.h                         |  2 ++\n t/t4202-log.sh                     | 31 +++++++++++++++++++++++++++++++\n 6 files changed, 48 insertions(+)\n\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex 03533af..d023290 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -705,6 +705,13 @@ endif::git-rev-list[]\n \tPrint also the children of the commit (in the form \"commit child...\").\n \tAlso enables parent rewriting, see 'History Simplification' below.\n \n+ifndef::git-rev-list[]\n+--bases::\n+\tFor merge commits, print the merge bases of the commit's\n+\tparents.  (These are the bases that were used in the creation\n+\tof the merge itself.)\n+endif::git-rev-list[]\n+\n ifdef::git-rev-list[]\n --timestamp::\n \tPrint the raw commit timestamp.\ndiff --git a/log-tree.c b/log-tree.c\nindex 08970bf..080f412 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -622,6 +622,8 @@ void show_log(struct rev_info *opt)\n \tctx.output_encoding = get_log_output_encoding();\n \tif (opt->from_ident.mail_begin && opt->from_ident.name_begin)\n \t\tctx.from_ident = &opt->from_ident;\n+\tif (opt->show_merge_bases && commit->parents && commit->parents->next)\n+\t\tctx.merge_bases = get_octopus_merge_bases(commit->parents);\n \tpretty_print_commit(&ctx, commit, &msgbuf);\n \n \tif (opt->add_signoff)\n@@ -662,6 +664,7 @@ void show_log(struct rev_info *opt)\n \n \tstrbuf_release(&msgbuf);\n \tfree(ctx.notes_message);\n+\tfree(ctx.merge_bases);\n }\n \n int log_tree_diff_flush(struct rev_info *opt)\ndiff --git a/pretty.c b/pretty.c\nindex 5e44cf8..8b28664 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -552,6 +552,9 @@ static void add_merge_info(const struct pretty_print_context *pp,\n \t\treturn;\n \n \tpp_commit_list(pp, sb, \"Merge:\", parent);\n+\n+\tif (pp->merge_bases)\n+\t\tpp_commit_list(pp, sb, \"Bases:\", pp->merge_bases);\n }\n \n static char *get_header(const struct commit *commit, const char *msg,\ndiff --git a/revision.c b/revision.c\nindex a0df72f..72255fb 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1729,6 +1729,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t} else if (!strcmp(arg, \"--parents\")) {\n \t\trevs->rewrite_parents = 1;\n \t\trevs->print_parents = 1;\n+\t} else if (!strcmp(arg, \"--merge-bases\")) {\n+\t\trevs->show_merge_bases = 1;\n \t} else if (!strcmp(arg, \"--dense\")) {\n \t\trevs->dense = 1;\n \t} else if (!strcmp(arg, \"--sparse\")) {\ndiff --git a/revision.h b/revision.h\nindex 88967d6..3111228 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -19,6 +19,7 @@\n #define PATCHSAME\t(1u<<9)\n #define BOTTOM\t\t(1u<<10)\n #define ALL_REV_FLAGS\t((1u<<11)-1)\n+/* merge-base.c uses bits 16-19.  --merge-bases will break if they overlap! */\n \n #define DECORATE_SHORT_REFS\t1\n #define DECORATE_FULL_REFS\t2\n@@ -137,6 +138,7 @@ struct rev_info {\n \t\t\tpreserve_subject:1;\n \tunsigned int\tdisable_stdin:1;\n \tunsigned int\tleak_pending:1;\n+\tunsigned int\tshow_merge_bases:1;\n \n \tenum date_mode date_mode;\n \ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex cb03d28..64f34a6 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -841,4 +841,35 @@ test_expect_success 'dotdot is a parent directory' '\n \ttest_cmp expect actual\n '\n \n+shorten () {\n+\tfor arg; do\n+\t\tgit rev-parse --short \"$arg\"\n+\tdone\n+}\n+\n+fill_in_merge_bases () {\n+\twhile IFS= read line; do\n+\t\tcase \"$line\" in\n+\t\tMerge:*)\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\t\tprintf \"%s\" \"Bases:\"\n+\t\t\tprintf \" %s\" $(shorten \\\n+\t\t\t    $(git merge-base --all --octopus \\\n+\t\t\t\t${line##Merge:}))\n+\t\t\tprintf \"\\n\"\n+\t\t\t;;\n+\t\t*)\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\t\t;;\n+\t\tesac\n+\tdone\n+}\n+\n+test_expect_success '--merge-bases' '\n+\tgit log |\n+\tfill_in_merge_bases >expect\n+\tgit log --merge-bases >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n1.9.rc2.232.gdd31389\n"},{"id":"234246","messageId":"e91b1c369bc8c77fb0627f2c053977cd8bcf5897.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[PATCH 6/9] combine-diff: do not pass revs->dense_combined_merges redundantly","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:35Z","receivedAt":"2014-02-04T22:17:35Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"The existing code passed revs->dense_combined_merges along revs itself\ninto the combine-diff functions, which is rather redundant.  Remove\nthe 'dense' argument until much further down the callchain to simplify\ncallers.\n\nNote that while the caller in submodule.c needs to do extra work now,\nthe next commit will simplify this to a single setting again.\n\nSigned-off-by: Thomas Rast <tr@thomasrast.ch>\n---\n builtin/diff.c |  3 +--\n combine-diff.c | 13 ++++++-------\n diff-lib.c     |  6 ++----\n diff.h         |  6 +++---\n log-tree.c     |  2 +-\n submodule.c    |  5 ++++-\n 6 files changed, 17 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/diff.c b/builtin/diff.c\nindex 0f247d2..47f663b 100644\n--- a/builtin/diff.c\n+++ b/builtin/diff.c\n@@ -196,8 +196,7 @@ static int builtin_diff_combined(struct rev_info *revs,\n \t\trevs->dense_combined_merges = revs->combine_merges = 1;\n \tfor (i = 1; i < ents; i++)\n \t\tsha1_array_append(&parents, ent[i].item->sha1);\n-\tdiff_tree_combined(ent[0].item->sha1, &parents,\n-\t\t\t   revs->dense_combined_merges, revs);\n+\tdiff_tree_combined(ent[0].item->sha1, &parents, revs);\n \tsha1_array_clear(&parents);\n \treturn 0;\n }\ndiff --git a/combine-diff.c b/combine-diff.c\nindex 3b92c448..6e80a73 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -952,7 +952,7 @@ static void show_combined_header(struct combine_diff_path *elem,\n }\n \n static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n-\t\t\t    int dense, int working_tree_file,\n+\t\t\t    int working_tree_file,\n \t\t\t    struct rev_info *rev)\n {\n \tstruct diff_options *opt = &rev->diffopt;\n@@ -967,6 +967,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \tstruct userdiff_driver *textconv = NULL;\n \tint is_binary;\n \tconst char *line_prefix = diff_line_prefix(opt);\n+\tint dense = rev->dense_combined_merges;\n \n \tcontext = opt->context;\n \tuserdiff = userdiff_find_by_path(elem->path);\n@@ -1214,7 +1215,6 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct re\n  */\n void show_combined_diff(struct combine_diff_path *p,\n \t\t       int num_parent,\n-\t\t       int dense,\n \t\t       struct rev_info *rev)\n {\n \tstruct diff_options *opt = &rev->diffopt;\n@@ -1226,7 +1226,7 @@ void show_combined_diff(struct combine_diff_path *p,\n \t\t\t\t  DIFF_FORMAT_NAME_STATUS))\n \t\tshow_raw_diff(p, num_parent, rev);\n \telse if (opt->output_format & DIFF_FORMAT_PATCH)\n-\t\tshow_patch_diff(p, num_parent, dense, 1, rev);\n+\t\tshow_patch_diff(p, num_parent, 1, rev);\n }\n \n static void free_combined_pair(struct diff_filepair *pair)\n@@ -1297,7 +1297,6 @@ static void handle_combined_callback(struct diff_options *opt,\n \n void diff_tree_combined(const unsigned char *sha1,\n \t\t\tconst struct sha1_array *parents,\n-\t\t\tint dense,\n \t\t\tstruct rev_info *rev)\n {\n \tstruct diff_options *opt = &rev->diffopt;\n@@ -1365,7 +1364,7 @@ void diff_tree_combined(const unsigned char *sha1,\n \t\t\t\t       opt->line_termination);\n \t\t\tfor (p = paths; p; p = p->next) {\n \t\t\t\tif (p->len)\n-\t\t\t\t\tshow_patch_diff(p, num_parent, dense,\n+\t\t\t\t\tshow_patch_diff(p, num_parent,\n \t\t\t\t\t\t\t0, rev);\n \t\t\t}\n \t\t}\n@@ -1381,7 +1380,7 @@ void diff_tree_combined(const unsigned char *sha1,\n \tfree_pathspec(&diffopts.pathspec);\n }\n \n-void diff_tree_combined_merge(const struct commit *commit, int dense,\n+void diff_tree_combined_merge(const struct commit *commit,\n \t\t\t      struct rev_info *rev)\n {\n \tstruct commit_list *parent = get_saved_parents(rev, commit);\n@@ -1391,6 +1390,6 @@ void diff_tree_combined_merge(const struct commit *commit, int dense,\n \t\tsha1_array_append(&parents, parent->item->object.sha1);\n \t\tparent = parent->next;\n \t}\n-\tdiff_tree_combined(commit->object.sha1, &parents, dense, rev);\n+\tdiff_tree_combined(commit->object.sha1, &parents, rev);\n \tsha1_array_clear(&parents);\n }\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 346cac6..8d0f572 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -174,9 +174,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\ti--;\n \n \t\t\tif (revs->combine_merges && num_compare_stages == 2) {\n-\t\t\t\tshow_combined_diff(dpath, 2,\n-\t\t\t\t\t\t   revs->dense_combined_merges,\n-\t\t\t\t\t\t   revs);\n+\t\t\t\tshow_combined_diff(dpath, 2, revs);\n \t\t\t\tfree(dpath);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -338,7 +336,7 @@ static int show_modified(struct rev_info *revs,\n \t\tp->parent[1].status = DIFF_STATUS_MODIFIED;\n \t\tp->parent[1].mode = old->ce_mode;\n \t\thashcpy(p->parent[1].sha1, old->sha1);\n-\t\tshow_combined_diff(p, 2, revs->dense_combined_merges, revs);\n+\t\tshow_combined_diff(p, 2, revs);\n \t\tfree(p);\n \t\treturn 0;\n \t}\ndiff --git a/diff.h b/diff.h\nindex ce123fa..ff77802 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -213,11 +213,11 @@ struct combine_diff_path {\n \t sizeof(struct combine_diff_parent) * (n) + (l) + 1)\n \n extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,\n-\t\t\t      int dense, struct rev_info *);\n+\t\t\t       struct rev_info *);\n \n-extern void diff_tree_combined(const unsigned char *sha1, const struct sha1_array *parents, int dense, struct rev_info *rev);\n+extern void diff_tree_combined(const unsigned char *sha1, const struct sha1_array *parents, struct rev_info *rev);\n \n-extern void diff_tree_combined_merge(const struct commit *commit, int dense, struct rev_info *rev);\n+extern void diff_tree_combined_merge(const struct commit *commit, struct rev_info *rev);\n \n void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 080f412..2fcca45 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -721,7 +721,7 @@ int log_tree_diff_flush(struct rev_info *opt)\n \n static int do_diff_combined(struct rev_info *opt, struct commit *commit)\n {\n-\tdiff_tree_combined_merge(commit, opt->dense_combined_merges, opt);\n+\tdiff_tree_combined_merge(commit, opt);\n \treturn !opt->loginfo;\n }\n \ndiff --git a/submodule.c b/submodule.c\nindex 613857e..83b80fb 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -505,10 +505,13 @@ static void find_unpushed_submodule_commits(struct commit *commit,\n \tstruct rev_info rev;\n \n \tinit_revisions(&rev, NULL);\n+\trev.ignore_merges = 0;\n+\trev.combined_merges = 1;\n+\trev.dense_combined_merges = 1;\n \trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n \trev.diffopt.format_callback = collect_submodules_from_diff;\n \trev.diffopt.format_callback_data = needs_pushing;\n-\tdiff_tree_combined_merge(commit, 1, &rev);\n+\tdiff_tree_combined_merge(commit, &rev);\n }\n \n int find_unpushed_submodules(unsigned char new_sha1[20],\n-- \n1.9.rc2.232.gdd31389\n"},{"id":"234242","messageId":"00547f8d781b2635f3917554a9e65d16ba443780.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[PATCH 7/9] Fold all merge diff variants into an enum","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:36Z","receivedAt":"2014-02-04T22:17:36Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"The four ways of displaying merge diffs,\n\n* none: no diff\n* -m: against each parent\n* -c: combined\n* --cc: combined-condensed\n\nwere encoded in three flag bits in struct rev_info.  Fold them all\ninto a single enum field that captures the variants.\n\nThis makes it easier to add new merge diff variants without yet more\nspecial casing.  It should also be slightly easier to read because one\ndoes not have to ensure that the flag bits are set in an expected\ncombination.\n\nSigned-off-by: Thomas Rast <tr@thomasrast.ch>\n---\n builtin/diff-files.c    |  5 +++--\n builtin/diff-tree.c     |  2 +-\n builtin/diff.c          |  9 +++++----\n builtin/fmt-merge-msg.c |  2 +-\n builtin/log.c           |  9 ++++-----\n builtin/merge.c         |  1 -\n combine-diff.c          |  2 +-\n diff-lib.c              |  7 ++++---\n log-tree.c              |  4 ++--\n revision.c              | 13 +++----------\n revision.h              | 22 +++++++++++++++++++---\n submodule.c             |  4 +---\n 12 files changed, 44 insertions(+), 36 deletions(-)\n\ndiff --git a/builtin/diff-files.c b/builtin/diff-files.c\nindex 9200069..172b50d 100644\n--- a/builtin/diff-files.c\n+++ b/builtin/diff-files.c\n@@ -57,9 +57,10 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)\n \t * was not asked to.  \"diff-files -c -p\" should not densify\n \t * (the user should ask with \"diff-files --cc\" explicitly).\n \t */\n-\tif (rev.max_count == -1 && !rev.combine_merges &&\n+\tif (rev.max_count == -1 &&\n+\t    !merge_diff_mode_is_any_combined(&rev) &&\n \t    (rev.diffopt.output_format & DIFF_FORMAT_PATCH))\n-\t\trev.combine_merges = rev.dense_combined_merges = 1;\n+\t\trev.merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;\n \n \tif (read_cache_preload(&rev.diffopt.pathspec) < 0) {\n \t\tperror(\"read_cache_preload\");\ndiff --git a/builtin/diff-tree.c b/builtin/diff-tree.c\nindex be6417d..2950f80 100644\n--- a/builtin/diff-tree.c\n+++ b/builtin/diff-tree.c\n@@ -96,7 +96,7 @@ static int diff_tree_stdin(char *line)\n static void diff_tree_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)\n {\n \tif (!rev->diffopt.output_format) {\n-\t\tif (rev->dense_combined_merges)\n+\t\tif (rev->merge_diff_mode == MERGE_DIFF_COMBINED_CONDENSED)\n \t\t\trev->diffopt.output_format = DIFF_FORMAT_PATCH;\n \t\telse\n \t\t\trev->diffopt.output_format = DIFF_FORMAT_RAW;\ndiff --git a/builtin/diff.c b/builtin/diff.c\nindex 47f663b..fd4c75f 100644\n--- a/builtin/diff.c\n+++ b/builtin/diff.c\n@@ -192,8 +192,8 @@ static int builtin_diff_combined(struct rev_info *revs,\n \tif (argc > 1)\n \t\tusage(builtin_diff_usage);\n \n-\tif (!revs->dense_combined_merges && !revs->combine_merges)\n-\t\trevs->dense_combined_merges = revs->combine_merges = 1;\n+\tif (!merge_diff_mode_is_any_combined(revs))\n+\t\trevs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;\n \tfor (i = 1; i < ents; i++)\n \t\tsha1_array_append(&parents, ent[i].item->sha1);\n \tdiff_tree_combined(ent[0].item->sha1, &parents, revs);\n@@ -242,9 +242,10 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv\n \t * dense one, --cc can be explicitly asked for, or just rely\n \t * on the default).\n \t */\n-\tif (revs->max_count == -1 && !revs->combine_merges &&\n+\tif (revs->max_count == -1 &&\n+\t    !merge_diff_mode_is_any_combined(revs) &&\n \t    (revs->diffopt.output_format & DIFF_FORMAT_PATCH))\n-\t\trevs->combine_merges = revs->dense_combined_merges = 1;\n+\t\trevs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;\n \n \tsetup_work_tree();\n \tif (read_cache_preload(&revs->diffopt.pathspec) < 0) {\ndiff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c\nindex 3906eda..2deeacd 100644\n--- a/builtin/fmt-merge-msg.c\n+++ b/builtin/fmt-merge-msg.c\n@@ -637,7 +637,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\thead = lookup_commit_or_die(head_sha1, \"HEAD\");\n \t\tinit_revisions(&rev, NULL);\n \t\trev.commit_format = CMIT_FMT_ONELINE;\n-\t\trev.ignore_merges = 1;\n+\t\trev.merge_diff_mode = MERGE_DIFF_IGNORE;\n \t\trev.limited = 1;\n \n \t\tstrbuf_complete_line(out);\ndiff --git a/builtin/log.c b/builtin/log.c\nindex b97373d..cebebea 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -499,13 +499,12 @@ static int show_tree_object(const unsigned char *sha1,\n \n static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)\n {\n-\tif (rev->ignore_merges) {\n+\tif (!rev->merge_diff_mode) {\n \t\t/* There was no \"-m\" on the command line */\n-\t\trev->ignore_merges = 0;\n-\t\tif (!rev->first_parent_only && !rev->combine_merges) {\n+\t\trev->merge_diff_mode = MERGE_DIFF_EACH;\n+\t\tif (!rev->first_parent_only) {\n \t\t\t/* No \"--first-parent\", \"-c\", nor \"--cc\" */\n-\t\t\trev->combine_merges = 1;\n-\t\t\trev->dense_combined_merges = 1;\n+\t\t\trev->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;\n \t\t}\n \t}\n \tif (!rev->diffopt.output_format)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e576a7f..6977af7 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -343,7 +343,6 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead\n \t\tdie_errno(_(\"Could not write to '%s'\"), filename);\n \n \tinit_revisions(&rev, NULL);\n-\trev.ignore_merges = 1;\n \trev.commit_format = CMIT_FMT_MEDIUM;\n \n \tcommit->object.flags |= UNINTERESTING;\ndiff --git a/combine-diff.c b/combine-diff.c\nindex 6e80a73..3fae2dd 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -967,7 +967,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \tstruct userdiff_driver *textconv = NULL;\n \tint is_binary;\n \tconst char *line_prefix = diff_line_prefix(opt);\n-\tint dense = rev->dense_combined_merges;\n+\tint dense = (rev->merge_diff_mode == MERGE_DIFF_COMBINED_CONDENSED);\n \n \tcontext = opt->context;\n \tuserdiff = userdiff_find_by_path(elem->path);\ndiff --git a/diff-lib.c b/diff-lib.c\nindex 8d0f572..e2700eb 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -173,7 +173,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t */\n \t\t\ti--;\n \n-\t\t\tif (revs->combine_merges && num_compare_stages == 2) {\n+\t\t\tif (merge_diff_mode_is_any_combined(revs) &&\n+\t\t\t    num_compare_stages == 2) {\n \t\t\t\tshow_combined_diff(dpath, 2, revs);\n \t\t\t\tfree(dpath);\n \t\t\t\tcontinue;\n@@ -316,7 +317,7 @@ static int show_modified(struct rev_info *revs,\n \t\treturn -1;\n \t}\n \n-\tif (revs->combine_merges && !cached &&\n+\tif (merge_diff_mode_is_any_combined(revs) && !cached &&\n \t    (hashcmp(sha1, old->sha1) || hashcmp(old->sha1, new->sha1))) {\n \t\tstruct combine_diff_path *p;\n \t\tint pathlen = ce_namelen(new);\n@@ -375,7 +376,7 @@ static void do_oneway_diff(struct unpack_trees_options *o,\n \t * But with the revision flag parsing, that's found in\n \t * \"!revs->ignore_merges\".\n \t */\n-\tmatch_missing = !revs->ignore_merges;\n+\tmatch_missing = (revs->merge_diff_mode == MERGE_DIFF_EACH);\n \n \tif (cached && idx && ce_stage(idx)) {\n \t\tstruct diff_filepair *pair;\ndiff --git a/log-tree.c b/log-tree.c\nindex 2fcca45..4ab3ffe 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -754,9 +754,9 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log\n \n \t/* More than one parent? */\n \tif (parents && parents->next) {\n-\t\tif (opt->ignore_merges)\n+\t\tif (opt->merge_diff_mode == MERGE_DIFF_IGNORE)\n \t\t\treturn 0;\n-\t\telse if (opt->combine_merges)\n+\t\telse if (merge_diff_mode_is_any_combined(opt))\n \t\t\treturn do_diff_combined(opt, commit);\n \t\telse if (opt->first_parent_only) {\n \t\t\t/*\ndiff --git a/revision.c b/revision.c\nindex 72255fb..3a1a810 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1329,7 +1329,6 @@ void init_revisions(struct rev_info *revs, const char *prefix)\n \tmemset(revs, 0, sizeof(*revs));\n \n \trevs->abbrev = DEFAULT_ABBREV;\n-\trevs->ignore_merges = 1;\n \trevs->simplify_history = 1;\n \tDIFF_OPT_SET(&revs->pruning, RECURSIVE);\n \tDIFF_OPT_SET(&revs->pruning, QUICK);\n@@ -1809,15 +1808,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\tDIFF_OPT_SET(&revs->diffopt, RECURSIVE);\n \t\tDIFF_OPT_SET(&revs->diffopt, TREE_IN_RECURSIVE);\n \t} else if (!strcmp(arg, \"-m\")) {\n-\t\trevs->ignore_merges = 0;\n+\t\trevs->merge_diff_mode = MERGE_DIFF_EACH;\n \t} else if (!strcmp(arg, \"-c\")) {\n-\t\trevs->diff = 1;\n-\t\trevs->dense_combined_merges = 0;\n-\t\trevs->combine_merges = 1;\n+\t\trevs->merge_diff_mode = MERGE_DIFF_COMBINED;\n \t} else if (!strcmp(arg, \"--cc\")) {\n-\t\trevs->diff = 1;\n-\t\trevs->dense_combined_merges = 1;\n-\t\trevs->combine_merges = 1;\n+\t\trevs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;\n \t} else if (!strcmp(arg, \"-v\")) {\n \t\trevs->verbose_header = 1;\n \t} else if (!strcmp(arg, \"--pretty\")) {\n@@ -2230,8 +2225,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\t\tcopy_pathspec(&revs->diffopt.pathspec,\n \t\t\t\t      &revs->prune_data);\n \t}\n-\tif (revs->combine_merges)\n-\t\trevs->ignore_merges = 0;\n \trevs->diffopt.abbrev = revs->abbrev;\n \n \tif (revs->line_level_traverse) {\ndiff --git a/revision.h b/revision.h\nindex 3111228..2ec596f 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -51,6 +51,17 @@ struct rev_cmdline_info {\n #define REVISION_WALK_NO_WALK_SORTED 1\n #define REVISION_WALK_NO_WALK_UNSORTED 2\n \n+enum merge_diff_mode {\n+\t/* default: do not show diffs for merge */\n+\tMERGE_DIFF_IGNORE = 0,\n+\t/* diff against each side (-m) */\n+\tMERGE_DIFF_EACH,\n+\t/* combined format (-c) */\n+\tMERGE_DIFF_COMBINED,\n+\t/* combined-condensed format (-cc) */\n+\tMERGE_DIFF_COMBINED_CONDENSED\n+};\n+\n struct rev_info {\n \t/* Starting list */\n \tstruct commit_list *commits;\n@@ -117,11 +128,10 @@ struct rev_info {\n \t\t\tshow_root_diff:1,\n \t\t\tno_commit_id:1,\n \t\t\tverbose_header:1,\n-\t\t\tignore_merges:1,\n-\t\t\tcombine_merges:1,\n-\t\t\tdense_combined_merges:1,\n \t\t\talways_show_header:1;\n \n+\tenum merge_diff_mode merge_diff_mode;\n+\n \t/* Format info */\n \tunsigned int\tshown_one:1,\n \t\t\tshown_dashes:1,\n@@ -199,6 +209,12 @@ struct rev_info {\n \tstruct saved_parents *saved_parents_slab;\n };\n \n+static inline int merge_diff_mode_is_any_combined(struct rev_info *revs)\n+{\n+\treturn (revs->merge_diff_mode == MERGE_DIFF_COMBINED ||\n+\t\trevs->merge_diff_mode == MERGE_DIFF_COMBINED_CONDENSED);\n+}\n+\n extern int ref_excluded(struct string_list *, const char *path);\n void clear_ref_exclusion(struct string_list **);\n void add_ref_exclusion(struct string_list **, const char *exclude);\ndiff --git a/submodule.c b/submodule.c\nindex 83b80fb..38973f2 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -505,9 +505,7 @@ static void find_unpushed_submodule_commits(struct commit *commit,\n \tstruct rev_info rev;\n \n \tinit_revisions(&rev, NULL);\n-\trev.ignore_merges = 0;\n-\trev.combined_merges = 1;\n-\trev.dense_combined_merges = 1;\n+\trev.merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;\n \trev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;\n \trev.diffopt.format_callback = collect_submodules_from_diff;\n \trev.diffopt.format_callback_data = needs_pushing;\n-- \n1.9.rc2.232.gdd31389\n"},{"id":"234248","messageId":"adf5e5bd91a6441f0fea7412a6507ac5fd9f9a6f.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[PATCH 8/9] merge-recursive: allow storing conflict hunks in index","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:37Z","receivedAt":"2014-02-04T22:17:37Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Add a --conflicts-in-index option to merge-recursive, which instructs\nit to always store the 3-way merged result in the index.  (Normally it\nonly does so in recursive invocations, but not for the final result.)\n\nThis serves as a building block for the \"remerge diff\" feature coming\nup in a subsequent patch.  The external option lets us easily use it\nfrom tests, where we'd otherwise need a new test-* helper to access\nthe feature.\n\nFurthermore, it might occasionally be useful for scripts that want to\nlook at the result of invoking git-merge without tampering with the\nworktree.  They could already get the _conflicts_ with --index-only,\nbut not (conveniently) the conflict-hunk formatted files that would\nnormally be written to the worktree.\n\nSigned-off-by: Thomas Rast <tr@thomasrast.ch>\n---\n Documentation/merge-strategies.txt |  5 +++++\n merge-recursive.c                  |  4 ++++\n merge-recursive.h                  |  1 +\n t/t3030-merge-recursive.sh         | 20 ++++++++++++++++++++\n 4 files changed, 30 insertions(+)\n\ndiff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt\nindex 2934e99..3468d99 100644\n--- a/Documentation/merge-strategies.txt\n+++ b/Documentation/merge-strategies.txt\n@@ -96,6 +96,11 @@ index-only;;\n \tWrite the merge result only to the index; do not touch the\n \tworktree.\n \n+conflicts-in-index;;\n+\tFor conflicted files, write 3-way merged contents with\n+\tconflict hunks to the index, instead of leaving their entries\n+\tunresolved.\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 f59c1d3..b682812 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -724,6 +724,8 @@ 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->conflicts_in_index)\n+\t\tupdate_cache = 1;\n \tif (o->call_depth || o->no_worktree)\n \t\tupdate_wd = 0;\n \n@@ -2098,6 +2100,8 @@ int parse_merge_opt(struct merge_options *o, const char *s)\n \t}\n \telse if (!strcmp(s, \"index-only\"))\n \t\to->no_worktree = 1;\n+\telse if (!strcmp(s, \"conflicts-in-index\"))\n+\t\to->conflicts_in_index = 1;\n \telse\n \t\treturn -1;\n \treturn 0;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex d8dd7a1..9b8e20b 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -16,6 +16,7 @@ struct merge_options {\n \tunsigned buffer_output : 1;\n \tunsigned renormalize : 1;\n \tunsigned no_worktree : 1; /* do not touch worktree */\n+\tunsigned conflicts_in_index : 1; /* index will contain conflict hunks */\n \tlong xdl_opts;\n \tint verbosity;\n \tint diff_rename_limit;\ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex 2f3a16c..4192fd3 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -309,6 +309,26 @@ test_expect_success 'merge-recursive --index-only' '\n \ttest_cmp expected-diff actual-diff\n '\n \n+test_expect_success 'merge-recursive --index-only --conflicts-in-index' '\n+\t# first pass: do a merge as usual to obtain \"expected\"\n+\trm -fr [abcd] &&\n+\tgit checkout -f \"$c2\" &&\n+\ttest_expect_code 1 git merge-recursive \"$c0\" -- \"$c2\" \"$c1\" &&\n+\tgit add [abcd] &&\n+\tgit ls-files -s >expected &&\n+\t# second pass: actual test\n+\trm -fr [abcd] &&\n+\tgit checkout -f \"$c2\" &&\n+\ttest_expect_code 1 \\\n+\t\tgit merge-recursive --index-only --conflicts-in-index \\\n+\t\t\"$c0\" -- \"$c2\" \"$c1\" &&\n+\tgit ls-files -s >actual &&\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.9.rc2.232.gdd31389\n"},{"id":"234245","messageId":"1f2585f382f382717bd3cd5d1eafead149fc1013.1391549294.git.tr@thomasrast.ch","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"[RFC PATCH 9/9] log --remerge-diff: show what the conflict resolution changed","fromName":"Thomas Rast","fromEmail":"tr@thomasrast.ch","sentAt":"2014-02-04T22:17:38Z","receivedAt":"2014-02-04T22:17:38Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Git has --cc as a very fast inspection tool that shows a brief summary\nof what a conflicted merge \"looks like\", and -c/-m as \"give me the\nfull information\" data dumps.\n\nBut --cc actually loses information: if the merge lost(!) some changes\nfrom one side, that hunk would fully agree with the other side, and\ntherefore be elided.  So --cc cannot be used to investigate mismerges.\nIndeed it is rather hard to find a merge that has lost changes, unless\none knows where to look.\n\nThe new option --remerge-diff is an attempt at filling this gap,\nadmittedly at the cost of a lot of CPU cycles.  For each merge commit,\nit diffs the merge result against a recursive merge of the merge's\nparents.\n\nFor files that can be auto-merged cleanly, it will typically show\nnothing.  However, it will make it obvious when the merge introduces\nextra changes.\n\nFor files that result in merge conflicts, we diff against the\nrepresentation with conflict hunks (what the user would usually see in\nthe worktree).  So the diff will show what was changed in the conflict\nhunks to resolve the conflict.\n\nIt still takes a bit of staring to tell an evil from a regular merge.\nBut at least the information is there, unlike with --cc; and the\noutput is usually much shorter than with -c.\n\nSigned-off-by: Thomas Rast <tr@thomasrast.ch>\n---\n Documentation/rev-list-options.txt |   7 ++\n log-tree.c                         |  60 +++++++++++++++\n merge-recursive.c                  |   3 +-\n merge-recursive.h                  |   1 +\n revision.c                         |   2 +\n revision.h                         |   4 +-\n t/t4202-log.sh                     |   3 +\n t/t4213-log-remerge-diff.sh        | 145 +++++++++++++++++++++++++++++++++++++\n 8 files changed, 223 insertions(+), 2 deletions(-)\n create mode 100755 t/t4213-log-remerge-diff.sh\n\ndiff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt\nindex d023290..6611557 100644\n--- a/Documentation/rev-list-options.txt\n+++ b/Documentation/rev-list-options.txt\n@@ -797,6 +797,13 @@ options may be given. See linkgit:git-diff-files[1] for more options.\n \tin that case, the output represents the changes the merge\n \tbrought _into_ the then-current branch.\n \n+--remerge-diff::\n+\tDiff merge commits against a recursive merge of their parents,\n+\twith conflict hunks.  Intuitively speaking, this shows what\n+\tthe author of the merge changed to resolve the merge.  It\n+\tassumes that all (or most) merges are recursive merges; other\n+\tstrategies are not supported.\n+\n -r::\n \tShow recursive diffs.\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 4ab3ffe..fa22737 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -11,6 +11,8 @@\n #include \"gpg-interface.h\"\n #include \"sequencer.h\"\n #include \"line-log.h\"\n+#include \"cache-tree.h\"\n+#include \"merge-recursive.h\"\n \n struct decoration name_decoration = { \"object names\" };\n \n@@ -725,6 +727,62 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit)\n \treturn !opt->loginfo;\n }\n \n+static int do_diff_remerge(struct rev_info *opt, struct commit *commit)\n+{\n+\tstruct commit_list *merge_bases;\n+\tstruct commit *result, *parent1, *parent2;\n+\tstruct merge_options o;\n+\tchar *branch1, *branch2;\n+\n+\tif (commit->parents->next->next) {\n+\t\tprintf(\"--remerge-diff not supported for octopus merges.\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\tparent1 = commit->parents->item;\n+\tparent2 = commit->parents->next->item;\n+\tparse_commit(parent1);\n+\tparse_commit(parent2);\n+\tbranch1 = xstrdup(sha1_to_hex(parent1->object.sha1));\n+\tbranch2 = xstrdup(sha1_to_hex(parent2->object.sha1));\n+\n+\tmerge_bases = get_octopus_merge_bases(commit->parents);\n+\tinit_merge_options(&o);\n+\to.verbosity = -1;\n+\to.no_worktree = 1;\n+\to.conflicts_in_index = 1;\n+\to.use_ondisk_index = 0;\n+\to.branch1 = branch1;\n+\to.branch2 = branch2;\n+\tmerge_recursive(&o, parent1, parent2, merge_bases, &result);\n+\tfree(branch1);\n+\tfree(branch2);\n+\n+\tactive_cache_tree = cache_tree();\n+\tif (cache_tree_update(active_cache_tree,\n+\t\t\t      (const struct cache_entry * const *)active_cache,\n+\t\t\t      active_nr, WRITE_TREE_SILENT) < 0) {\n+\t\tprintf(\"BUG: merge conflicts not fully folded, cannot diff.\\n\");\n+\t\treturn 0;\n+\t}\n+\n+\tif (opt->loginfo && !opt->no_commit_id) {\n+\t\tshow_log(opt);\n+\n+\t\tif (opt->verbose_header && opt->diffopt.output_format)\n+\t\t\tprintf(\"%s%c\", diff_line_prefix(&opt->diffopt),\n+\t\t\t       opt->diffopt.line_termination);\n+\t}\n+\n+\tdiff_tree_sha1(active_cache_tree->sha1, commit->tree->object.sha1,\n+\t\t       \"\", &opt->diffopt);\n+\tlog_tree_diff_flush(opt);\n+\n+\tcache_tree_free(&active_cache_tree);\n+\n+\treturn !opt->loginfo;\n+}\n+\n /*\n  * Show the diff of a commit.\n  *\n@@ -758,6 +816,8 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log\n \t\t\treturn 0;\n \t\telse if (merge_diff_mode_is_any_combined(opt))\n \t\t\treturn do_diff_combined(opt, commit);\n+\t\telse if (opt->merge_diff_mode == MERGE_DIFF_REMERGE)\n+\t\t\treturn do_diff_remerge(opt, commit);\n \t\telse if (opt->first_parent_only) {\n \t\t\t/*\n \t\t\t * Generate merge log entry only for the first\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b682812..1507a7a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1947,7 +1947,7 @@ int merge_recursive(struct merge_options *o,\n \t}\n \n \tdiscard_cache();\n-\tif (!o->call_depth)\n+\tif (!o->call_depth && o->use_ondisk_index)\n \t\tread_cache();\n \n \to->ancestor = \"merged common ancestors\";\n@@ -2043,6 +2043,7 @@ void init_merge_options(struct merge_options *o)\n \to->diff_rename_limit = -1;\n \to->merge_rename_limit = -1;\n \to->renormalize = 0;\n+\to->use_ondisk_index = 1;\n \tgit_config(merge_recursive_config, o);\n \tif (getenv(\"GIT_MERGE_VERBOSITY\"))\n \t\to->verbosity =\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 9b8e20b..d7466c7 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -17,6 +17,7 @@ struct merge_options {\n \tunsigned renormalize : 1;\n \tunsigned no_worktree : 1; /* do not touch worktree */\n \tunsigned conflicts_in_index : 1; /* index will contain conflict hunks */\n+\tunsigned use_ondisk_index : 1; /* tree-level merge loads .git/index */\n \tlong xdl_opts;\n \tint verbosity;\n \tint diff_rename_limit;\ndiff --git a/revision.c b/revision.c\nindex 3a1a810..bfdf91d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1813,6 +1813,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->merge_diff_mode = MERGE_DIFF_COMBINED;\n \t} else if (!strcmp(arg, \"--cc\")) {\n \t\trevs->merge_diff_mode = MERGE_DIFF_COMBINED_CONDENSED;\n+\t} else if (!strcmp(arg, \"--remerge-diff\")) {\n+\t\trevs->merge_diff_mode = MERGE_DIFF_REMERGE;\n \t} else if (!strcmp(arg, \"-v\")) {\n \t\trevs->verbose_header = 1;\n \t} else if (!strcmp(arg, \"--pretty\")) {\ndiff --git a/revision.h b/revision.h\nindex 2ec596f..419bbd9 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -59,7 +59,9 @@ enum merge_diff_mode {\n \t/* combined format (-c) */\n \tMERGE_DIFF_COMBINED,\n \t/* combined-condensed format (-cc) */\n-\tMERGE_DIFF_COMBINED_CONDENSED\n+\tMERGE_DIFF_COMBINED_CONDENSED,\n+\t/* --remerge-diff */\n+\tMERGE_DIFF_REMERGE\n };\n \n struct rev_info {\ndiff --git a/t/t4202-log.sh b/t/t4202-log.sh\nindex 64f34a6..bd3f536 100755\n--- a/t/t4202-log.sh\n+++ b/t/t4202-log.sh\n@@ -872,4 +872,7 @@ test_expect_success '--merge-bases' '\n \ttest_cmp expect actual\n '\n \n+cat >expect <<\\EOF\n+EOF\n+\n test_done\ndiff --git a/t/t4213-log-remerge-diff.sh b/t/t4213-log-remerge-diff.sh\nnew file mode 100755\nindex 0000000..ca6bbde\n--- /dev/null\n+++ b/t/t4213-log-remerge-diff.sh\n@@ -0,0 +1,145 @@\n+#!/bin/sh\n+\n+test_description='test log --remerge-diff'\n+. ./test-lib.sh\n+\n+# A ----------\n+# | \\  \\      \\\n+# |  C  \\      \\\n+# B  |\\  \\      |\n+# |  | |  D    unrelated_file\n+# |\\ | |__|__   |\n+# | X  |_ |  \\  |\n+# |/ \\/  \\|   \\ |\n+# M1 M2   M3   M4\n+# ^  ^    ^     ^\n+# |  |    dm    unrelated\n+# |  evil\n+# benign\n+#\n+#\n+# M1 has a \"benign\" conflict\n+# M2 has an \"evil\" conflict: it ignores the changes in D\n+# M3 has a delete/modify conflict, resolved in favor of a modification\n+# M4 is a merge of an unrelated change, without conflicts\n+\n+test_expect_success 'setup' '\n+\ttest_commit A file original &&\n+\ttest_commit B file change &&\n+\tgit checkout -b side A &&\n+\ttest_commit C file side &&\n+\tgit checkout -b delete A &&\n+\tgit rm file &&\n+\ttest_commit D &&\n+\tgit checkout -b benign master &&\n+\ttest_must_fail git merge C &&\n+\ttest_commit M1 file merged &&\n+\tgit checkout -b evil B &&\n+\ttest_must_fail git merge C &&\n+\ttest_commit M2 file change &&\n+\tgit checkout -b dm C &&\n+\ttest_must_fail git merge D &&\n+\ttest_commit M3 file resolved &&\n+\tgit checkout -b unrelated A &&\n+\ttest_commit unrelated_file &&\n+\tgit merge C &&\n+\ttest_tick &&\n+\tgit tag M4 &&\n+\tgit branch -D master side\n+'\n+\n+test_expect_success 'unrelated merge: without conflicts' '\n+\tgit log -p --cc unrelated >expected &&\n+\tgit log -p --remerge-diff unrelated >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+clean_output () {\n+\tgit name-rev --name-only --stdin |\n+\t# strip away bits that aren't treated by the above\n+\tsed -e 's/^\\(index\\|Merge:\\|Date:\\).*/\\1/'\n+}\n+\n+cat >expected <<EOF\n+commit benign\n+Merge:\n+Author: A U Thor <author@example.com>\n+Date:\n+\n+    M1\n+\n+diff --git a/file b/file\n+index\n+--- a/file\n++++ b/file\n+@@ -1,5 +1 @@\n+-<<<<<<< tags/B\n+-change\n+-=======\n+-side\n+->>>>>>> tags/C\n++merged\n+EOF\n+\n+test_expect_success 'benign merge: conflicts resolved' '\n+\tgit log -1 -p --remerge-diff benign >output &&\n+\tclean_output <output >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF\n+commit evil\n+Merge:\n+Author: A U Thor <author@example.com>\n+Date:\n+\n+    M2\n+\n+diff --git a/file b/file\n+index\n+--- a/file\n++++ b/file\n+@@ -1,5 +1 @@\n+-<<<<<<< tags/B\n+ change\n+-=======\n+-side\n+->>>>>>> tags/C\n+EOF\n+\n+test_expect_success 'evil merge: changes ignored' '\n+\tgit log -1 --remerge-diff -p evil >output &&\n+\tclean_output <output >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+cat >expected <<EOF\n+commit dm\n+Merge:\n+Author: A U Thor <author@example.com>\n+Date:\n+\n+    M3\n+\n+diff --git a/file b/file\n+index\n+--- a/file\n++++ b/file\n+@@ -1,5 +1 @@\n+-<<<<<<< tags/B\n+ change\n+-=======\n+->>>>>>> tags/D\n++resolved\n+EOF\n+\n+# The above just one idea what the output might be.  It's not clear\n+# yet what the best solution is.\n+\n+test_expect_failure 'delete/modify conflict' '\n+\tgit log -1 --remerge-diff -p dm >output &&\n+\tclean_output <output >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.9.rc2.232.gdd31389\n"},{"id":"234260","messageId":"xmqq1tzi7a4c.fsf@gitster.dls.corp.google.com","threadId":"35800","inReplyTo":"cover.1391549294.git.tr@thomasrast.ch","subject":"Re: [PATCH 0/9] remerge diff proof of concept/RFC","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-02-04T23:04:35Z","receivedAt":"2014-02-04T23:04:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <tr@thomasrast.ch> writes:\n\n>   log --remerge-diff: show what the conflict resolution changed\n\nYay ;-)\n\n> This series explores another angle, which I call \"remerge diff\".  It\n> works by re-doing the merge in core, using features from patches 1-3\n> and 7.  Likely that will result in conflicts, which are formatted in\n> the usual <<<<<<< way.  Then it diffs this \"remerge\" against the\n> merge's tree that is recorded in history.\n\nYup, that is the obvious way to recreate what would have been shown\nto the person who recorded the merge result.  I like that approach.\n\n> So I would welcome comments, and/or better ideas, on the following\n> proposed resolution:\n>\n> * Implement what I described last, to take care of delete/modify\n>   conflicts.\n\nNaively, I would have thought that\n\n - If the recorded result of the merge does not have the path, then\n   show the deletion of the contents relative to the side that\n   modified it.\n\n - If the recorded result of the merge has the path, then show the\n   change between the side that modified it and the recorded result.\n\nmight be more useful.  Then we will know what we lost in full (if\nthe recorded result is to \"delete\" it), but it is very likely that\nwe won't see anything if the recorded result blindly took what the\nmodifying side left.  Comparing with the synthetic stages 2+3 will\nat least show us there was something funky going on, so your\napproach would be reasonable in this case.\n\n> * Punt on more complex conflicts, by removing those files from the\n>   index, and emitting a warning about those files instead.\n\nSensible.\n"}]}