Merge branch 'zj/diff-stat-dyncol'

By Zbigniew Jędrzejewski-Szmek (8) and Junio C Hamano (1)
* zj/diff-stat-dyncol:
  : This breaks tests. Perhaps it is not worth using the decimal-width stuff
  : for this series, at least initially.
  diff --stat: add config option to limit graph width
  diff --stat: enable limiting of the graph part
  diff --stat: add a test for output with COLUMNS=40
  diff --stat: use a maximum of 5/8 for the filename part
  merge --stat: use the full terminal width
  log --stat: use the full terminal width
  show --stat: use the full terminal width
  diff --stat: use the full terminal width
  diff --stat: tests for long filenames and big change counts
This commit is contained in:
Junio C Hamano 2012-03-06 14:53:06 -08:00
commit af050219e4
9 changed files with 336 additions and 34 deletions

View File

@ -52,6 +52,10 @@ directories with less than 10% of the total amount of changed files,
and accumulating child directory counts in the parent directories: and accumulating child directory counts in the parent directories:
`files,10,cumulative`. `files,10,cumulative`.
diff.statGraphWidth::
Limit the width of the graph part in --stat output. If set, applies
to all commands generating --stat outuput except format-patch.
diff.external:: diff.external::
If this config variable is set, diff generation is not If this config variable is set, diff generation is not
performed using the internal diff machinery, but using the performed using the internal diff machinery, but using the

View File

@ -56,13 +56,19 @@ endif::git-format-patch[]
Generate a diff using the "histogram diff" algorithm. Generate a diff using the "histogram diff" algorithm.
--stat[=<width>[,<name-width>[,<count>]]]:: --stat[=<width>[,<name-width>[,<count>]]]::
Generate a diffstat. You can override the default Generate a diffstat. By default, as much space as necessary
output width for 80-column terminal by `--stat=<width>`. will be used for the filename part, and the rest for the graph
The width of the filename part can be controlled by part. Maximum width defaults to terminal width, or 80 columns
giving another width to it separated by a comma. if not connected to a terminal, and can be overriden by
`<width>`. The width of the filename part can be limited by
giving another width `<name-width>` after a comma. The width
of the graph part can be limited by using
`--stat-graph-width=<width>` (affects all commands generating
a stat graph) or by setting `diff.statGraphWidth=<width>`
(does not affect `git format-patch`).
By giving a third parameter `<count>`, you can limit the By giving a third parameter `<count>`, you can limit the
output to the first `<count>` lines, followed by output to the first `<count>` lines, followed by `...` if
`...` if there are more. there are more.
+ +
These parameters can also be set individually with `--stat-width=<width>`, These parameters can also be set individually with `--stat-width=<width>`,
`--stat-name-width=<name-width>` and `--stat-count=<count>`. `--stat-name-width=<name-width>` and `--stat-count=<count>`.

View File

@ -285,6 +285,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
/* Otherwise, we are doing the usual "git" diff */ /* Otherwise, we are doing the usual "git" diff */
rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index;
/* Scale to real terminal size and respect statGraphWidth config */
rev.diffopt.stat_width = -1;
rev.diffopt.stat_graph_width = -1;
/* Default to let external and textconv be used */ /* Default to let external and textconv be used */
DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL); DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);

View File

@ -77,6 +77,8 @@ static void cmd_log_init_defaults(struct rev_info *rev)
get_commit_format(fmt_pretty, rev); get_commit_format(fmt_pretty, rev);
rev->verbose_header = 1; rev->verbose_header = 1;
DIFF_OPT_SET(&rev->diffopt, RECURSIVE); DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
rev->diffopt.stat_width = -1; /* use full terminal width */
rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */
rev->abbrev_commit = default_abbrev_commit; rev->abbrev_commit = default_abbrev_commit;
rev->show_root_diff = default_show_root; rev->show_root_diff = default_show_root;
rev->subject_prefix = fmt_patch_subject_prefix; rev->subject_prefix = fmt_patch_subject_prefix;
@ -447,6 +449,8 @@ int cmd_show(int argc, const char **argv, const char *prefix)
rev.diff = 1; rev.diff = 1;
rev.always_show_header = 1; rev.always_show_header = 1;
rev.no_walk = 1; rev.no_walk = 1;
rev.diffopt.stat_width = -1; /* Scale to real terminal size */
memset(&opt, 0, sizeof(opt)); memset(&opt, 0, sizeof(opt));
opt.def = "HEAD"; opt.def = "HEAD";
opt.tweak = show_rev_tweak_rev; opt.tweak = show_rev_tweak_rev;

View File

@ -399,6 +399,8 @@ static void finish(struct commit *head_commit,
if (new_head && show_diffstat) { if (new_head && show_diffstat) {
struct diff_options opts; struct diff_options opts;
diff_setup(&opts); diff_setup(&opts);
opts.stat_width = -1; /* use full terminal width */
opts.stat_graph_width = -1; /* respect statGraphWidth config */
opts.output_format |= opts.output_format |=
DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
opts.detect_rename = DIFF_DETECT_RENAME; opts.detect_rename = DIFF_DETECT_RENAME;

View File

@ -2092,6 +2092,7 @@ _git_config ()
core.whitespace core.whitespace
core.worktree core.worktree
diff.autorefreshindex diff.autorefreshindex
diff.statGraphWidth
diff.external diff.external
diff.ignoreSubmodules diff.ignoreSubmodules
diff.mnemonicprefix diff.mnemonicprefix

116
diff.c
View File

@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg;
int diff_auto_refresh_index = 1; int diff_auto_refresh_index = 1;
static int diff_mnemonic_prefix; static int diff_mnemonic_prefix;
static int diff_no_prefix; static int diff_no_prefix;
static int diff_stat_graph_width;
static int diff_dirstat_permille_default = 30; static int diff_dirstat_permille_default = 30;
static struct diff_options default_diff_options; static struct diff_options default_diff_options;
@ -156,6 +157,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
diff_no_prefix = git_config_bool(var, value); diff_no_prefix = git_config_bool(var, value);
return 0; return 0;
} }
if (!strcmp(var, "diff.statgraphwidth")) {
diff_stat_graph_width = git_config_int(var, value);
return 0;
}
if (!strcmp(var, "diff.external")) if (!strcmp(var, "diff.external"))
return git_config_string(&external_diff_cmd_cfg, var, value); return git_config_string(&external_diff_cmd_cfg, var, value);
if (!strcmp(var, "diff.wordregex")) if (!strcmp(var, "diff.wordregex"))
@ -1375,7 +1380,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
int i, len, add, del, adds = 0, dels = 0; int i, len, add, del, adds = 0, dels = 0;
uintmax_t max_change = 0, max_len = 0; uintmax_t max_change = 0, max_len = 0;
int total_files = data->nr; int total_files = data->nr;
int width, name_width, count; int width, name_width, graph_width, number_width = 4, count;
const char *reset, *add_c, *del_c; const char *reset, *add_c, *del_c;
const char *line_prefix = ""; const char *line_prefix = "";
int extra_shown = 0; int extra_shown = 0;
@ -1389,25 +1394,15 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
line_prefix = msg->buf; line_prefix = msg->buf;
} }
width = options->stat_width ? options->stat_width : 80;
name_width = options->stat_name_width ? options->stat_name_width : 50;
count = options->stat_count ? options->stat_count : data->nr; count = options->stat_count ? options->stat_count : data->nr;
/* Sanity: give at least 5 columns to the graph,
* but leave at least 10 columns for the name.
*/
if (width < 25)
width = 25;
if (name_width < 10)
name_width = 10;
else if (width < name_width + 15)
name_width = width - 15;
/* Find the longest filename and max number of changes */
reset = diff_get_color_opt(options, DIFF_RESET); reset = diff_get_color_opt(options, DIFF_RESET);
add_c = diff_get_color_opt(options, DIFF_FILE_NEW); add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
del_c = diff_get_color_opt(options, DIFF_FILE_OLD); del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
/*
* Find the longest filename and max number of changes
*/
for (i = 0; (i < count) && (i < data->nr); i++) { for (i = 0; (i < count) && (i < data->nr); i++) {
struct diffstat_file *file = data->files[i]; struct diffstat_file *file = data->files[i];
uintmax_t change = file->added + file->deleted; uintmax_t change = file->added + file->deleted;
@ -1428,19 +1423,72 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
} }
count = i; /* min(count, data->nr) */ count = i; /* min(count, data->nr) */
/* Compute the width of the graph part; /*
* 10 is for one blank at the beginning of the line plus * We have width = stat_width or term_columns() columns total.
* " | count " between the name and the graph. * We want a maximum of min(max_len, stat_name_width) for the name part.
* We want a maximum of min(max_change, stat_graph_width) for the +- part.
* We also need 1 for " " and 4 + decimal_width(max_change)
* for " | NNNN " and one the empty column at the end, altogether
* 6 + decimal_width(max_change).
* *
* From here on, name_width is the width of the name area, * If there's not enough space, we will use the smaller of
* and width is the width of the graph area. * stat_name_width (if set) and 5/8*width for the filename,
* and the rest for constant elements + graph part, but no more
* than stat_graph_width for the graph part.
* (5/8 gives 50 for filename and 30 for the constant parts + graph
* for the standard terminal size).
*
* In other words: stat_width limits the maximum width, and
* stat_name_width fixes the maximum width of the filename,
* and is also used to divide available columns if there
* aren't enough.
*/ */
name_width = (name_width < max_len) ? name_width : max_len;
if (width < (name_width + 10) + max_change)
width = width - (name_width + 10);
else
width = max_change;
if (options->stat_width == -1)
width = term_columns();
else
width = options->stat_width ? options->stat_width : 80;
if (options->stat_graph_width == -1)
options->stat_graph_width = diff_stat_graph_width;
/*
* Guarantee 3/8*16==6 for the graph part
* and 5/8*16==10 for the filename part
*/
if (width < 16 + 6 + number_width)
width = 16 + 6 + number_width;
/*
* First assign sizes that are wanted, ignoring available width.
*/
graph_width = (options->stat_graph_width &&
options->stat_graph_width < max_change) ?
options->stat_graph_width : max_change;
name_width = (options->stat_name_width > 0 &&
options->stat_name_width < max_len) ?
options->stat_name_width : max_len;
/*
* Adjust adjustable widths not to exceed maximum width
*/
if (name_width + number_width + 6 + graph_width > width) {
if (graph_width > width * 3/8 - number_width - 6)
graph_width = width * 3/8 - number_width - 6;
if (options->stat_graph_width &&
graph_width > options->stat_graph_width)
graph_width = options->stat_graph_width;
if (name_width > width - number_width - 6 - graph_width)
name_width = width - number_width - 6 - graph_width;
else
graph_width = width - number_width - 6 - name_width;
}
/*
* From here name_width is the width of the name area,
* and graph_width is the width of the graph area.
* max_change is used to scale graph properly.
*/
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
const char *prefix = ""; const char *prefix = "";
char *name = data->files[i]->print_name; char *name = data->files[i]->print_name;
@ -1496,18 +1544,18 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
adds += add; adds += add;
dels += del; dels += del;
if (width <= max_change) { if (graph_width <= max_change) {
int total = add + del; int total = add + del;
total = scale_linear(add + del, width, max_change); total = scale_linear(add + del, graph_width, max_change);
if (total < 2 && add && del) if (total < 2 && add && del)
/* width >= 2 due to the sanity check */ /* width >= 2 due to the sanity check */
total = 2; total = 2;
if (add < del) { if (add < del) {
add = scale_linear(add, width, max_change); add = scale_linear(add, graph_width, max_change);
del = total - add; del = total - add;
} else { } else {
del = scale_linear(del, width, max_change); del = scale_linear(del, graph_width, max_change);
add = total - del; add = total - del;
} }
} }
@ -3299,6 +3347,7 @@ static int stat_opt(struct diff_options *options, const char **av)
char *end; char *end;
int width = options->stat_width; int width = options->stat_width;
int name_width = options->stat_name_width; int name_width = options->stat_name_width;
int graph_width = options->stat_graph_width;
int count = options->stat_count; int count = options->stat_count;
int argcount = 1; int argcount = 1;
@ -3327,6 +3376,16 @@ static int stat_opt(struct diff_options *options, const char **av)
name_width = strtoul(av[1], &end, 10); name_width = strtoul(av[1], &end, 10);
argcount = 2; argcount = 2;
} }
} else if (!prefixcmp(arg, "-graph-width")) {
arg += strlen("-graph-width");
if (*arg == '=')
graph_width = strtoul(arg + 1, &end, 10);
else if (!*arg && !av[1])
die("Option '--stat-graph-width' requires a value");
else if (!*arg) {
graph_width = strtoul(av[1], &end, 10);
argcount = 2;
}
} else if (!prefixcmp(arg, "-count")) { } else if (!prefixcmp(arg, "-count")) {
arg += strlen("-count"); arg += strlen("-count");
if (*arg == '=') if (*arg == '=')
@ -3352,6 +3411,7 @@ static int stat_opt(struct diff_options *options, const char **av)
return 0; return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT; options->output_format |= DIFF_FORMAT_DIFFSTAT;
options->stat_name_width = name_width; options->stat_name_width = name_width;
options->stat_graph_width = graph_width;
options->stat_width = width; options->stat_width = width;
options->stat_count = count; options->stat_count = count;
return argcount; return argcount;

1
diff.h
View File

@ -129,6 +129,7 @@ struct diff_options {
int stat_width; int stat_width;
int stat_name_width; int stat_name_width;
int stat_graph_width;
int stat_count; int stat_count;
const char *word_regex; const char *word_regex;
enum diff_words_type word_diff; enum diff_words_type word_diff;

220
t/t4052-stat-output.sh Executable file
View File

@ -0,0 +1,220 @@
#!/bin/sh
#
# Copyright (c) 2012 Zbigniew Jędrzejewski-Szmek
#
test_description='test --stat output of various commands'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-terminal.sh
# 120 character name
name=aaaaaaaaaa
name=$name$name$name$name$name$name$name$name$name$name$name$name
test_expect_success 'preparation' '
>"$name" &&
git add "$name" &&
git commit -m message &&
echo a >"$name" &&
git commit -m message "$name"
'
while read cmd args
do
cat >expect <<-'EOF'
...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
test_expect_success "$cmd: small change with long name gives more space to the name" '
git $cmd $args >output &&
grep " | " output >actual &&
test_cmp expect actual
'
cat >expect <<-'EOF'
...aaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
test_expect_success "$cmd --stat=width: a long name is given more room when the bar is short" '
git $cmd $args --stat=40 >output &&
grep " | " output >actual &&
test_cmp expect actual
'
test_expect_success "$cmd --stat-width=width with long name" '
git $cmd $args --stat-width=40 >output &&
grep " | " output >actual &&
test_cmp expect actual
'
cat >expect <<-'EOF'
...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
test_expect_success "$cmd --stat=...,name-width with long name" '
git $cmd $args --stat=60,30 >output &&
grep " | " output >actual &&
test_cmp expect actual
'
test_expect_success "$cmd --stat-name-width with long name" '
git $cmd $args --stat-name-width=30 >output &&
grep " | " output >actual &&
test_cmp expect actual
'
done <<\EOF
format-patch -1 --stdout
diff HEAD^ HEAD --stat
show --stat
log -1 --stat
EOF
test_expect_success 'preparation for big change tests' '
>abcd &&
git add abcd &&
git commit -m message &&
i=0 &&
while test $i -lt 1000
do
echo $i && i=$(($i + 1))
done >abcd &&
git commit -m message abcd
'
cat >expect80 <<'EOF'
abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EOF
cat >expect200 <<'EOF'
abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EOF
while read verb expect cmd args
do
test_expect_success "$cmd $verb COLUMNS (big change)" '
COLUMNS=200 git $cmd $args >output
grep " | " output >actual &&
test_cmp "$expect" actual
'
done <<\EOF
ignores expect80 format-patch -1 --stdout
respects expect200 diff HEAD^ HEAD --stat
respects expect200 show --stat
respects expect200 log -1 --stat
EOF
cat >expect40 <<'EOF'
abcd | 1000 ++++++++++++++++++++++++++
EOF
while read verb expect cmd args
do
test_expect_success "$cmd $verb not enough COLUMNS (big change)" '
COLUMNS=40 git $cmd $args >output
grep " | " output >actual &&
test_cmp "$expect" actual
'
test_expect_success "$cmd $verb statGraphWidth config" '
git -c diff.statGraphWidth=26 $cmd $args >output
grep " | " output >actual &&
test_cmp "$expect" actual
'
done <<\EOF
ignores expect80 format-patch -1 --stdout
respects expect40 diff HEAD^ HEAD --stat
respects expect40 show --stat
respects expect40 log -1 --stat
EOF
cat >expect <<'EOF'
abcd | 1000 ++++++++++++++++++++++++++
EOF
while read cmd args
do
test_expect_success "$cmd --stat=width with big change" '
git $cmd $args --stat=40 >output
grep " | " output >actual &&
test_cmp expect actual
'
test_expect_success "$cmd --stat-width=width with big change" '
git $cmd $args --stat-width=40 >output
grep " | " output >actual &&
test_cmp expect actual
'
test_expect_success "$cmd --stat-graph--width with big change" '
git $cmd $args --stat-graph-width=26 >output
grep " | " output >actual &&
test_cmp expect actual
'
done <<\EOF
format-patch -1 --stdout
diff HEAD^ HEAD --stat
show --stat
log -1 --stat
EOF
test_expect_success 'preparation for long filename tests' '
cp abcd aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&
git add aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &&
git commit -m message
'
cat >expect <<'EOF'
...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++
EOF
while read cmd args
do
test_expect_success "$cmd --stat=width with big change is more balanced" '
git $cmd $args --stat-width=60 >output &&
grep " | " output >actual &&
test_cmp expect actual
'
done <<\EOF
format-patch -1 --stdout
diff HEAD^ HEAD --stat
show --stat
log -1 --stat
EOF
cat >expect80 <<'EOF'
...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++
EOF
cat >expect200 <<'EOF'
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EOF
while read verb expect cmd args
do
test_expect_success "$cmd $verb COLUMNS (long filename)" '
COLUMNS=200 git $cmd $args >output
grep " | " output >actual &&
test_cmp "$expect" actual
'
done <<\EOF
ignores expect80 format-patch -1 --stdout
respects expect200 diff HEAD^ HEAD --stat
respects expect200 show --stat
respects expect200 log -1 --stat
EOF
cat >expect <<'EOF'
abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EOF
test_expect_success 'merge --stat respects COLUMNS (big change)' '
git checkout -b branch HEAD^^ &&
COLUMNS=100 git merge --stat --no-ff master^ >output &&
grep " | " output >actual
test_cmp expect actual
'
cat >expect <<'EOF'
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++
EOF
test_expect_success 'merge --stat respects COLUMNS (long filename)' '
COLUMNS=100 git merge --stat --no-ff master >output &&
grep " | " output >actual
test_cmp expect actual
'
test_done