Code

Merge branch 'zj/diff-stat-dyncol'
authorJunio C Hamano <gitster@pobox.com>
Tue, 6 Mar 2012 22:53:06 +0000 (14:53 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 6 Mar 2012 22:53:06 +0000 (14:53 -0800)
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

Documentation/diff-config.txt
Documentation/diff-options.txt
builtin/diff.c
builtin/log.c
builtin/merge.c
contrib/completion/git-completion.bash
diff.c
diff.h
t/t4052-stat-output.sh [new file with mode: 0755]

index 1aed79e7dc451f02e1b15dd0644c89fee6071703..6aa1be04787b998d12b873afc98d9b498c942363 100644 (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:
 `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::
        If this config variable is set, diff generation is not
        performed using the internal diff machinery, but using the
index ba7cd13483194399762b0f48e218ddb4011dfcf1..7d4566f829e860758328309c377608a1083b0298 100644 (file)
@@ -56,13 +56,19 @@ endif::git-format-patch[]
        Generate a diff using the "histogram diff" algorithm.
 
 --stat[=<width>[,<name-width>[,<count>]]]::
-       Generate a diffstat.  You can override the default
-       output width for 80-column terminal by `--stat=<width>`.
-       The width of the filename part can be controlled by
-       giving another width to it separated by a comma.
+       Generate a diffstat. By default, as much space as necessary
+       will be used for the filename part, and the rest for the graph
+       part. Maximum width defaults to terminal width, or 80 columns
+       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
-       output to the first `<count>` lines, followed by
-       `...` if there are more.
+       output to the first `<count>` lines, followed by `...` if
+       there are more.
 +
 These parameters can also be set individually with `--stat-width=<width>`,
 `--stat-name-width=<name-width>` and `--stat-count=<count>`.
index 387afa75680d813bccd5493e5946600c08679890..424c815f9bc2ca8f87eb4694d1375b949b635170 100644 (file)
@@ -285,6 +285,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        /* Otherwise, we are doing the usual "git" diff */
        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 */
        DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
index 7d1f6f88a0e0f76bc8ee35155a532b641c14a845..8a47012b0bd2fefe616c44b918d16a18463b5d2a 100644 (file)
@@ -77,6 +77,8 @@ static void cmd_log_init_defaults(struct rev_info *rev)
                get_commit_format(fmt_pretty, rev);
        rev->verbose_header = 1;
        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->show_root_diff = default_show_root;
        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.always_show_header = 1;
        rev.no_walk = 1;
+       rev.diffopt.stat_width = -1;    /* Scale to real terminal size */
+
        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
        opt.tweak = show_rev_tweak_rev;
index d3e1e8dc9e478aaea7ef4da855fb7c2e10397644..cb8f14910b93edb7f1bfa6a4ee82f6a3d9fd7e40 100644 (file)
@@ -399,6 +399,8 @@ static void finish(struct commit *head_commit,
        if (new_head && show_diffstat) {
                struct diff_options opts;
                diff_setup(&opts);
+               opts.stat_width = -1; /* use full terminal width */
+               opts.stat_graph_width = -1; /* respect statGraphWidth config */
                opts.output_format |=
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
index 33f0e4dd696c73ffca2b9d2c3955661f1cdd00cd..fba076dde27440eaa90fd4fcfdfa55764410dd11 100755 (executable)
@@ -2092,6 +2092,7 @@ _git_config ()
                core.whitespace
                core.worktree
                diff.autorefreshindex
+               diff.statGraphWidth
                diff.external
                diff.ignoreSubmodules
                diff.mnemonicprefix
diff --git a/diff.c b/diff.c
index a1c06b554b80e433dcb735c8e0adf186b6252ae5..f47bffa2a3641cbf69b2e6d3f0f6c93b05f2c716 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -31,6 +31,7 @@ static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 static int diff_no_prefix;
+static int diff_stat_graph_width;
 static int diff_dirstat_permille_default = 30;
 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);
                return 0;
        }
+       if (!strcmp(var, "diff.statgraphwidth")) {
+               diff_stat_graph_width = git_config_int(var, value);
+               return 0;
+       }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
        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;
        uintmax_t max_change = 0, max_len = 0;
        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 *line_prefix = "";
        int extra_shown = 0;
@@ -1389,25 +1394,15 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                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;
 
-       /* 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);
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        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++) {
                struct diffstat_file *file = data->files[i];
                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) */
 
-       /* 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.
+       /*
+        * We have width = stat_width or term_columns() columns total.
+        * 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).
+        *
+        * If there's not enough space, we will use the smaller of
+        * 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).
         *
-        * From here on, name_width is the width of the name area,
-        * and width is the width of the graph area.
+        * 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);
+
+       if (options->stat_width == -1)
+               width = term_columns();
        else
-               width = max_change;
+               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++) {
                const char *prefix = "";
                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;
                dels += del;
 
-               if (width <= max_change) {
+               if (graph_width <= max_change) {
                        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)
                                /* width >= 2 due to the sanity check */
                                total = 2;
                        if (add < del) {
-                               add = scale_linear(add, width, max_change);
+                               add = scale_linear(add, graph_width, max_change);
                                del = total - add;
                        } else {
-                               del = scale_linear(del, width, max_change);
+                               del = scale_linear(del, graph_width, max_change);
                                add = total - del;
                        }
                }
@@ -3299,6 +3347,7 @@ static int stat_opt(struct diff_options *options, const char **av)
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
+       int graph_width = options->stat_graph_width;
        int count = options->stat_count;
        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);
                                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")) {
                        arg += strlen("-count");
                        if (*arg == '=')
@@ -3352,6 +3411,7 @@ static int stat_opt(struct diff_options *options, const char **av)
                return 0;
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
+       options->stat_graph_width = graph_width;
        options->stat_width = width;
        options->stat_count = count;
        return argcount;
diff --git a/diff.h b/diff.h
index 7af5f1e2a7af3fde64ac945f8b2a9ed88829895a..e6a782c220865d764eb26cfbce471cad88b910ba 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -129,6 +129,7 @@ struct diff_options {
 
        int stat_width;
        int stat_name_width;
+       int stat_graph_width;
        int stat_count;
        const char *word_regex;
        enum diff_words_type word_diff;
diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh
new file mode 100755 (executable)
index 0000000..328aa8f
--- /dev/null
@@ -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