{"thread":{"id":"3881","subject":"[WIP/PATCH] Combined diffstat","startedAt":"2006-04-15T18:08:51Z","lastAt":"2006-04-15T18:08:51Z","messageCount":1,"participants":["Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"18704","messageId":"Pine.LNX.4.63.0604152006440.10192@wbgn013.biozentrum.uni-wuerzburg.de","threadId":"3881","inReplyTo":null,"subject":"[WIP/PATCH] Combined diffstat","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2006-04-15T18:08:51Z","receivedAt":"2006-04-15T18:08:51Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nI ended up implementing both proposals for merge diffstats:\n\nJunio)\n\n pack-objects.c |  923 ++++++++----- ++++++++++++++++++++++++++++------ +++--\n rev-list.c     |    5\n send-pack.c    |   86               +-                                 +\n upload-pack.c  |    7\n 4 files changed, 786 insertions(+), 235 deletions(-)\n\nMarco)\n\n pack-objects.c |   22 ++++++++++++++--------\n rev-list.c     |    0\n send-pack.c    |    0\n upload-pack.c  |    0\n 4 files changed, 156 insertions(+), 86 deletions(-)\n\n pack-objects.c |   56 +++++++++++++++++++++++++++++++++++++++++++++++---------\n send-pack.c    |    4 +++-\n upload-pack.c  |    0\n 3 files changed, 532 insertions(+), 103 deletions(-)\n\n pack-objects.c |    9 ++++++---\n rev-list.c     |    0\n send-pack.c    |    4 +++-\n upload-pack.c  |    0\n 4 files changed, 98 insertions(+), 46 deletions(-)\n\nFor the moment, I like Junio's better, if only because it was more difficult\nto implement.\n\nIf you want to try it, I suggest using this as a test case:\n\n\tgit log --stat f0b0af1b04\n\nTo get the output with separate diffstats for each parent, just define the\nenvironment variable NO_COMBINED_DIFFSTAT before calling git.\n\nSigned-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\n\n---\n\n combine-diff.c |  271 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n diff.h         |    1 \n log-tree.c     |    6 +\n 3 files changed, 278 insertions(+), 0 deletions(-)\n\n9e802df5d029d44938568060d4d567a4fd130d79\ndiff --git a/combine-diff.c b/combine-diff.c\nindex 9bd27f8..b82ee8c 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -148,6 +148,15 @@ static void append_lost(struct sline *sl\n \tsline->lost_tail = &lline->next;\n }\n \n+struct diffstat_t {\n+        struct diffstat_file {\n+                char *name;\n+                struct diffstat_parent {\n+\t\t\tunsigned int added, deleted;\n+\t\t} *parent;\n+        } *files, *current;\n+};\n+\n struct combine_diff_state {\n \tstruct xdiff_emit_state xm;\n \n@@ -557,6 +566,35 @@ static void dump_sline(struct sline *sli\n \t}\n }\n \n+static void diffstat_sline(struct diffstat_t *diffstat,\n+\t\tstruct sline *sline, unsigned long cnt, int num_parent)\n+{\n+\tstruct diffstat_parent *data = diffstat->current->parent;\n+\tunsigned long interesting = (1UL<<num_parent);\n+\tint lno;\n+\tfor (lno = 0; lno <= cnt; lno++) {\n+\t\tstruct sline *sl = sline + lno;\n+\t\tstruct lline *ll;\n+\t\tint j;\n+\n+\t\tif (!(sl->flag & interesting))\n+\t\t\tcontinue;\n+\n+\t\t\n+\t\tfor (ll = sl->lost_head; ll; ll = ll->next)\n+\t\t\tfor (j = 0; j < num_parent; j++)\n+\t\t\t\tif (ll->parent_map & (1UL<<j))\n+\t\t\t\t\tdata[j].deleted++;\n+\n+\t\tif (cnt < lno)\n+\t\t\tbreak;\n+\n+\t\tfor (j = 0; j < num_parent; j++)\n+\t\t\tif (sl->flag & (1UL<<j))\n+\t\t\t\tdata[j].added++;\n+\t}\n+}\n+\n static void reuse_combine_diff(struct sline *sline, unsigned long cnt,\n \t\t\t       int i, int j)\n {\n@@ -596,6 +634,7 @@ static int show_patch_diff(struct combin\n \tint working_tree_file = !memcmp(elem->sha1, null_sha1, 20);\n \tint abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;\n \tmmfile_t result_file;\n+\tstruct diffstat_t *diffstat = opt->diffstat;\n \n \t/* Read the result of merge first */\n \tif (!working_tree_file)\n@@ -686,6 +725,12 @@ static int show_patch_diff(struct combin\n \n \tshow_hunks = make_hunks(sline, cnt, num_parent, dense);\n \n+\tif (diffstat) {\n+\t\tdiffstat->current->name = strdup(elem->path);\n+\t\tdiffstat_sline(diffstat, sline, cnt, num_parent);\n+\t\tshow_hunks = 0;\n+\t}\n+\n \tif (show_hunks || mode_differs || working_tree_file) {\n \t\tconst char *abb;\n \n@@ -826,11 +871,213 @@ int show_combined_diff(struct combine_di\n \t\treturn 1;\n \n \tdefault:\n+\tcase DIFF_FORMAT_DIFFSTAT:\n \tcase DIFF_FORMAT_PATCH:\n \t\treturn show_patch_diff(p, num_parent, dense, header, opt);\n \t}\n }\n \n+static const char pluses[] = \"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\";\n+static const char minuses[]= \"----------------------------------------------------------------------\";\n+\n+static void show_stats(struct diffstat_t *diffstat,\n+\t\tint num_paths, int num_parent)\n+{\n+\tint show_combined_diffstat = (getenv(\"NO_COMBINED_DIFFSTAT\") == NULL);\n+\tint i, j;\n+\tint max_length = 0, max_change = 0;\n+\tint *parent_max_change;\n+\tchar *prefix = \"\";\n+\tint len, add, del, total, adds, dels, total_files, max;\n+\n+\tif (num_paths == 0)\n+\t\treturn;\n+\n+\tparent_max_change = xcalloc(sizeof(int), num_parent);\n+\n+\tfor (i = 0; i < num_paths; i++) {\n+\t\tint len;\n+\t\tchar *name = diffstat->files[i].name;\n+\t\tif (0 < (len = quote_c_style(name, NULL, NULL, 0))) {\n+\t\t\tchar *qname = xmalloc(len + 1);\n+\t\t\tquote_c_style(name, qname, NULL, 0);\n+\t\t\tfree(name);\n+\t\t\tdiffstat->files[i].name = name = qname;\n+\t\t} else\n+\t\t\tlen = strlen(name);\n+\n+\t\tif (max_length < len)\n+\t\t\tmax_length = len;\n+\n+\t\tfor (j = 0; j < num_parent; j++) {\n+\t\t\tstruct diffstat_parent *data =\n+\t\t\t\tdiffstat->files[i].parent + j;\n+\t\t\tint change = data->added + data->deleted;\n+\t\t\tif (parent_max_change[j] < change) {\n+\t\t\t\tparent_max_change[j] = change;\n+\t\t\t\tif (max_change < change)\n+\t\t\t\t\tmax_change = change;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (max_change == 0)\n+\t\treturn;\n+\n+\tprintf(\"---\\n\");\n+\n+\tif (show_combined_diffstat) {\n+\t\tchar buffer[80];\n+\t\tmax_change = 0;\n+\t\tfor (j = 0; j < num_parent; j++)\n+\t\t\tmax_change += parent_max_change[j];\n+\t\t\n+\t\tprefix = \"\";\n+\t\ttotal_files = num_paths;\n+\t\tadds = dels = 0;\n+\n+\t\tfor (i = 0; i < num_paths; i++) {\n+\t\t\tint offset;\n+\t\t\tchar *name = diffstat->files[i].name;\n+\n+\t\t\t/*\n+\t\t\t * \"scale\" the filename\n+\t\t\t */\n+\t\t\tlen = strlen(name);\n+\t\t\tmax = max_length;\n+\t\t\tif (max > 50)\n+\t\t\t\tmax = 50;\n+\t\t\tif (len > max) {\n+\t\t\t\tchar *slash;\n+\t\t\t\tprefix = \"...\";\n+\t\t\t\tmax -= 3;\n+\t\t\t\tname += len - max;\n+\t\t\t\tslash = strchr(name, '/');\n+\t\t\t\tif (slash)\n+\t\t\t\t\tname = slash;\n+\t\t\t}\n+\t\t\tlen = max;\n+\n+\t\t\t/*\n+\t\t\t * scale the add/delete\n+\t\t\t */\n+\t\t\tmax = max_change + num_parent - 1;\n+\t\t\tif (max + len > 70)\n+\t\t\t\tmax = 70 - len;\n+\n+\t\t\tmemset(buffer, ' ', 70);\n+\t\t\tbuffer[max + 1] = 0;\n+\n+\t\t\tmax -= num_parent - 1;\n+\t\t\toffset = add = del = 0;\n+\t\t\tfor (j = 0; j < num_parent; j++) {\n+\t\t\t\tstruct diffstat_parent *data =\n+\t\t\t\t\tdiffstat->files[i].parent + j;\n+\t\t\t\tint a, d, size;\n+\n+\t\t\t\tsize = max * parent_max_change[j] / max_change;\n+\t\t\t\tif (size == 0)\n+\t\t\t\t\tsize = 1;\n+\n+\t\t\t\ta = data->added;\n+\t\t\t\td = a + data->deleted;\n+\n+\t\t\t\ta = a * size / parent_max_change[j];\n+\t\t\t\td = d * size / parent_max_change[j];\n+\n+\t\t\t\tmemset(buffer + offset, '+', a);\n+\t\t\t\tmemset(buffer + offset + a, '-', d - a);\n+\n+\t\t\t\tadd += data->added;\n+\t\t\t\tdel += data->deleted;\n+\n+\t\t\t\toffset += size + 1;\n+\t\t\t}\n+\n+\t\t\tif (add + del == 0) {\n+\t\t\t\ttotal_files--;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tadds += add;\n+\t\t\tdels += del;\n+\n+\t\t\tprintf(\" %s%-*s |%5d %s\\n\", prefix, len, name,\n+\t\t\t\t\tadd + del, buffer);\n+\t\t}\n+\t\tprintf(\" %d files changed, %d insertions(+), %d deletions(-)\\n\",\n+\t\t\t\ttotal_files, adds, dels);\n+\t} else {\n+\t\tfor (j = 0; j < num_parent; j++) {\n+\t\t\tprefix = \"\";\n+\t\t\ttotal_files = num_paths;\n+\t\t\tadds = dels = 0;\n+\n+\t\t\tfor (i = 0; i < num_paths; i++) {\n+\t\t\t\tchar *name = diffstat->files[i].name;\n+\t\t\t\tstruct diffstat_parent *data =\n+\t\t\t\t\tdiffstat->files[i].parent + j;\n+\n+\t\t\t\t/*\n+\t\t\t\t * \"scale\" the filename\n+\t\t\t\t */\n+\t\t\t\tlen = strlen(name);\n+\t\t\t\tmax = max_length;\n+\t\t\t\tif (max > 50)\n+\t\t\t\t\tmax = 50;\n+\t\t\t\tif (len > max) {\n+\t\t\t\t\tchar *slash;\n+\t\t\t\t\tprefix = \"...\";\n+\t\t\t\t\tmax -= 3;\n+\t\t\t\t\tname += len - max;\n+\t\t\t\t\tslash = strchr(name, '/');\n+\t\t\t\t\tif (slash)\n+\t\t\t\t\t\tname = slash;\n+\t\t\t\t}\n+\t\t\t\tlen = max;\n+\n+\t\t\t\t/*\n+\t\t\t\t * scale the add/delete\n+\t\t\t\t */\n+\t\t\t\tmax = max_change;\n+\t\t\t\tif (max + len > 70)\n+\t\t\t\t\tmax = 70 - len;\n+\n+\t\t\t\tadd = data->added;\n+\t\t\t\tdel = data->deleted;\n+\n+\t\t\t\tif (add + del == 0) {\n+\t\t\t\t\ttotal_files--;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\n+\t\t\t\ttotal = add + del;\n+\t\t\t\tadds += add;\n+\t\t\t\tdels += del;\n+\n+\t\t\t\tif (max_change > 0) {\n+\t\t\t\t\ttotal = (total * max + max_change / 2) / max_change;\n+\t\t\t\t\tadd = (add * max + max_change / 2) / max_change;\n+\t\t\t\t\tdel = total - add;\n+\t\t\t\t}\n+\t\t\t\tprintf(\" %s%-*s |%5d %.*s%.*s\\n\", prefix,\n+\t\t\t\t\t\tlen, name, add + del,\n+\t\t\t\t\t\tadd, pluses, del, minuses);\n+\t\t\t}\n+\t\t\tprintf(\" %d files changed, %d insertions(+), %d deletions(-)\\n\\n\",\n+\t\t\t\t\ttotal_files, adds, dels);\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < num_paths; i++) {\n+\t\tfree(diffstat->files[i].parent);\n+\t\tfree(diffstat->files[i].name);\n+\t}\n+\tfree(diffstat->files);\n+\tdiffstat->files = NULL;\n+\tfree(parent_max_change);\n+}\n+\n const char *diff_tree_combined_merge(const unsigned char *sha1,\n \t\t\t     const char *header, int dense,\n \t\t\t     struct diff_options *opt)\n@@ -840,6 +1087,11 @@ const char *diff_tree_combined_merge(con\n \tstruct commit_list *parents;\n \tstruct combine_diff_path *p, *paths = NULL;\n \tint num_parent, i, num_paths;\n+\tstruct diffstat_t *diffstat = NULL;\n+\n+\tif (opt->output_format == DIFF_FORMAT_DIFFSTAT)\n+\t\topt->diffstat = diffstat =\n+\t\t\txcalloc(sizeof(struct diffstat_t), 1);\n \n \tdiffopts = *opt;\n \tdiffopts.output_format = DIFF_FORMAT_NO_OUTPUT;\n@@ -870,6 +1122,15 @@ const char *diff_tree_combined_merge(con\n \t\t\tnum_paths++;\n \t}\n \tif (num_paths) {\n+\t\tif (diffstat) {\n+\t\t\tdiffstat->files = xcalloc(sizeof(struct diffstat_file),\n+\t\t\t\t\tnum_paths);\n+\t\t\tfor (i = 0; i < num_paths; i++)\n+\t\t\t\tdiffstat->files[i].parent =\n+\t\t\t\t\txcalloc(sizeof(struct diffstat_parent),\n+\t\t\t\t\t\t\tnum_parent);\n+\t\t\tdiffstat->current = diffstat->files;\n+\t\t}\n \t\tif (opt->with_raw) {\n \t\t\tint saved_format = opt->output_format;\n \t\t\topt->output_format = DIFF_FORMAT_RAW;\n@@ -882,12 +1143,22 @@ const char *diff_tree_combined_merge(con\n \t\t\tputchar(opt->line_termination);\n \t\t}\n \t\tfor (p = paths; p; p = p->next) {\n+\t\t\tif (p->len == 0)\n+\t\t\t\tcontinue;\n \t\t\tif (show_combined_diff(p, num_parent, dense,\n \t\t\t\t\t       header, opt))\n \t\t\t\theader = NULL;\n+\t\t\tif (diffstat)\n+\t\t\t\tdiffstat->current++;\n \t\t}\n \t}\n \n+\tif (diffstat) {\n+\t\tshow_stats(diffstat, num_paths, num_parent);\n+\t\tfree(diffstat);\n+\t\topt->diffstat = diffstat = NULL;\n+\t}\n+\n \t/* Clean things up */\n \twhile (paths) {\n \t\tstruct combine_diff_path *tmp = paths;\ndiff --git a/diff.h b/diff.h\nindex f783bae..26e3c02 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -45,6 +45,7 @@ struct diff_options {\n \tint *pathlens;\n \tchange_fn_t change;\n \tadd_remove_fn_t add_remove;\n+\tstruct diffstat_t *diffstat;\n };\n \n extern void diff_tree_setup_paths(const char **paths, struct diff_options *);\ndiff --git a/log-tree.c b/log-tree.c\nindex cb0d0b1..bb46f06 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -232,6 +232,12 @@ int parse_whatchanged_opt(int ac, const \n \t\t\topt->diffopt.output_format = DIFF_FORMAT_PATCH;\n \t\tif (opt->diffopt.output_format == DIFF_FORMAT_PATCH)\n \t\t\topt->diffopt.recursive = 1;\n+\t\tif (opt->diffopt.output_format == DIFF_FORMAT_DIFFSTAT) {\n+\t\t\topt->ignore_merges = 0;\n+\t\t\topt->combine_merges = 1;\n+\t\t\topt->dense_combined_merges = 1;\n+\t\t\topt->diffopt.recursive = 1;\n+\t\t}\n \t\tif (!wcopt->full_diff && rev->prune_data)\n \t\t\tdiff_tree_setup_paths(rev->prune_data, &opt->diffopt);\n \t\tdiff_setup_done(&opt->diffopt);\n-- \n1.3.0.rc4.g667c\n"}]}