Re: git diff <-> diffstat

From: Junio C Hamano
Date: Tue Sep 26 2006 - 18:05:03 EST


Linus Torvalds <torvalds@xxxxxxxx> writes:

> On Mon, 25 Sep 2006, Adrian Bunk wrote:
>>
>> Is there any way for "git diff" to handle additional options diffstat
>> handles? I'm a big fan of the -w72 diffstat option.
>
> No, I think we've got the width fixed at 80 columns.

Here is a patch to lift this limit. It also adds colors ;-).

-- >8 --
diff --stat --stat-width=72 --stat-name-width=30 --color

This does two completely unrelated things:

- The option --stat-width=N can be used to restrict the width of
the output further or adjust the output for wider terminal.
When not given the default is 80 for traditional terminals.

- The option --stat-name-width=M can be used to set the maximum
width used for filename part in the diffstat output. When
not given the default is 50.

- With --color option, add (+) and delete (-) are colored the
same way as added / deleted lines in the colored patch.

---
diff --git a/diff.c b/diff.c
index 443e248..5538f9a 100644
--- a/diff.c
+++ b/diff.c
@@ -545,21 +545,67 @@ static void diffstat_consume(void *priv,
x->deleted++;
}

-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
const char mime_boundary_leader[] = "------------";

-static void show_stats(struct diffstat_t* data)
+static int scale_change(int it, int width, int max_change)
+{
+ /*
+ * round(width * it / max_change);
+ */
+ return (it * width * 2 + max_change) / (max_change * 2);
+}
+
+static void show_name(const char *prefix, const char *name, int len,
+ const char *reset, const char *fname)
+{
+ printf(" %s%s%-*s%s |", fname, prefix, len, name, reset);
+}
+
+static void show_graph(char ch, int cnt, const char *set, const char *reset)
+{
+ if (!cnt)
+ return;
+ printf("%s", set);
+ while (cnt--)
+ putchar(ch);
+ printf("%s", reset);
+}
+
+static void show_stats(struct diffstat_t* data, struct diff_options *options)
{
int i, len, add, del, total, adds = 0, dels = 0;
- int max, max_change = 0, max_len = 0;
+ int max_change = 0, max_len = 0, min_change = 0;
int total_files = data->nr;
+ int stat_width, name_width;
+ const char *reset, *plain, *add_c, *del_c;

if (data->nr == 0)
return;

+ stat_width = options->stat_width ? options->stat_width : 80;
+ name_width = options->stat_name_width ? options->stat_name_width : 50;
+
+ if (stat_width < name_width + 10)
+ stat_width = name_width + 10; /* see below */
+
+ reset = diff_get_color(options->color_diff, DIFF_RESET);
+ plain = diff_get_color(options->color_diff, DIFF_PLAIN);
+ add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+ del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+
+ /* Find the longest filename and max/min number of changes */
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
+ int added = file->added;
+ int deleted = file->deleted;
+ int change = added + deleted;
+
+ if (0 < (len = quote_c_style(file->name, NULL, NULL, 0))) {
+ char *qname = xmalloc(len + 1);
+ quote_c_style(file->name, qname, NULL, 0);
+ free(file->name);
+ file->name = qname;
+ }

len = strlen(file->name);
if (max_len < len)
@@ -567,54 +613,57 @@ static void show_stats(struct diffstat_t

if (file->is_binary || file->is_unmerged)
continue;
- if (max_change < file->added + file->deleted)
- max_change = file->added + file->deleted;
- }
+ if (max_change < change)
+ max_change = change;
+ if (0 < added && (!min_change || added < min_change))
+ min_change = added;
+ if (0 < deleted && (!min_change || deleted < min_change))
+ min_change = deleted;
+ }
+
+ /* Compute the width of the graph part;
+ * 10 is for one blank at the beginning of the line plus
+ * " | count " between the name and the graph.
+ *
+ * From here on, name_width is the width of the name area,
+ * and stat_width is the width of the graph area.
+ */
+ name_width = (name_width < max_len) ? name_width : max_len;
+ if (stat_width < (name_width + 10) + max_change)
+ stat_width = stat_width - (name_width + 10);
+ else
+ stat_width = max_change;

for (i = 0; i < data->nr; i++) {
const char *prefix = "";
char *name = data->files[i]->name;
int added = data->files[i]->added;
int deleted = data->files[i]->deleted;
-
- if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
- char *qname = xmalloc(len + 1);
- quote_c_style(name, qname, NULL, 0);
- free(name);
- data->files[i]->name = name = qname;
- }
+ int name_len;

/*
* "scale" the filename
*/
- len = strlen(name);
- max = max_len;
- if (max > 50)
- max = 50;
- if (len > max) {
+ len = name_width;
+ name_len = strlen(name);
+ if (name_width < name_len) {
char *slash;
prefix = "...";
- max -= 3;
- name += len - max;
+ len -= 3;
+ name += name_len - len;
slash = strchr(name, '/');
if (slash)
name = slash;
}
- len = max;
-
- /*
- * scale the add/delete
- */
- max = max_change;
- if (max + len > 70)
- max = 70 - len;

if (data->files[i]->is_binary) {
- printf(" %s%-*s | Bin\n", prefix, len, name);
+ show_name(prefix, name, len, reset, plain);
+ printf(" Bin\n");
goto free_diffstat_file;
}
else if (data->files[i]->is_unmerged) {
- printf(" %s%-*s | Unmerged\n", prefix, len, name);
+ show_name(prefix, name, len, reset, plain);
+ printf(" Unmerged\n");
goto free_diffstat_file;
}
else if (!data->files[i]->is_renamed &&
@@ -623,27 +672,32 @@ static void show_stats(struct diffstat_t
goto free_diffstat_file;
}

+ /*
+ * scale the add/delete
+ */
add = added;
del = deleted;
total = add + del;
adds += add;
dels += del;

- if (max_change > 0) {
- total = (total * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
+ if (stat_width <= max_change) {
+ total = scale_change(total, stat_width, max_change);
+ add = scale_change(add, stat_width, max_change);
del = total - add;
}
- printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
- len, name, added + deleted,
- add, pluses, del, minuses);
+ show_name(prefix, name, len, reset, plain);
+ printf("%5d ", added + deleted);
+ show_graph('+', add, add_c, reset);
+ show_graph('-', del, del_c, reset);
+ putchar('\n');
free_diffstat_file:
free(data->files[i]->name);
free(data->files[i]);
}
free(data->files);
- printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+ plain, total_files, adds, dels, reset);
}

struct checkdiff_t {
@@ -1681,6 +1735,14 @@ int diff_opt_parse(struct diff_options *
}
else if (!strcmp(arg, "--stat"))
options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ else if (!strncmp(arg, "--stat-width=", 13)) {
+ options->stat_width = strtoul(arg + 13, NULL, 10);
+ options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ }
+ else if (!strncmp(arg, "--stat-name-width=", 18)) {
+ options->stat_name_width = strtoul(arg + 18, NULL, 10);
+ options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ }
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
else if (!strcmp(arg, "--summary"))
@@ -2438,7 +2500,7 @@ void diff_flush(struct diff_options *opt
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
- show_stats(&diffstat);
+ show_stats(&diffstat, options);
separator++;
}

diff --git a/diff.h b/diff.h
index b60a02e..e06d0f4 100644
--- a/diff.h
+++ b/diff.h
@@ -69,6 +69,9 @@ struct diff_options {
const char *stat_sep;
long xdl_opts;

+ int stat_width;
+ int stat_name_width;
+
int nr_paths;
const char **paths;
int *pathlens;

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/