{"thread":{"id":"3136","subject":"[PATCH] diff-tree -c: show a merge commit a bit more sensibly.","startedAt":"2006-01-24T09:34:21Z","lastAt":"2006-01-25T12:35:22Z","messageCount":4,"participants":["Junio C Hamano","Linus Torvalds","Florian Weimer"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"15083","messageId":"7vwtgqas0y.fsf@assigned-by-dhcp.cox.net","threadId":"3136","inReplyTo":null,"subject":"[PATCH] diff-tree -c: show a merge commit a bit more sensibly.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-01-24T09:34:21Z","receivedAt":"2006-01-24T09:34:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"A new option '-c' to diff-tree changes the way a merge commit is\ndisplayed when generating a patch output.  It shows a \"combined\ndiff\" (hence the option letter 'c'), which looks like this:\n\n    $ git-diff-tree --pretty -c -p fec9ebf1 | head -n 18\n    diff-tree fec9ebf... (from parents)\n    Merge: 0620db3... 8a263ae...\n    Author: Junio C Hamano <junkio@cox.net>\n    Date:   Sun Jan 15 22:25:35 2006 -0800\n\n\tMerge fixes up to GIT 1.1.3\n\n    diff --combined describe.c\n    @@ +99,18 @@\n       }\n\n    -  static void describe(char *arg)\n     - static void describe(struct commit *cmit, int last_one)\n    ++ static void describe(char *arg, int last_one)\n       {\n     +      unsigned char sha1[20];\n     +      struct commit *cmit;\n\t    struct commit_list *list;\n\nUnlike \"gitk\", this is monochrome output.  A '-' character in\nthe nth column means the line is from the nth parent and does\nnot appear in the merge result.  A '+' character in the nth\ncolumn means the line appears in the merge result, and the nth\nparent does not have that line.  The above example shows that the\nfunction signature was changed from either parents (hence two\n'-' lines and a '++' line), and \"unsigned char sha1[20]\",\nprefixed by a \" +\", was inherited from the first parent.\n\nThe code probably is still buggy with rough edges, but with my\nlimited tests, it did not seem to dump core ;-).\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n\n---\n\n Makefile       |    2 \n combine-diff.c |  436 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n diff-tree.c    |   34 ++++\n diff.h         |    2 \n 4 files changed, 467 insertions(+), 7 deletions(-)\n create mode 100644 combine-diff.c\n\nb80f9de155bfc183839551e19e767859d689b593\ndiff --git a/Makefile b/Makefile\nindex 3046056..552b20f 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -182,7 +182,7 @@ LIB_H = \\\n \n DIFF_OBJS = \\\n \tdiff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \\\n-\tdiffcore-pickaxe.o diffcore-rename.o tree-diff.o\n+\tdiffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o\n \n LIB_OBJS = \\\n \tblob.o commit.o connect.o count-delta.o csum-file.o \\\ndiff --git a/combine-diff.c b/combine-diff.c\nnew file mode 100644\nindex 0000000..b47eda5\n--- /dev/null\n+++ b/combine-diff.c\n@@ -0,0 +1,436 @@\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"diffcore.h\"\n+#include \"quote.h\"\n+\n+struct path_list {\n+\tstruct path_list *next;\n+\tint len;\n+\tchar *path;\n+\tunsigned char sha1[20];\n+\tunsigned char parent_sha1[FLEX_ARRAY][20];\n+};\n+\n+static int uninteresting(struct diff_filepair *p)\n+{\n+\tif (diff_unmodified_pair(p))\n+\t\treturn 1;\n+\tif (!S_ISREG(p->one->mode) || !S_ISREG(p->two->mode))\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static struct path_list *intersect_paths(struct path_list *curr,\n+\t\t\t\t\t int n, int num_parent)\n+{\n+\tstruct diff_queue_struct *q = &diff_queued_diff;\n+\tstruct path_list *p;\n+\tint i;\n+\n+\tif (!curr) {\n+\t\tstruct path_list *list = NULL, *tail = NULL;\n+\t\tfor (i = 0; i < q->nr; i++) {\n+\t\t\tint len;\n+\t\t\tconst char *path;\n+\t\t\tif (uninteresting(q->queue[i]))\n+\t\t\t\tcontinue;\n+\t\t\tpath = q->queue[i]->two->path;\n+\t\t\tlen = strlen(path);\n+\n+\t\t\tp = xmalloc(sizeof(*p) + len + 1 + num_parent * 20);\n+\t\t\tp->path = (char*) &(p->parent_sha1[num_parent][0]);\n+\t\t\tmemcpy(p->path, path, len);\n+\t\t\tp->path[len] = 0;\n+\t\t\tp->len = len;\n+\t\t\tp->next = NULL;\n+\t\t\tmemcpy(p->sha1, q->queue[i]->two->sha1, 20);\n+\t\t\tmemcpy(p->parent_sha1[n], q->queue[i]->one->sha1, 20);\n+\t\t\tif (!tail)\n+\t\t\t\tlist = tail = p;\n+\t\t\telse {\n+\t\t\t\ttail->next = p;\n+\t\t\t\tp = tail;\n+\t\t\t}\n+\t\t}\n+\t\treturn list;\n+\t}\n+\n+\tfor (p = curr; p; p = p->next) {\n+\t\tint found = 0;\n+\t\tif (!p->len)\n+\t\t\tcontinue;\n+\t\tfor (i = 0; i < q->nr; i++) {\n+\t\t\tconst char *path;\n+\t\t\tint len;\n+\n+\t\t\tif (uninteresting(q->queue[i]))\n+\t\t\t\tcontinue;\n+\t\t\tpath = q->queue[i]->two->path;\n+\t\t\tlen = strlen(path);\n+\t\t\tif (len == p->len && !memcmp(path, p->path, len)) {\n+\t\t\t\tfound = 1;\n+\t\t\t\tmemcpy(p->parent_sha1[n],\n+\t\t\t\t       q->queue[i]->one->sha1, 20);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tif (!found)\n+\t\t\tp->len = 0;\n+\t}\n+\treturn curr;\n+}\n+\n+struct lline {\n+\tstruct lline *next;\n+\tint nth;\n+\tchar line[FLEX_ARRAY];\n+};\n+\n+struct sline {\n+\tstruct lline *lost_head, **lost_tail;\n+\tchar *bol;\n+\tint len;\n+\tunsigned long flag;\n+};\n+\n+static char *grab_blob(const unsigned char *sha1, unsigned long *size)\n+{\n+\tchar *blob;\n+\tchar type[20];\n+\tif (!memcmp(sha1, null_sha1, 20)) {\n+\t\t/* deleted blob */\n+\t\t*size = 0;\n+\t\treturn xcalloc(1, 1);\n+\t}\n+\tblob = read_sha1_file(sha1, type, size);\n+\tif (strcmp(type, \"blob\"))\n+\t\tdie(\"object '%s' is not a blob!\", sha1_to_hex(sha1));\n+\treturn blob;\n+}\n+\n+#define TMPPATHLEN 50\n+#define MAXLINELEN 10240\n+\n+static void write_to_temp_file(char *tmpfile, void *blob, unsigned long size)\n+{\n+\tint fd = git_mkstemp(tmpfile, TMPPATHLEN, \".diff_XXXXXX\");\n+\tif (fd < 0)\n+\t\tdie(\"unable to create temp-file\");\n+\tif (write(fd, blob, size) != size)\n+\t\tdie(\"unable to write temp-file\");\n+\tclose(fd);\n+}\n+\n+static void write_temp_blob(char *tmpfile, const unsigned char *sha1)\n+{\n+\tunsigned long size;\n+\tvoid *blob;\n+\tblob = grab_blob(sha1, &size);\n+\twrite_to_temp_file(tmpfile, blob, size);\n+\tfree(blob);\n+}\n+\n+static int parse_num(char **cp_p, unsigned int *num_p)\n+{\n+\tchar *cp = *cp_p;\n+\tunsigned int num = 0;\n+\tint read_some;\n+\n+\twhile ('0' <= *cp && *cp <= '9')\n+\t\tnum = num * 10 + *cp++ - '0';\n+\tif (!(read_some = cp - *cp_p))\n+\t\treturn -1;\n+\t*cp_p = cp;\n+\t*num_p = num;\n+\treturn 0;\n+}\n+\n+static int parse_hunk_header(char *line, int len,\n+\t\t\t     unsigned int *ob, unsigned int *on,\n+\t\t\t     unsigned int *nb, unsigned int *nn)\n+{\n+\tchar *cp;\n+\tcp = line + 4;\n+\tif (parse_num(&cp, ob)) {\n+\tbad_line:\n+\t\treturn error(\"malformed diff output: %s\", line);\n+\t}\n+\tif (*cp == ',') {\n+\t\tcp++;\n+\t\tif (parse_num(&cp, on))\n+\t\t\tgoto bad_line;\n+\t}\n+\telse\n+\t\t*on = 1;\n+\tif (*cp++ != ' ' || *cp++ != '+')\n+\t\tgoto bad_line;\n+\tif (parse_num(&cp, nb))\n+\t\tgoto bad_line;\n+\tif (*cp == ',') {\n+\t\tcp++;\n+\t\tif (parse_num(&cp, nn))\n+\t\t\tgoto bad_line;\n+\t}\n+\telse\n+\t\t*nn = 1;\n+\treturn -!!memcmp(cp, \" @@\", 3);\n+}\n+\n+static void append_lost(struct sline *sline, int n, const char *line)\n+{\n+\tstruct lline *lline;\n+\tint len = strlen(line);\n+\tif (line[len-1] == '\\n')\n+\t\tlen--;\n+\tlline = xmalloc(sizeof(*lline) + len + 1);\n+\tlline->next = NULL;\n+\tlline->nth = n;\n+\tif (sline->lost_head)\n+\t\t*(sline->lost_tail) = lline;\n+\telse\n+\t\tsline->lost_head = lline;\n+\tsline->lost_tail = &lline->next;\n+\tmemcpy(lline->line, line, len);\n+\tlline->line[len] = 0;\n+}\n+\n+static void combine_diff(const unsigned char *parent, const char *ourtmp,\n+\t\t\t struct sline *sline, int cnt, int n)\n+{\n+\tFILE *in;\n+\tchar parent_tmp[TMPPATHLEN];\n+\tchar cmd[TMPPATHLEN * 2 + 1024];\n+\tchar line[MAXLINELEN];\n+\tunsigned int lno, ob, on, nb, nn;\n+\tunsigned long pmask = ~(1UL << n);\n+\tstruct sline *lost_bucket = NULL;\n+\n+\twrite_temp_blob(parent_tmp, parent);\n+\tsprintf(cmd, \"diff --unified=0 -La/x -Lb/x '%s' '%s'\",\n+\t\tparent_tmp, ourtmp);\n+\tin = popen(cmd, \"r\");\n+\tif (!in)\n+\t\treturn;\n+\n+\tlno = 1;\n+\twhile (fgets(line, sizeof(line), in) != NULL) {\n+\t\tint len = strlen(line);\n+\t\tif (5 < len && !memcmp(\"@@ -\", line, 4)) {\n+\t\t\tif (parse_hunk_header(line, len,\n+\t\t\t\t\t      &ob, &on, &nb, &nn))\n+\t\t\t\tbreak;\n+\t\t\tlno = nb;\n+\t\t\tlost_bucket = &sline[nb-1]; /* sline is 0 based */\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!lost_bucket)\n+\t\t\tcontinue;\n+\t\tswitch (line[0]) {\n+\t\tcase '-':\n+\t\t\tappend_lost(lost_bucket, n, line+1);\n+\t\t\tbreak;\n+\t\tcase '+':\n+\t\t\tsline[lno-1].flag &= pmask;\n+\t\t\tlno++;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tfclose(in);\n+\tunlink(parent_tmp);\n+}\n+\n+static unsigned long context = 2;\n+\n+static int interesting(struct sline *sline, unsigned long all_mask)\n+{\n+\treturn ((sline->flag & all_mask) != all_mask || sline->lost_head);\n+}\n+\n+static void make_hunks(struct sline *sline, unsigned long cnt, int num_parent)\n+{\n+\tunsigned long all_mask = (1UL<<num_parent) - 1;\n+\tunsigned long mark = (1UL<<num_parent);\n+\tunsigned long i;\n+\n+\ti = 0;\n+\twhile (i < cnt) {\n+\t\tif (interesting(&sline[i], all_mask)) {\n+\t\t\tunsigned long j = (context < i) ? i - context : 0;\n+\t\t\twhile (j <= i)\n+\t\t\t\tsline[j++].flag |= mark;\n+\t\t\twhile (++i < cnt) {\n+\t\t\t\tif (!interesting(&sline[i], all_mask))\n+\t\t\t\t\tbreak;\n+\t\t\t\tsline[i].flag |= mark;\n+\t\t\t}\n+\t\t\tj = (i + context < cnt) ? i + context : cnt;\n+\t\t\twhile (i < j)\n+\t\t\t\tsline[i++].flag |= mark;\n+\t\t\tcontinue;\n+\t\t}\n+\t\ti++;\n+\t}\n+}\n+\n+static void dump_sline(struct sline *sline, int cnt, int num_parent)\n+{\n+\tunsigned long mark = (1UL<<num_parent);\n+\tint i;\n+\tint lno = 0;\n+\n+\twhile (1) {\n+\t\tstruct sline *sl = &sline[lno];\n+\t\tint hunk_end;\n+\t\twhile (lno < cnt && !(sline[lno].flag & mark))\n+\t\t\tlno++;\n+\t\tif (cnt <= lno)\n+\t\t\tbreak;\n+\t\tfor (hunk_end = lno + 1; hunk_end < cnt; hunk_end++)\n+\t\t\tif (!(sline[hunk_end].flag & mark))\n+\t\t\t\tbreak;\n+\t\tfor (i = 0; i < num_parent; i++) putchar('@');\n+\t\tprintf(\" +%d,%d \", lno+1, hunk_end-lno);\n+\t\tfor (i = 0; i < num_parent; i++) putchar('@');\n+\t\tputchar('\\n');\n+\t\twhile (lno < hunk_end) {\n+\t\t\tstruct lline *ll;\n+\t\t\tsl = &sline[lno++];\n+\t\t\tll = sl->lost_head;\n+\t\t\tint j;\n+\t\t\twhile (ll) {\n+\t\t\t\tfor (j = 0; j < ll->nth; j++)\n+\t\t\t\t\tputchar(' ');\n+\t\t\t\tputchar('-');\n+\t\t\t\tfor (j = ll->nth + 1; j < num_parent; j++)\n+\t\t\t\t\tputchar(' ');\n+\t\t\t\tputchar(' ');\n+\t\t\t\tputs(ll->line);\n+\t\t\t\tll = ll->next;\n+\t\t\t}\n+\t\t\tfor (j = 0; j < num_parent; j++) {\n+\t\t\t\tif ((1UL<<j) & sl->flag)\n+\t\t\t\t\tputchar(' ');\n+\t\t\t\telse\n+\t\t\t\t\tputchar('+');\n+\t\t\t}\n+\t\t\tprintf(\" %.*s\\n\", sl->len, sl->bol);\n+\t\t}\n+\t}\n+}\n+\n+static void show_combined_diff(struct path_list *elem, int num_parent)\n+{\n+\tunsigned long size, cnt, lno;\n+\tchar *result, *cp, *ep;\n+\tstruct sline *sline; /* survived lines */\n+\tint i;\n+\tchar ourtmp[TMPPATHLEN];\n+\n+\t/* Read the result of merge first */\n+\tresult = grab_blob(elem->sha1, &size);\n+\twrite_to_temp_file(ourtmp, result, size);\n+\n+\tfor (cnt = 0, cp = result; cp - result < size; cp++) {\n+\t\tif (*cp == '\\n')\n+\t\t\tcnt++;\n+\t}\n+\tif (result[size-1] != '\\n')\n+\t\tcnt++; /* incomplete line */\n+\n+\tsline = xcalloc(cnt, sizeof(*sline));\n+\tep = result;\n+\tsline[0].bol = result;\n+\tfor (lno = 0, cp = result; cp - result < size; cp++) {\n+\t\tif (*cp == '\\n') {\n+\t\t\tsline[lno].len = cp - sline[lno].bol;\n+\t\t\tsline[lno].flag = (1UL<<num_parent) - 1;\n+\t\t\tlno++;\n+\t\t\tif (lno < cnt)\n+\t\t\t\tsline[lno].bol = cp + 1;\n+\t\t}\n+\t}\n+\tif (result[size-1] != '\\n') {\n+\t\tsline[cnt-1].len = size - (sline[cnt-1].bol - result);\n+\t\tsline[cnt-1].flag = (1UL<<num_parent) - 1;\n+\t}\n+\n+\tfor (i = 0; i < num_parent; i++)\n+\t\tcombine_diff(elem->parent_sha1[i], ourtmp, sline, cnt, i);\n+\n+\tmake_hunks(sline, cnt, num_parent);\n+\n+\tdump_sline(sline, cnt, num_parent);\n+\tunlink(ourtmp);\n+\tfree(result);\n+\n+\tfor (i = 0; i < cnt; i++) {\n+\t\tif (sline[i].lost_head) {\n+\t\t\tstruct lline *ll = sline[i].lost_head;\n+\t\t\twhile (ll) {\n+\t\t\t\tstruct lline *tmp = ll;\n+\t\t\t\tll = ll->next;\n+\t\t\t\tfree(tmp);\n+\t\t\t}\n+\t\t}\n+\t}\n+\tfree(sline);\n+}\n+\n+int diff_tree_combined_merge(const unsigned char *sha1, const char *header)\n+{\n+\tstruct commit *commit = lookup_commit(sha1);\n+\tstruct diff_options diffopts;\n+\tstruct commit_list *parents;\n+\tstruct path_list *p, *paths = NULL;\n+\tint num_parent, i, num_paths;\n+\n+\tdiff_setup(&diffopts);\n+\tdiffopts.output_format = DIFF_FORMAT_NO_OUTPUT;\n+\tdiffopts.recursive = 1;\n+\n+\t/* count parents */\n+\tfor (parents = commit->parents, num_parent = 0;\n+\t     parents;\n+\t     parents = parents->next, num_parent++)\n+\t\t; /* nothing */\n+\n+\t/* find set of paths that everybody touches */\n+\tfor (parents = commit->parents, i = 0;\n+\t     parents;\n+\t     parents = parents->next, i++) {\n+\t\tstruct commit *parent = parents->item;\n+\t\tdiff_tree_sha1(parent->object.sha1, commit->object.sha1, \"\",\n+\t\t\t       &diffopts);\n+\t\tpaths = intersect_paths(paths, i, num_parent);\n+\t\tdiff_flush(&diffopts);\n+\t}\n+\n+\t/* find out surviving paths */\n+\tfor (num_paths = 0, p = paths; p; p = p->next) {\n+\t\tif (p->len)\n+\t\t\tnum_paths++;\n+\t}\n+\tif (num_paths) {\n+\t\tputs(header);\n+\t\tfor (p = paths; p; p = p->next) {\n+\t\t\tif (!p->len)\n+\t\t\t\tcontinue;\n+\t\t\tprintf(\"diff --combined \");\n+\t\t\tif (quote_c_style(p->path, NULL, NULL, 0))\n+\t\t\t\tquote_c_style(p->path, NULL, stdout, 0);\n+\t\t\telse\n+\t\t\t\tprintf(\"%s\", p->path);\n+\t\t\tputchar('\\n');\n+\t\t\tshow_combined_diff(p, num_parent);\n+\t\t}\n+\t}\n+\n+\t/* Clean things up */\n+\twhile (paths) {\n+\t\tstruct path_list *tmp = paths;\n+\t\tpaths = paths->next;\n+\t\tfree(tmp);\n+\t}\n+\treturn 0;\n+}\ndiff --git a/diff-tree.c b/diff-tree.c\nindex efa2b94..79f2497 100644\n--- a/diff-tree.c\n+++ b/diff-tree.c\n@@ -6,6 +6,7 @@ static int show_root_diff = 0;\n static int no_commit_id = 0;\n static int verbose_header = 0;\n static int ignore_merges = 1;\n+static int combine_merges = 0;\n static int read_stdin = 0;\n \n static const char *header = NULL;\n@@ -79,9 +80,13 @@ static const char *generate_header(const\n \toffset = sprintf(this_header, \"%s%s \",\n \t\t\t header_prefix,\n \t\t\t diff_unique_abbrev(commit_sha1, abbrev));\n-\toffset += sprintf(this_header + offset, \"(from %s)\\n\",\n-\t\t\t parent_sha1 ?\n-\t\t\t diff_unique_abbrev(parent_sha1, abbrev) : \"root\");\n+\tif (commit_sha1 != parent_sha1)\n+\t\toffset += sprintf(this_header + offset, \"(from %s)\\n\",\n+\t\t\t\t  parent_sha1\n+\t\t\t\t  ? diff_unique_abbrev(parent_sha1, abbrev)\n+\t\t\t\t  : \"root\");\n+\telse\n+\t\toffset += sprintf(this_header + offset, \"(from parents)\\n\");\n \toffset += pretty_print_commit(commit_format, msg, len,\n \t\t\t\t      this_header + offset,\n \t\t\t\t      sizeof(this_header) - offset);\n@@ -108,8 +113,15 @@ static int diff_tree_commit(const unsign\n \t}\n \n \t/* More than one parent? */\n-\tif (ignore_merges && commit->parents && commit->parents->next)\n-\t\treturn 0;\n+\tif (commit->parents && commit->parents->next) {\n+\t\tif (ignore_merges)\n+\t\t\treturn 0;\n+\t\telse if (combine_merges) {\n+\t\t\theader = generate_header(sha1, sha1,\n+\t\t\t\t\t\t commit->buffer);\n+\t\t\treturn diff_tree_combined_merge(sha1, header);\n+\t\t}\n+\t}\n \n \tfor (parents = commit->parents; parents; parents = parents->next) {\n \t\tstruct commit *parent = parents->item;\n@@ -154,7 +166,7 @@ static int diff_tree_stdin(char *line)\n }\n \n static const char diff_tree_usage[] =\n-\"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] \"\n+\"git-diff-tree [--stdin] [-m] [-c] [-s] [-v] [--pretty] [-t] [-r] [--root] \"\n \"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\\n\"\n \"  -r            diff recursively\\n\"\n \"  --root        include the initial commit as diff against /dev/null\\n\"\n@@ -217,6 +229,11 @@ int main(int argc, const char **argv)\n \t\t\tignore_merges = 0;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (!strcmp(arg, \"-c\")) {\n+\t\t\tignore_merges = 0;\n+\t\t\tcombine_merges = 1;\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!strcmp(arg, \"-v\")) {\n \t\t\tverbose_header = 1;\n \t\t\theader_prefix = \"diff-tree \";\n@@ -245,6 +262,11 @@ int main(int argc, const char **argv)\n \tif (diff_options.output_format == DIFF_FORMAT_PATCH)\n \t\tdiff_options.recursive = 1;\n \n+\tif (combine_merges) {\n+\t\tif (diff_options.output_format != DIFF_FORMAT_PATCH)\n+\t\t\tdie(\"-c cannot be used without -p\");\n+\t}\n+\n \tdiff_tree_setup_paths(get_pathspec(prefix, argv));\n \tdiff_setup_done(&diff_options);\n \ndiff --git a/diff.h b/diff.h\nindex 5696f2a..c0e8e0e 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -56,6 +56,8 @@ extern int diff_tree(struct tree_desc *t\n extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,\n \t\t\t  const char *base, struct diff_options *opt);\n \n+extern int diff_tree_combined_merge(const unsigned char *sha1, const char *);\n+\n extern void diff_addremove(struct diff_options *,\n \t\t\t   int addremove,\n \t\t\t   unsigned mode,\n-- \n1.1.4.g2bb7f\n"},{"id":"15094","messageId":"Pine.LNX.4.64.0601241655090.10804@evo.osdl.org","threadId":"3136","inReplyTo":"7vwtgqas0y.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] diff-tree -c: show a merge commit a bit more sensibly.","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-01-24T22:15:44Z","receivedAt":"2006-01-24T22:15:44Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nOn Tue, 24 Jan 2006, Junio C Hamano wrote:\n>\n> A new option '-c' to diff-tree changes the way a merge commit is\n> displayed when generating a patch output.  It shows a \"combined\n> diff\" (hence the option letter 'c'), which looks like this:\n\nThank you!\n\nCan you make this the default for \"git-whatchanged\" too? That way, merges \nthat have manual fixups will actually show up in the whatchanged output.\n\nAlso, it would be perhaps even nicer if it had a \"dense\" version, which \nonly showed the chunks that had differences from more than one parent. \nChunks that have diffs from just one parent obviously had no conflicts in \nthat chunk, so they are much less interesting than a chunk that was \ndifferent from more than one parent..\n\n\t\t\tLinus\n"},{"id":"15101","messageId":"7virs92h50.fsf@assigned-by-dhcp.cox.net","threadId":"3136","inReplyTo":"Pine.LNX.4.64.0601241655090.10804@evo.osdl.org","subject":"Re: [PATCH] diff-tree -c: show a merge commit a bit more sensibly.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-01-25T02:08:59Z","receivedAt":"2006-01-25T02:08:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Linus Torvalds <torvalds@osdl.org> writes:\n\n> On Tue, 24 Jan 2006, Junio C Hamano wrote:\n>>\n>> A new option '-c' to diff-tree changes the way a merge commit is\n>> displayed when generating a patch output.  It shows a \"combined\n>> diff\" (hence the option letter 'c'), which looks like this:\n>\n> Can you make this the default for \"git-whatchanged\" too? That way, merges \n> that have manual fixups will actually show up in the whatchanged output.\n\nOne problem with that I see is that it would mean making the\npatch output as the default for whatchanged, since I made '-c'\nto imply '-p' (I did not think of a reasonable 'combined diff'\nbehaviour for the diff-raw output).\n\nI suspect many people are now used to the current default of\nshowing an abbreviated diff-raw and use it to quickly sanity\ncheck the list of paths touched by each commit, so I am not so\nsure that '-c' would be a good default for whatchanged.\n\nFor now, please do \"git-repo-config whatchanged.difftree\" ;-).\n\nBTW, one feature that might be interesting is to tell either\nrev-list or diff-tree to show _only_ merges.  My debugging\nsession went like this:\n\n\t$ git rev-list --all --parents |\n          sed -ne 's/\\([^ ]*\\) [^ ]* [^ ].*/\\1/p' |\n          git diff-tree -c --stdin --pretty\n\nI doubt such a flag would be of any practical value other than\ncuriosity, though.\n\n> Also, it would be perhaps even nicer if it had a \"dense\" version, which \n> only showed the chunks that had differences from more than one parent. \n> Chunks that have diffs from just one parent obviously had no conflicts in \n> that chunk, so they are much less interesting than a chunk that was \n> different from more than one parent..\n\nI agree that would be useful.  The logic to cull \"uninteresting\"\ncommit comes much earlier than the combined diff is split into\nhunks, so it is probably not trivial (but I suspect not hard) to\narrange.  For now, I output the \"diff --combined\" header along\nwith the commit log even if \"dense\" ends up removing all hunks\nas not-so-interesting.  An updated version is currently in the\n\"pu\" branch.\n\nI think the current output can be enhanced to have \"-line,count\"\nfor all parents to make the output machine applicable (i.e. we\ncould teach git-apply to take such a patch, which is somewhat\nyucky) but I suspect dense mode would make that inpractical.  I\npersonally think combined diff is purely for human consumption,\nso the machine applicability may not be an issue.\n"},{"id":"15113","messageId":"871wywzdrp.fsf@mid.deneb.enyo.de","threadId":"3136","inReplyTo":"7vwtgqas0y.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] diff-tree -c: show a merge commit a bit more sensibly.","fromName":"Florian Weimer","fromEmail":"fw@deneb.enyo.de","sentAt":"2006-01-25T12:35:22Z","receivedAt":"2006-01-25T12:35:22Z","isPatch":true,"sender":{"key":"fw@deneb.enyo.de","avatar":null},"body":"* Junio C. Hamano:\n\n> A new option '-c' to diff-tree changes the way a merge commit is\n> displayed when generating a patch output.  It shows a \"combined\n> diff\" (hence the option letter 'c'), which looks like this:\n>\n>     $ git-diff-tree --pretty -c -p fec9ebf1 | head -n 18\n>     diff-tree fec9ebf... (from parents)\n>     Merge: 0620db3... 8a263ae...\n>     Author: Junio C Hamano <junkio@cox.net>\n>     Date:   Sun Jan 15 22:25:35 2006 -0800\n>\n> \tMerge fixes up to GIT 1.1.3\n>\n>     diff --combined describe.c\n>     @@ +99,18 @@\n>        }\n>\n>     -  static void describe(char *arg)\n>      - static void describe(struct commit *cmit, int last_one)\n>     ++ static void describe(char *arg, int last_one)\n\nMaybe it would make sense to include a lines like these?\n\n      #  0620db3... (Merge branch 'fix')\n       # 8a263ae... (GIT 1.1.3)\n      ## fec9ebf... (Merge fixes up to GIT 1.1.3)\n\nThis would make more clear which columns corresponds to which parent.\n"}]}