Merge branch 'nd/columns'
authorJunio C Hamano <gitster@pobox.com>
Thu, 3 May 2012 22:13:31 +0000 (15:13 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 3 May 2012 22:13:31 +0000 (15:13 -0700)
A couple of commands learn --column option to produce columnar output.

By Nguyễn Thái Ngọc Duy (9) and Zbigniew Jędrzejewski-Szmek (1)
* nd/columns:
tag: add --column
column: support piping stdout to external git-column process
status: add --column
branch: add --column
help: reuse print_columns() for help -a
column: add dense layout support
t9002: work around shells that are unable to set COLUMNS to 1
column: add columnar layout
Stop starting pager recursively
Add column layout skeleton and git-column

27 files changed:
.gitignore
Documentation/config.txt
Documentation/git-branch.txt
Documentation/git-column.txt [new file with mode: 0644]
Documentation/git-status.txt
Documentation/git-tag.txt
Makefile
builtin.h
builtin/branch.c
builtin/column.c [new file with mode: 0644]
builtin/commit.c
builtin/help.c
builtin/tag.c
column.c [new file with mode: 0644]
column.h [new file with mode: 0644]
command-list.txt
git.c
help.c
help.h
pager.c
parse-options.h
t/t3200-branch.sh
t/t7004-tag.sh
t/t7508-status.sh
t/t9002-column.sh [new file with mode: 0755]
wt-status.c
wt-status.h
index 1dbeb668dbdcf13ccdbb532e4314c901ae6f7d1c..bf66648e2c5f59cb92470dbda546c5348bcc85ef 100644 (file)
@@ -26,6 +26,7 @@
 /git-cherry-pick
 /git-clean
 /git-clone
+/git-column
 /git-commit
 /git-commit-tree
 /git-config
index 355ee53652aad455a94df81096a7b4ef3fefdd14..915cb5a547896966377e9f39059527e3142b27c8 100644 (file)
@@ -856,6 +856,44 @@ color.ui::
        `never` if you prefer git commands not to use color unless enabled
        explicitly with some other configuration or the `--color` option.
 
+column.ui::
+       Specify whether supported commands should output in columns.
+       This variable consists of a list of tokens separated by spaces
+       or commas:
++
+--
+`always`;;
+       always show in columns
+`never`;;
+       never show in columns
+`auto`;;
+       show in columns if the output is to the terminal
+`column`;;
+       fill columns before rows (default)
+`row`;;
+       fill rows before columns
+`plain`;;
+       show in one column
+`dense`;;
+       make unequal size columns to utilize more space
+`nodense`;;
+       make equal size columns
+--
++
+       This option defaults to 'never'.
+
+column.branch::
+       Specify whether to output branch listing in `git branch` in columns.
+       See `column.ui` for details.
+
+column.status::
+       Specify whether to output untracked files in `git status` in columns.
+       See `column.ui` for details.
+
+column.tag::
+       Specify whether to output tag listing in `git tag` in columns.
+       See `column.ui` for details.
+
 commit.status::
        A boolean to enable/disable inclusion of status information in the
        commit message template when using an editor to prepare the commit
index e71370d6b49f58ae479318b89834b4829b45d423..47235bea0403bf25fa73529a37a7c5f87d0d7eb9 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 [verse]
 'git branch' [--color[=<when>] | --no-color] [-r | -a]
        [--list] [-v [--abbrev=<length> | --no-abbrev]]
+       [--column[=<options>] | --no-column]
        [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -107,6 +108,14 @@ OPTIONS
        default to color output.
        Same as `--color=never`.
 
+--column[=<options>]::
+--no-column::
+       Display branch listing in columns. See configuration variable
+       column.branch for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable in non-verbose mode.
+
 -r::
 --remotes::
        List or delete (if used with -d) the remote-tracking branches.
diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt
new file mode 100644 (file)
index 0000000..9be16ee
--- /dev/null
@@ -0,0 +1,53 @@
+git-column(1)
+=============
+
+NAME
+----
+git-column - Display data in columns
+
+SYNOPSIS
+--------
+[verse]
+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]
+            [--indent=<string>] [--nl=<string>] [--pading=<n>]
+
+DESCRIPTION
+-----------
+This command formats its input into multiple columns.
+
+OPTIONS
+-------
+--command=<name>::
+       Look up layout mode using configuration variable column.<name> and
+       column.ui.
+
+--mode=<mode>::
+       Specify layout mode. See configuration variable column.ui for option
+       syntax.
+
+--raw-mode=<n>::
+       Same as --mode but take mode encoded as a number. This is mainly used
+       by other commands that have already parsed layout mode.
+
+--width=<width>::
+       Specify the terminal width. By default 'git column' will detect the
+       terminal width, or fall back to 80 if it is unable to do so.
+
+--indent=<string>::
+       String to be printed at the beginning of each line.
+
+--nl=<N>::
+       String to be printed at the end of each line,
+       including newline character.
+
+--padding=<N>::
+       The number of spaces between columns. One space by default.
+
+
+Author
+------
+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>
+
+GIT
+---
+Part of the linkgit:git[1] suite
index a29aae60cded28263785b3f35640246837b0df30..2883a285bae11598b7b1539c87f7aecb2a5ab42c 100644 (file)
@@ -77,6 +77,13 @@ configuration variable documented in linkgit:git-config[1].
        Terminate entries with NUL, instead of LF.  This implies
        the `--porcelain` output format if no other format is given.
 
+--column[=<options>]::
+--no-column::
+       Display untracked files in columns. See configuration variable
+       column.status for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never'
+       respectively.
+
 
 OUTPUT
 ------
index 8d32b9a814675c9ebb58c25615a7f647dae20c93..e36a7c3d1e11e38950c74b32ab4f1ac9a4683a41 100644 (file)
@@ -13,6 +13,7 @@ SYNOPSIS
        <tagname> [<commit> | <object>]
 'git tag' -d <tagname>...
 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]
+       [--column[=<options>] | --no-column] [<pattern>...]
        [<pattern>...]
 'git tag' -v <tagname>...
 
@@ -84,6 +85,14 @@ OPTIONS
        using fnmatch(3)).  Multiple patterns may be given; if any of
        them matches, the tag is shown.
 
+--column[=<options>]::
+--no-column::
+       Display tag listing in columns. See configuration variable
+       column.tag for option syntax.`--column` and `--no-column`
+       without options are equivalent to 'always' and 'never' respectively.
++
+This option is only applicable when listing tags without annotation lines.
+
 --contains <commit>::
        Only list tags which contain the specified commit.
 
index bdf2a578df684224c640332861f7093a38cbe8f3..ac40e24625f7b1c53fc9d2e7658abb6d772bf5b3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -688,6 +688,7 @@ LIB_OBJS += bulk-checkin.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
 LIB_OBJS += color.o
+LIB_OBJS += column.o
 LIB_OBJS += combine-diff.o
 LIB_OBJS += commit.o
 LIB_OBJS += compat/obstack.o
@@ -818,6 +819,7 @@ BUILTIN_OBJS += builtin/checkout-index.o
 BUILTIN_OBJS += builtin/checkout.o
 BUILTIN_OBJS += builtin/clean.o
 BUILTIN_OBJS += builtin/clone.o
+BUILTIN_OBJS += builtin/column.o
 BUILTIN_OBJS += builtin/commit-tree.o
 BUILTIN_OBJS += builtin/commit.o
 BUILTIN_OBJS += builtin/config.o
@@ -2224,6 +2226,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h
 builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
 builtin/tar-tree.o archive-tar.o: tar.h
 connect.o transport.o url.o http-backend.o: url.h
+builtin/branch.o builtin/commit.o builtin/tag.o column.o help.o pager.o: column.h
 http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
 http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
 
index 857b9c8aa85fff5764b528485a880cd9dfa95b17..338f540e39af7093b39668a3bed16158a4483566 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
 extern int cmd_clone(int argc, const char **argv, const char *prefix);
 extern int cmd_clean(int argc, const char **argv, const char *prefix);
+extern int cmd_column(int argc, const char **argv, const char *prefix);
 extern int cmd_commit(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_config(int argc, const char **argv, const char *prefix);
index f12b626a0ece679cd18a77345c3591b15d5f6cbc..d51648fee4e73ef7fb72f32c722b79a913d6b275 100644 (file)
@@ -15,6 +15,8 @@
 #include "branch.h"
 #include "diff.h"
 #include "revision.h"
+#include "string-list.h"
+#include "column.h"
 
 static const char * const builtin_branch_usage[] = {
        "git branch [options] [-r | -a] [--merged | --no-merged]",
@@ -53,6 +55,9 @@ static enum merge_filter {
 } merge_filter;
 static unsigned char merge_filter_ref[20];
 
+static struct string_list output = STRING_LIST_INIT_DUP;
+static unsigned int colopts;
+
 static int parse_branch_color_slot(const char *var, int ofs)
 {
        if (!strcasecmp(var+ofs, "plain"))
@@ -70,6 +75,8 @@ static int parse_branch_color_slot(const char *var, int ofs)
 
 static int git_branch_config(const char *var, const char *value, void *cb)
 {
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "branch", &colopts);
        if (!strcmp(var, "color.branch")) {
                branch_use_color = git_config_colorbool(var, value);
                return 0;
@@ -482,7 +489,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
        else if (verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
                add_verbose_info(&out, item, verbose, abbrev);
-       printf("%s\n", out.buf);
+       if (column_active(colopts)) {
+               assert(!verbose && "--column and --verbose are incompatible");
+               string_list_append(&output, out.buf);
+       } else {
+               printf("%s\n", out.buf);
+       }
        strbuf_release(&name);
        strbuf_release(&out);
 }
@@ -741,6 +753,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,
                        opt_parse_merge_filter, (intptr_t) "HEAD",
                },
+               OPT_COLUMN(0, "column", &colopts, "list branches in columns"),
                OPT_END(),
        };
 
@@ -763,6 +776,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        }
        hashcpy(merge_filter_ref, head_sha1);
 
+
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
 
@@ -774,12 +788,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        if (abbrev == -1)
                abbrev = DEFAULT_ABBREV;
+       finalize_colopts(&colopts, -1);
+       if (verbose) {
+               if (explicitly_enable_column(colopts))
+                       die(_("--column and --verbose are incompatible"));
+               colopts = 0;
+       }
 
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds, quiet);
-       else if (list)
-               return print_ref_list(kinds, detached, verbose, abbrev,
-                                     with_commit, argv);
+       else if (list) {
+               int ret = print_ref_list(kinds, detached, verbose, abbrev,
+                                        with_commit, argv);
+               print_columns(&output, colopts, NULL);
+               string_list_clear(&output, 0);
+               return ret;
+       }
        else if (edit_description) {
                const char *branch_name;
                struct strbuf branch_ref = STRBUF_INIT;
diff --git a/builtin/column.c b/builtin/column.c
new file mode 100644 (file)
index 0000000..5ea798a
--- /dev/null
@@ -0,0 +1,59 @@
+#include "builtin.h"
+#include "cache.h"
+#include "strbuf.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "column.h"
+
+static const char * const builtin_column_usage[] = {
+       "git column [options]",
+       NULL
+};
+static unsigned int colopts;
+
+static int column_config(const char *var, const char *value, void *cb)
+{
+       return git_column_config(var, value, cb, &colopts);
+}
+
+int cmd_column(int argc, const char **argv, const char *prefix)
+{
+       struct string_list list = STRING_LIST_INIT_DUP;
+       struct strbuf sb = STRBUF_INIT;
+       struct column_options copts;
+       const char *command = NULL, *real_command = NULL;
+       struct option options[] = {
+               OPT_STRING(0, "command", &real_command, "name", "lookup config vars"),
+               OPT_COLUMN(0, "mode", &colopts, "layout to use"),
+               OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"),
+               OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
+               OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"),
+               OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"),
+               OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"),
+               OPT_END()
+       };
+
+       /* This one is special and must be the first one */
+       if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
+               command = argv[1] + 10;
+               git_config(column_config, (void *)command);
+       } else
+               git_config(column_config, NULL);
+
+       memset(&copts, 0, sizeof(copts));
+       copts.width = term_columns();
+       copts.padding = 1;
+       argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
+       if (argc)
+               usage_with_options(builtin_column_usage, options);
+       if (real_command || command) {
+               if (!real_command || !command || strcmp(real_command, command))
+                       die(_("--command must be the first argument"));
+       }
+       finalize_colopts(&colopts, -1);
+       while (!strbuf_getline(&sb, stdin, '\n'))
+               string_list_append(&list, sb.buf);
+
+       print_columns(&list, colopts, &copts);
+       return 0;
+}
index 01780293aa6afcd81e2f37490b59e44cf553bdca..a876a73e6b4c1f1690839ce2d447a4b77573c9f5 100644 (file)
@@ -27,6 +27,7 @@
 #include "quote.h"
 #include "submodule.h"
 #include "gpg-interface.h"
+#include "column.h"
 
 static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@ -88,6 +89,7 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
 static char *sign_commit;
+static unsigned int colopts;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -1173,6 +1175,8 @@ static int git_status_config(const char *k, const char *v, void *cb)
 {
        struct wt_status *s = cb;
 
+       if (!prefixcmp(k, "column."))
+               return git_column_config(k, v, "status", &colopts);
        if (!strcmp(k, "status.submodulesummary")) {
                int is_bool;
                s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
@@ -1238,6 +1242,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
                  "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_COLUMN(0, "column", &colopts, "list untracked files in columns"),
                OPT_END(),
        };
 
@@ -1251,6 +1256,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
+       finalize_colopts(&colopts, -1);
+       s.colopts = colopts;
 
        if (null_termination && status_format == STATUS_FORMAT_LONG)
                status_format = STATUS_FORMAT_PORCELAIN;
index e63668ade4dd0721c27ca5045bc8a524a147f9c1..43d3c84449a57ec7028acb663d4ab4bea134c97e 100644 (file)
@@ -9,6 +9,7 @@
 #include "common-cmds.h"
 #include "parse-options.h"
 #include "run-command.h"
+#include "column.h"
 #include "help.h"
 
 static struct man_viewer_list {
@@ -30,6 +31,7 @@ enum help_format {
 };
 
 static int show_all = 0;
+static unsigned int colopts;
 static enum help_format help_format = HELP_FORMAT_NONE;
 static struct option builtin_help_options[] = {
        OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
@@ -251,6 +253,8 @@ static int add_man_viewer_info(const char *var, const char *value)
 
 static int git_help_config(const char *var, const char *value, void *cb)
 {
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "help", &colopts);
        if (!strcmp(var, "help.format")) {
                if (!value)
                        return config_error_nonbool(var);
@@ -424,8 +428,9 @@ int cmd_help(int argc, const char **argv, const char *prefix)
        parsed_help_format = help_format;
 
        if (show_all) {
+               git_config(git_help_config, NULL);
                printf(_("usage: %s%s"), _(git_usage_string), "\n\n");
-               list_commands(&main_cmds, &other_cmds);
+               list_commands(colopts, &main_cmds, &other_cmds);
                printf("%s\n", _(git_more_info_string));
                return 0;
        }
index fe7e5e5b3d64dc8168ebe687ac5c6c2d1cbbdba1..4fb6bd7b3dd39dadbbb2eb3bfb8a8c3dc96e8484 100644 (file)
@@ -16,6 +16,7 @@
 #include "revision.h"
 #include "gpg-interface.h"
 #include "sha1-array.h"
+#include "column.h"
 
 static const char * const git_tag_usage[] = {
        "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
@@ -33,6 +34,7 @@ struct tag_filter {
 };
 
 static struct sha1_array points_at;
+static unsigned int colopts;
 
 static int match_pattern(const char **patterns, const char *ref)
 {
@@ -263,6 +265,8 @@ static int git_tag_config(const char *var, const char *value, void *cb)
        int status = git_gpg_config(var, value, cb);
        if (status)
                return status;
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "tag", &colopts);
        return git_default_config(var, value, cb);
 }
 
@@ -459,6 +463,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                OPT_STRING('u', "local-user", &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT__FORCE(&force, "replace the tag if exists"),
+               OPT_COLUMN(0, "column", &colopts, "show tag list in columns"),
 
                OPT_GROUP("Tag listing options"),
                {
@@ -495,9 +500,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
        if (list + delete + verify > 1)
                usage_with_options(git_tag_usage, options);
-       if (list)
-               return list_tags(argv, lines == -1 ? 0 : lines,
-                                with_commit);
+       finalize_colopts(&colopts, -1);
+       if (list && lines != -1) {
+               if (explicitly_enable_column(colopts))
+                       die(_("--column and -n are incompatible"));
+               colopts = 0;
+       }
+       if (list) {
+               int ret;
+               if (column_active(colopts)) {
+                       struct column_options copts;
+                       memset(&copts, 0, sizeof(copts));
+                       copts.padding = 2;
+                       run_column_filter(colopts, &copts);
+               }
+               ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);
+               if (column_active(colopts))
+                       stop_column_filter();
+               return ret;
+       }
        if (lines != -1)
                die(_("-n option is only allowed with -l."));
        if (with_commit)
diff --git a/column.c b/column.c
new file mode 100644 (file)
index 0000000..9367ba5
--- /dev/null
+++ b/column.c
@@ -0,0 +1,434 @@
+#include "cache.h"
+#include "column.h"
+#include "string-list.h"
+#include "parse-options.h"
+#include "run-command.h"
+#include "utf8.h"
+
+#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \
+                           (x) * (d)->rows + (y) : \
+                           (y) * (d)->cols + (x))
+
+struct column_data {
+       const struct string_list *list;
+       unsigned int colopts;
+       struct column_options opts;
+
+       int rows, cols;
+       int *len;               /* cell length */
+       int *width;           /* index to the longest row in column */
+};
+
+/* return length of 's' in letters, ANSI escapes stripped */
+static int item_length(unsigned int colopts, const char *s)
+{
+       int len, i = 0;
+       struct strbuf str = STRBUF_INIT;
+
+       strbuf_addstr(&str, s);
+       while ((s = strstr(str.buf + i, "\033[")) != NULL) {
+               int len = strspn(s + 2, "0123456789;");
+               i = s - str.buf;
+               strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
+       }
+       len = utf8_strwidth(str.buf);
+       strbuf_release(&str);
+       return len;
+}
+
+/*
+ * Calculate cell width, rows and cols for a table of equal cells, given
+ * table width and how many spaces between cells.
+ */
+static void layout(struct column_data *data, int *width)
+{
+       int i;
+
+       *width = 0;
+       for (i = 0; i < data->list->nr; i++)
+               if (*width < data->len[i])
+                       *width = data->len[i];
+
+       *width += data->opts.padding;
+
+       data->cols = (data->opts.width - strlen(data->opts.indent)) / *width;
+       if (data->cols == 0)
+               data->cols = 1;
+
+       data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
+}
+
+static void compute_column_width(struct column_data *data)
+{
+       int i, x, y;
+       for (x = 0; x < data->cols; x++) {
+               data->width[x] = XY2LINEAR(data, x, 0);
+               for (y = 0; y < data->rows; y++) {
+                       i = XY2LINEAR(data, x, y);
+                       if (i < data->list->nr &&
+                           data->len[data->width[x]] < data->len[i])
+                               data->width[x] = i;
+               }
+       }
+}
+
+/*
+ * Shrink all columns by shortening them one row each time (and adding
+ * more columns along the way). Hopefully the longest cell will be
+ * moved to the next column, column is shrunk so we have more space
+ * for new columns. The process ends when the whole thing no longer
+ * fits in data->total_width.
+ */
+static void shrink_columns(struct column_data *data)
+{
+       data->width = xrealloc(data->width,
+                              sizeof(*data->width) * data->cols);
+       while (data->rows > 1) {
+               int x, total_width, cols, rows;
+               rows = data->rows;
+               cols = data->cols;
+
+               data->rows--;
+               data->cols = DIV_ROUND_UP(data->list->nr, data->rows);
+               if (data->cols != cols)
+                       data->width = xrealloc(data->width,
+                                              sizeof(*data->width) * data->cols);
+               compute_column_width(data);
+
+               total_width = strlen(data->opts.indent);
+               for (x = 0; x < data->cols; x++) {
+                       total_width += data->len[data->width[x]];
+                       total_width += data->opts.padding;
+               }
+               if (total_width > data->opts.width) {
+                       data->rows = rows;
+                       data->cols = cols;
+                       break;
+               }
+       }
+       compute_column_width(data);
+}
+
+/* Display without layout when not enabled */
+static void display_plain(const struct string_list *list,
+                         const char *indent, const char *nl)
+{
+       int i;
+
+       for (i = 0; i < list->nr; i++)
+               printf("%s%s%s", indent, list->items[i].string, nl);
+}
+
+/* Print a cell to stdout with all necessary leading/traling space */
+static int display_cell(struct column_data *data, int initial_width,
+                       const char *empty_cell, int x, int y)
+{
+       int i, len, newline;
+
+       i = XY2LINEAR(data, x, y);
+       if (i >= data->list->nr)
+               return -1;
+
+       len = data->len[i];
+       if (data->width && data->len[data->width[x]] < initial_width) {
+               /*
+                * empty_cell has initial_width chars, if real column
+                * is narrower, increase len a bit so we fill less
+                * space.
+                */
+               len += initial_width - data->len[data->width[x]];
+               len -= data->opts.padding;
+       }
+
+       if (COL_LAYOUT(data->colopts) == COL_COLUMN)
+               newline = i + data->rows >= data->list->nr;
+       else
+               newline = x == data->cols - 1 || i == data->list->nr - 1;
+
+       printf("%s%s%s",
+              x == 0 ? data->opts.indent : "",
+              data->list->items[i].string,
+              newline ? data->opts.nl : empty_cell + len);
+       return 0;
+}
+
+/* Display COL_COLUMN or COL_ROW */
+static void display_table(const struct string_list *list,
+                         unsigned int colopts,
+                         const struct column_options *opts)
+{
+       struct column_data data;
+       int x, y, i, initial_width;
+       char *empty_cell;
+
+       memset(&data, 0, sizeof(data));
+       data.list = list;
+       data.colopts = colopts;
+       data.opts = *opts;
+
+       data.len = xmalloc(sizeof(*data.len) * list->nr);
+       for (i = 0; i < list->nr; i++)
+               data.len[i] = item_length(colopts, list->items[i].string);
+
+       layout(&data, &initial_width);
+
+       if (colopts & COL_DENSE)
+               shrink_columns(&data);
+
+       empty_cell = xmalloc(initial_width + 1);
+       memset(empty_cell, ' ', initial_width);
+       empty_cell[initial_width] = '\0';
+       for (y = 0; y < data.rows; y++) {
+               for (x = 0; x < data.cols; x++)
+                       if (display_cell(&data, initial_width, empty_cell, x, y))
+                               break;
+       }
+
+       free(data.len);
+       free(data.width);
+       free(empty_cell);
+}
+
+void print_columns(const struct string_list *list, unsigned int colopts,
+                  const struct column_options *opts)
+{
+       struct column_options nopts;
+
+       if (!list->nr)
+               return;
+       assert((colopts & COL_ENABLE_MASK) != COL_AUTO);
+
+       memset(&nopts, 0, sizeof(nopts));
+       nopts.indent = opts && opts->indent ? opts->indent : "";
+       nopts.nl = opts && opts->nl ? opts->nl : "\n";
+       nopts.padding = opts ? opts->padding : 1;
+       nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
+       if (!column_active(colopts)) {
+               display_plain(list, "", "\n");
+               return;
+       }
+       switch (COL_LAYOUT(colopts)) {
+       case COL_PLAIN:
+               display_plain(list, nopts.indent, nopts.nl);
+               break;
+       case COL_ROW:
+       case COL_COLUMN:
+               display_table(list, colopts, &nopts);
+               break;
+       default:
+               die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
+       }
+}
+
+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)
+{
+       if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {
+               if (stdout_is_tty < 0)
+                       stdout_is_tty = isatty(1);
+               *colopts &= ~COL_ENABLE_MASK;
+               if (stdout_is_tty)
+                       *colopts |= COL_ENABLED;
+       }
+       return 0;
+}
+
+struct colopt {
+       const char *name;
+       unsigned int value;
+       unsigned int mask;
+};
+
+#define LAYOUT_SET 1
+#define ENABLE_SET 2
+
+static int parse_option(const char *arg, int len, unsigned int *colopts,
+                       int *group_set)
+{
+       struct colopt opts[] = {
+               { "always", COL_ENABLED,  COL_ENABLE_MASK },
+               { "never",  COL_DISABLED, COL_ENABLE_MASK },
+               { "auto",   COL_AUTO,     COL_ENABLE_MASK },
+               { "plain",  COL_PLAIN,    COL_LAYOUT_MASK },
+               { "column", COL_COLUMN,   COL_LAYOUT_MASK },
+               { "row",    COL_ROW,      COL_LAYOUT_MASK },
+               { "dense",  COL_DENSE,    0 },
+       };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(opts); i++) {
+               int set = 1, arg_len = len, name_len;
+               const char *arg_str = arg;
+
+               if (!opts[i].mask) {
+                       if (arg_len > 2 && !strncmp(arg_str, "no", 2)) {
+                               arg_str += 2;
+                               arg_len -= 2;
+                               set = 0;
+                       }
+               }
+
+               name_len = strlen(opts[i].name);
+               if (arg_len != name_len ||
+                   strncmp(arg_str, opts[i].name, name_len))
+                       continue;
+
+               switch (opts[i].mask) {
+               case COL_ENABLE_MASK:
+                       *group_set |= ENABLE_SET;
+                       break;
+               case COL_LAYOUT_MASK:
+                       *group_set |= LAYOUT_SET;
+                       break;
+               }
+
+               if (opts[i].mask)
+                       *colopts = (*colopts & ~opts[i].mask) | opts[i].value;
+               else {
+                       if (set)
+                               *colopts |= opts[i].value;
+                       else
+                               *colopts &= ~opts[i].value;
+               }
+               return 0;
+       }
+
+       return error("unsupported option '%s'", arg);
+}
+
+static int parse_config(unsigned int *colopts, const char *value)
+{
+       const char *sep = " ,";
+       int group_set = 0;
+
+       while (*value) {
+               int len = strcspn(value, sep);
+               if (len) {
+                       if (parse_option(value, len, colopts, &group_set))
+                               return -1;
+
+                       value += len;
+               }
+               value += strspn(value, sep);
+       }
+       /*
+        * Setting layout implies "always" if neither always, never
+        * nor auto is specified.
+        *
+        * Current value in COL_ENABLE_MASK is disregarded. This means if
+        * you set column.ui = auto and pass --column=row, then "auto"
+        * will become "always".
+        */
+       if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))
+               *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+       return 0;
+}
+
+static int column_config(const char *var, const char *value,
+                        const char *key, unsigned int *colopts)
+{
+       if (!value)
+               return config_error_nonbool(var);
+       if (parse_config(colopts, value))
+               return error("invalid column.%s mode %s", key, value);
+       return 0;
+}
+
+int git_column_config(const char *var, const char *value,
+                     const char *command, unsigned int *colopts)
+{
+       const char *it = skip_prefix(var, "column.");
+       if (!it)
+               return 0;
+
+       if (!strcmp(it, "ui"))
+               return column_config(var, value, "ui", colopts);
+
+       if (command && !strcmp(it, command))
+               return column_config(var, value, it, colopts);
+
+       return 0;
+}
+
+int parseopt_column_callback(const struct option *opt,
+                            const char *arg, int unset)
+{
+       unsigned int *colopts = opt->value;
+       *colopts |= COL_PARSEOPT;
+       *colopts &= ~COL_ENABLE_MASK;
+       if (unset)              /* --no-column == never */
+               return 0;
+       /* --column == always unless "arg" states otherwise */
+       *colopts |= COL_ENABLED;
+       if (arg)
+               return parse_config(colopts, arg);
+
+       return 0;
+}
+
+static int fd_out = -1;
+static struct child_process column_process;
+
+int run_column_filter(int colopts, const struct column_options *opts)
+{
+       const char *av[10];
+       int ret, ac = 0;
+       struct strbuf sb_colopt  = STRBUF_INIT;
+       struct strbuf sb_width   = STRBUF_INIT;
+       struct strbuf sb_padding = STRBUF_INIT;
+
+       if (fd_out != -1)
+               return -1;
+
+       av[ac++] = "column";
+       strbuf_addf(&sb_colopt, "--raw-mode=%d", colopts);
+       av[ac++] = sb_colopt.buf;
+       if (opts && opts->width) {
+               strbuf_addf(&sb_width, "--width=%d", opts->width);
+               av[ac++] = sb_width.buf;
+       }
+       if (opts && opts->indent) {
+               av[ac++] = "--indent";
+               av[ac++] = opts->indent;
+       }
+       if (opts && opts->padding) {
+               strbuf_addf(&sb_padding, "--padding=%d", opts->padding);
+               av[ac++] = sb_padding.buf;
+       }
+       av[ac] = NULL;
+
+       fflush(stdout);
+       memset(&column_process, 0, sizeof(column_process));
+       column_process.in = -1;
+       column_process.out = dup(1);
+       column_process.git_cmd = 1;
+       column_process.argv = av;
+
+       ret = start_command(&column_process);
+
+       strbuf_release(&sb_colopt);
+       strbuf_release(&sb_width);
+       strbuf_release(&sb_padding);
+
+       if (ret)
+               return -2;
+
+       fd_out = dup(1);
+       close(1);
+       dup2(column_process.in, 1);
+       close(column_process.in);
+       return 0;
+}
+
+int stop_column_filter(void)
+{
+       if (fd_out == -1)
+               return -1;
+
+       fflush(stdout);
+       close(1);
+       finish_command(&column_process);
+       dup2(fd_out, 1);
+       close(fd_out);
+       fd_out = -1;
+       return 0;
+}
diff --git a/column.h b/column.h
new file mode 100644 (file)
index 0000000..0a61917
--- /dev/null
+++ b/column.h
@@ -0,0 +1,45 @@
+#ifndef COLUMN_H
+#define COLUMN_H
+
+#define COL_LAYOUT_MASK   0x000F
+#define COL_ENABLE_MASK   0x0030   /* always, never or auto */
+#define COL_PARSEOPT      0x0040   /* --column is given from cmdline */
+#define COL_DENSE         0x0080   /* Shrink columns when possible,
+                                     making space for more columns */
+
+#define COL_DISABLED      0x0000   /* must be zero */
+#define COL_ENABLED       0x0010
+#define COL_AUTO          0x0020
+
+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)
+#define COL_COLUMN             0   /* Fill columns before rows */
+#define COL_ROW                1   /* Fill rows before columns */
+#define COL_PLAIN             15   /* one column */
+
+#define explicitly_enable_column(c) \
+       (((c) & COL_PARSEOPT) && column_active(c))
+
+struct column_options {
+       int width;
+       int padding;
+       const char *indent;
+       const char *nl;
+};
+
+struct option;
+extern int parseopt_column_callback(const struct option *, const char *, int);
+extern int git_column_config(const char *var, const char *value,
+                            const char *command, unsigned int *colopts);
+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);
+static inline int column_active(unsigned int colopts)
+{
+       return (colopts & COL_ENABLE_MASK) == COL_ENABLED;
+}
+
+extern void print_columns(const struct string_list *list, unsigned int colopts,
+                         const struct column_options *opts);
+
+extern int run_column_filter(int colopts, const struct column_options *);
+extern int stop_column_filter(void);
+
+#endif
index 38ec5f7b8617ad66f95597ab1bfefce8480c39a3..14ea67af038fbfce388dd20b8adcda503106911d 100644 (file)
@@ -20,6 +20,7 @@ git-cherry-pick                         mainporcelain
 git-citool                              mainporcelain
 git-clean                               mainporcelain
 git-clone                               mainporcelain common
+git-column                              purehelpers
 git-commit                              mainporcelain common
 git-commit-tree                         plumbingmanipulators
 git-config                              ancillarymanipulators
diff --git a/git.c b/git.c
index 4486debf7dfb2db263cf88a825979505fee5c635..d232de92e496bd5750015edce22d3b67f236b605 100644 (file)
--- a/git.c
+++ b/git.c
@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
                { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
                { "clone", cmd_clone },
+               { "column", cmd_column, RUN_SETUP_GENTLY },
                { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config, RUN_SETUP_GENTLY },
diff --git a/help.c b/help.c
index a39b7bf8971e3ea54f0e0c0f675b3613b156eecd..69d483d8d3b7e109345b1e8124df6b023ed312fb 100644 (file)
--- a/help.c
+++ b/help.c
@@ -4,6 +4,8 @@
 #include "levenshtein.h"
 #include "help.h"
 #include "common-cmds.h"
+#include "string-list.h"
+#include "column.h"
 
 void add_cmdname(struct cmdnames *cmds, const char *name, int len)
 {
@@ -70,31 +72,25 @@ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
        cmds->cnt = cj;
 }
 
-static void pretty_print_string_list(struct cmdnames *cmds, int longest)
+static void pretty_print_string_list(struct cmdnames *cmds,
+                                    unsigned int colopts)
 {
-       int cols = 1, rows;
-       int space = longest + 1; /* min 1 SP between words */
-       int max_cols = term_columns() - 1; /* don't print *on* the edge */
-       int i, j;
-
-       if (space < max_cols)
-               cols = max_cols / space;
-       rows = DIV_ROUND_UP(cmds->cnt, cols);
-
-       for (i = 0; i < rows; i++) {
-               printf("  ");
+       struct string_list list = STRING_LIST_INIT_NODUP;
+       struct column_options copts;
+       int i;
 
-               for (j = 0; j < cols; j++) {
-                       int n = j * rows + i;
-                       int size = space;
-                       if (n >= cmds->cnt)
-                               break;
-                       if (j == cols-1 || n + rows >= cmds->cnt)
-                               size = 1;
-                       printf("%-*s", size, cmds->names[n]->name);
-               }
-               putchar('\n');
-       }
+       for (i = 0; i < cmds->cnt; i++)
+               string_list_append(&list, cmds->names[i]->name);
+       /*
+        * always enable column display, we only consult column.*
+        * about layout strategy and stuff
+        */
+       colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+       memset(&copts, 0, sizeof(copts));
+       copts.indent = "  ";
+       copts.padding = 2;
+       print_columns(&list, colopts, &copts);
+       string_list_clear(&list, 0);
 }
 
 static int is_executable(const char *name)
@@ -203,29 +199,21 @@ void load_command_list(const char *prefix,
        exclude_cmds(other_cmds, main_cmds);
 }
 
-void list_commands(struct cmdnames *main_cmds, struct cmdnames *other_cmds)
+void list_commands(unsigned int colopts,
+                  struct cmdnames *main_cmds, struct cmdnames *other_cmds)
 {
-       int i, longest = 0;
-
-       for (i = 0; i < main_cmds->cnt; i++)
-               if (longest < main_cmds->names[i]->len)
-                       longest = main_cmds->names[i]->len;
-       for (i = 0; i < other_cmds->cnt; i++)
-               if (longest < other_cmds->names[i]->len)
-                       longest = other_cmds->names[i]->len;
-
        if (main_cmds->cnt) {
                const char *exec_path = git_exec_path();
                printf_ln(_("available git commands in '%s'"), exec_path);
                putchar('\n');
-               pretty_print_string_list(main_cmds, longest);
+               pretty_print_string_list(main_cmds, colopts);
                putchar('\n');
        }
 
        if (other_cmds->cnt) {
                printf_ln(_("git commands available from elsewhere on your $PATH"));
                putchar('\n');
-               pretty_print_string_list(other_cmds, longest);
+               pretty_print_string_list(other_cmds, colopts);
                putchar('\n');
        }
 }
diff --git a/help.h b/help.h
index dc406c8c50ad2c851b76d446e829550c6c444631..0ae5a124a3af9912d551caed909aac77acc59b0b 100644 (file)
--- a/help.h
+++ b/help.h
@@ -25,7 +25,6 @@ extern void add_cmdname(struct cmdnames *cmds, const char *name, int len);
 /* Here we require that excludes is a sorted list. */
 extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
 extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);
-extern void list_commands(struct cmdnames *main_cmds,
-                         struct cmdnames *other_cmds);
+extern void list_commands(unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds);
 
 #endif /* HELP_H */
diff --git a/pager.c b/pager.c
index 05584dead6728ceff818630fbccaa91bb6c6b686..4dcb08d9674c252cbe2c2725f92eca2a0350dc93 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -73,7 +73,7 @@ void setup_pager(void)
 {
        const char *pager = git_pager(isatty(1));
 
-       if (!pager)
+       if (!pager || pager_in_use())
                return;
 
        /*
index def9ced739b675cbcdac7598c3baebf5423239a0..da999f8995ee79865bd4c2f72a1e009502a69926 100644 (file)
@@ -234,5 +234,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);
          PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
 #define OPT__COLOR(var, h) \
        OPT_COLOR_FLAG(0, "color", (var), (h))
+#define OPT_COLUMN(s, l, v, h) \
+       { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
 
 #endif
index 9fe1d8feab419e1a8065b2ea5881f991edc68855..a17f8b2a407c2de2bf81d8a858957da409f1c980 100755 (executable)
@@ -160,6 +160,83 @@ test_expect_success 'git branch --list -d t should fail' '
        test_path_is_missing .git/refs/heads/t
 '
 
+test_expect_success 'git branch --column' '
+       COLUMNS=81 git branch --column=column >actual &&
+       cat >expected <<\EOF &&
+  a/b/c     bam       foo       l       * master    n         o/p       r
+  abc       bar       j/k       m/m       master2   o/o       q
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'git branch --column with an extremely long branch name' '
+       long=this/is/a/part/of/long/branch/name &&
+       long=z$long/$long/$long/$long &&
+       test_when_finished "git branch -d $long" &&
+       git branch $long &&
+       COLUMNS=80 git branch --column=column >actual &&
+       cat >expected <<EOF &&
+  a/b/c
+  abc
+  bam
+  bar
+  foo
+  j/k
+  l
+  m/m
+* master
+  master2
+  n
+  o/o
+  o/p
+  q
+  r
+  $long
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'git branch with column.*' '
+       git config column.ui column &&
+       git config column.branch "dense" &&
+       COLUMNS=80 git branch >actual &&
+       git config --unset column.branch &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+  a/b/c   bam   foo   l   * master    n     o/p   r
+  abc     bar   j/k   m/m   master2   o/o   q
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'git branch --column -v should fail' '
+       test_must_fail git branch --column -v
+'
+
+test_expect_success 'git branch -v with column.ui ignored' '
+       git config column.ui column &&
+       COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+  a/b/c
+  abc
+  bam
+  bar
+  foo
+  j/k
+  l
+  m/m
+* master
+  master2
+  n
+  o/o
+  o/p
+  q
+  r
+EOF
+       test_cmp expected actual
+'
+
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '
index f8c247a7500d723e46796e7b9b76b9812e35db9b..518944653477225da94db0826d77936eaa0d249a 100755 (executable)
@@ -263,6 +263,50 @@ test_expect_success 'tag -l can accept multiple patterns' '
        test_cmp expect actual
 '
 
+test_expect_success 'listing tags in column' '
+       COLUMNS=40 git tag -l --column=row >actual &&
+       cat >expected <<\EOF &&
+a1      aa1     cba     t210    t211
+v0.2.1  v1.0    v1.0.1  v1.1.3
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'listing tags in column with column.*' '
+       git config column.tag row &&
+       git config column.ui dense &&
+       COLUMNS=40 git tag -l >actual &&
+       git config --unset column.ui &&
+       git config --unset column.tag &&
+       cat >expected <<\EOF &&
+a1      aa1   cba     t210    t211
+v0.2.1  v1.0  v1.0.1  v1.1.3
+EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'listing tag with -n --column should fail' '
+       test_must_fail git tag --column -n
+'
+
+test_expect_success 'listing tags -n in column with column.ui ignored' '
+       git config column.ui "row dense" &&
+       COLUMNS=40 git tag -l -n >actual &&
+       git config --unset column.ui &&
+       cat >expected <<\EOF &&
+a1              Foo
+aa1             Foo
+cba             Foo
+t210            Foo
+t211            Foo
+v0.2.1          Foo
+v1.0            Foo
+v1.0.1          Foo
+v1.1.3          Foo
+EOF
+       test_cmp expected actual
+'
+
 # creating and verifying lightweight tags:
 
 test_expect_success \
index fc57b135c50e34ab86c04d0a0ec81052ce40f8ff..8f5cfac33158f96d1397fecf965abcd43c119e8b 100755 (executable)
@@ -59,6 +59,30 @@ test_expect_success 'status (1)' '
        test_i18ngrep "use \"git rm --cached <file>\.\.\.\" to unstage" output
 '
 
+test_expect_success 'status --column' '
+       COLUMNS=50 git status --column="column dense" >output &&
+       cat >expect <<\EOF &&
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changes not staged for commit:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked dir2/untracked untracked
+#      dir2/modified  output
+EOF
+       test_cmp expect output
+'
+
 cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
diff --git a/t/t9002-column.sh b/t/t9002-column.sh
new file mode 100755 (executable)
index 0000000..8998352
--- /dev/null
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+test_description='git column'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       cat >lista <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+'
+
+test_expect_success 'never' '
+       git column --indent=Z --mode=never <lista >actual &&
+       test_cmp lista actual
+'
+
+test_expect_success 'always' '
+       cat >expected <<\EOF &&
+Zone
+Ztwo
+Zthree
+Zfour
+Zfive
+Zsix
+Zseven
+Zeight
+Znine
+Zten
+Zeleven
+EOF
+       git column --indent=Z --mode=plain <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '80 columns' '
+       cat >expected <<\EOF &&
+one    two    three  four   five   six    seven  eight  nine   ten    eleven
+EOF
+       COLUMNS=80 git column --mode=column <lista >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+one
+two
+three
+four
+five
+six
+seven
+eight
+nine
+ten
+eleven
+EOF
+
+test_expect_success COLUMNS_CAN_BE_1 'COLUMNS = 1' '
+       COLUMNS=1 git column --mode=column <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'width = 1' '
+       git column --mode=column --width=1 <lista >actual &&
+       test_cmp expected actual
+'
+
+COLUMNS=20
+export COLUMNS
+
+test_expect_success '20 columns' '
+       cat >expected <<\EOF &&
+one    seven
+two    eight
+three  nine
+four   ten
+five   eleven
+six
+EOF
+       git column --mode=column <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, nodense' '
+       cat >expected <<\EOF &&
+one    seven
+two    eight
+three  nine
+four   ten
+five   eleven
+six
+EOF
+       git column --mode=column,nodense < lista > actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, dense' '
+       cat >expected <<\EOF &&
+one   five  nine
+two   six   ten
+three seven eleven
+four  eight
+EOF
+       git column --mode=column,dense < lista > actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, padding 2' '
+       cat >expected <<\EOF &&
+one     seven
+two     eight
+three   nine
+four    ten
+five    eleven
+six
+EOF
+       git column --mode=column --padding 2 <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, indented' '
+       cat >expected <<\EOF &&
+  one    seven
+  two    eight
+  three  nine
+  four   ten
+  five   eleven
+  six
+EOF
+       git column --mode=column --indent="  " <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first' '
+       cat >expected <<\EOF &&
+one    two
+three  four
+five   six
+seven  eight
+nine   ten
+eleven
+EOF
+       git column --mode=row <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, nodense' '
+       cat >expected <<\EOF &&
+one    two
+three  four
+five   six
+seven  eight
+nine   ten
+eleven
+EOF
+       git column --mode=row,nodense <lista >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '20 columns, row first, dense' '
+       cat >expected <<\EOF &&
+one   two    three
+four  five   six
+seven eight  nine
+ten   eleven
+EOF
+       git column --mode=row,dense <lista >actual &&
+       test_cmp expected actual
+'
+
+test_done
index 9ffc535f1ab0296fd0a3330b1f136d69f9ac9bb2..eeef17e7b6b1bbab7355eda9ae81534239d13a7b 100644 (file)
@@ -11,6 +11,7 @@
 #include "remote.h"
 #include "refs.h"
 #include "submodule.h"
+#include "column.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -641,6 +642,8 @@ static void wt_status_print_other(struct wt_status *s,
 {
        int i;
        struct strbuf buf = STRBUF_INIT;
+       static struct string_list output = STRING_LIST_INIT_DUP;
+       struct column_options copts;
 
        if (!l->nr)
                return;
@@ -649,12 +652,33 @@ static void wt_status_print_other(struct wt_status *s,
 
        for (i = 0; i < l->nr; i++) {
                struct string_list_item *it;
+               const char *path;
                it = &(l->items[i]);
+               path = quote_path(it->string, strlen(it->string),
+                                 &buf, s->prefix);
+               if (column_active(s->colopts)) {
+                       string_list_append(&output, path);
+                       continue;
+               }
                status_printf(s, color(WT_STATUS_HEADER, s), "\t");
                status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
-                       "%s\n", quote_path(it->string, strlen(it->string),
-                                           &buf, s->prefix));
+                                  "%s\n", path);
        }
+
+       strbuf_release(&buf);
+       if (!column_active(s->colopts))
+               return;
+
+       strbuf_addf(&buf, "%s#\t%s",
+                   color(WT_STATUS_HEADER, s),
+                   color(WT_STATUS_UNTRACKED, s));
+       memset(&copts, 0, sizeof(copts));
+       copts.padding = 1;
+       copts.indent = buf.buf;
+       if (want_color(s->use_color))
+               copts.nl = GIT_COLOR_RESET "\n";
+       print_columns(&output, s->colopts, &copts);
+       string_list_clear(&output, 0);
        strbuf_release(&buf);
 }
 
index 682b4c8f7da2c58f741a958f6488a48fd7b483b4..6dd7207e25eb0292ba6630c9521c0eae719bb11d 100644 (file)
@@ -56,6 +56,7 @@ struct wt_status {
        enum untracked_status_type show_untracked_files;
        const char *ignore_submodule_arg;
        char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
+       int colopts;
 
        /* These are computed during processing of the individual sections */
        int commitable;