{"thread":{"id":"30231","subject":"[PATCH v9 0/9] Column display","startedAt":"2012-04-13T10:54:32Z","lastAt":"2012-04-21T05:40:39Z","messageCount":17,"participants":["Nguyễn Thái Ngọc Duy","Junio C Hamano"],"isPatch":true,"patchVersion":9,"patchTotal":9},"messages":[{"id":"189183","messageId":"1334314481-14470-1-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":null,"subject":"[PATCH v9 0/9] Column display","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:32Z","receivedAt":"2012-04-13T10:54:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Compared to v8 [1], the only change is \"COL_ENABLE(c) == COL_ENABLED\"\nbecomes column_active(c). Oh the the experimental modes are dropped,\nobviously they are not ready.\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/191522/focus=192997\n\nNguyễn Thái Ngọc Duy (9):\n  Add column layout skeleton and git-column\n  Stop starting pager recursively\n  column: add columnar layout\n  column: add dense layout support\n  help: reuse print_columns() for help -a\n  branch: add --column\n  status: add --column\n  column: support piping stdout to external git-column process\n  tag: add --column\n\n .gitignore                   |    1 +\n Documentation/config.txt     |   38 ++++\n Documentation/git-branch.txt |    9 +\n Documentation/git-column.txt |   53 +++++\n Documentation/git-status.txt |    7 +\n Documentation/git-tag.txt    |    9 +\n Makefile                     |    3 +\n builtin.h                    |    1 +\n builtin/branch.c             |   32 +++-\n builtin/column.c             |   59 ++++++\n builtin/commit.c             |    7 +\n builtin/help.c               |    7 +-\n builtin/tag.c                |   27 +++-\n column.c                     |  435 ++++++++++++++++++++++++++++++++++++++++++\n column.h                     |   45 +++++\n command-list.txt             |    1 +\n git.c                        |    1 +\n help.c                       |   59 +++----\n help.h                       |    2 +-\n pager.c                      |    2 +-\n parse-options.h              |    2 +\n t/t3200-branch.sh            |   77 ++++++++\n t/t7004-tag.sh               |   44 +++++\n t/t7508-status.sh            |   24 +++\n t/t9002-column.sh            |  179 +++++++++++++++++\n wt-status.c                  |   28 +++-\n wt-status.h                  |    1 +\n 27 files changed, 1105 insertions(+), 48 deletions(-)\n create mode 100644 Documentation/git-column.txt\n create mode 100644 builtin/column.c\n create mode 100644 column.c\n create mode 100644 column.h\n create mode 100755 t/t9002-column.sh\n\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189184","messageId":"1334314481-14470-2-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 1/9] Add column layout skeleton and git-column","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:33Z","receivedAt":"2012-04-13T10:54:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"A column option string consists of many token separated by either\na space or a  comma. A token belongs to one of three groups:\n\n - enabling: always, never and auto\n - layout mode: currently plain (which does not layout at all)\n - other future tuning flags\n\ngit-column can be used to pipe output to from a command that wants\ncolumn layout, but not to mess with its own output code. Simpler output\ncode can be changed to use column layout code directly.\n\nThanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n .gitignore                   |    1 +\n Documentation/config.txt     |   18 +++++\n Documentation/git-column.txt |   53 +++++++++++++\n Makefile                     |    3 +\n builtin.h                    |    1 +\n builtin/column.c             |   59 +++++++++++++++\n column.c                     |  170 ++++++++++++++++++++++++++++++++++++++++++\n column.h                     |   38 +++++++++\n command-list.txt             |    1 +\n git.c                        |    1 +\n parse-options.h              |    2 +\n t/t9002-column.sh            |   45 +++++++++++\n 12 files changed, 392 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-column.txt\n create mode 100644 builtin/column.c\n create mode 100644 column.c\n create mode 100644 column.h\n create mode 100755 t/t9002-column.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 87fcc5f..2540264 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -26,6 +26,7 @@\n /git-cherry-pick\n /git-clean\n /git-clone\n+/git-column\n /git-commit\n /git-commit-tree\n /git-config\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e55dae1..9aabef1 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -836,6 +836,24 @@ color.ui::\n \t`never` if you prefer git commands not to use color unless enabled\n \texplicitly with some other configuration or the `--color` option.\n \n+column.ui::\n+\tSpecify whether supported commands should output in columns.\n+\tThis variable consists of a list of tokens separated by spaces\n+\tor commas:\n++\n+--\n+`always`;;\n+\talways show in columns\n+`never`;;\n+\tnever show in columns\n+`auto`;;\n+\tshow in columns if the output is to the terminal\n+`plain`;;\n+\tshow in one column\n+--\n++\n+\tThis option defaults to 'never'.\n+\n commit.status::\n \tA boolean to enable/disable inclusion of status information in the\n \tcommit message template when using an editor to prepare the commit\ndiff --git a/Documentation/git-column.txt b/Documentation/git-column.txt\nnew file mode 100644\nindex 0000000..9be16ee\n--- /dev/null\n+++ b/Documentation/git-column.txt\n@@ -0,0 +1,53 @@\n+git-column(1)\n+=============\n+\n+NAME\n+----\n+git-column - Display data in columns\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]\n+\t     [--indent=<string>] [--nl=<string>] [--pading=<n>]\n+\n+DESCRIPTION\n+-----------\n+This command formats its input into multiple columns.\n+\n+OPTIONS\n+-------\n+--command=<name>::\n+\tLook up layout mode using configuration variable column.<name> and\n+\tcolumn.ui.\n+\n+--mode=<mode>::\n+\tSpecify layout mode. See configuration variable column.ui for option\n+\tsyntax.\n+\n+--raw-mode=<n>::\n+\tSame as --mode but take mode encoded as a number. This is mainly used\n+\tby other commands that have already parsed layout mode.\n+\n+--width=<width>::\n+\tSpecify the terminal width. By default 'git column' will detect the\n+\tterminal width, or fall back to 80 if it is unable to do so.\n+\n+--indent=<string>::\n+\tString to be printed at the beginning of each line.\n+\n+--nl=<N>::\n+\tString to be printed at the end of each line,\n+\tincluding newline character.\n+\n+--padding=<N>::\n+\tThe number of spaces between columns. One space by default.\n+\n+\n+Author\n+------\n+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 1fb1705..b579b6b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o\n LIB_OBJS += bundle.o\n LIB_OBJS += cache-tree.o\n LIB_OBJS += color.o\n+LIB_OBJS += column.o\n LIB_OBJS += combine-diff.o\n LIB_OBJS += commit.o\n LIB_OBJS += compat/obstack.o\n@@ -774,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o\n BUILTIN_OBJS += builtin/checkout.o\n BUILTIN_OBJS += builtin/clean.o\n BUILTIN_OBJS += builtin/clone.o\n+BUILTIN_OBJS += builtin/column.o\n BUILTIN_OBJS += builtin/commit-tree.o\n BUILTIN_OBJS += builtin/commit.o\n BUILTIN_OBJS += builtin/config.o\n@@ -2166,6 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h\n builtin/commit.o builtin/revert.o wt-status.o: wt-status.h\n builtin/tar-tree.o archive-tar.o: tar.h\n connect.o transport.o url.o http-backend.o: url.h\n+column.o help.o pager.o: column.h\n http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h\n http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h\n \ndiff --git a/builtin.h b/builtin.h\nindex 857b9c8..338f540 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);\n extern int cmd_clone(int argc, const char **argv, const char *prefix);\n extern int cmd_clean(int argc, const char **argv, const char *prefix);\n+extern int cmd_column(int argc, const char **argv, const char *prefix);\n extern int cmd_commit(int argc, const char **argv, const char *prefix);\n extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/column.c b/builtin/column.c\nnew file mode 100644\nindex 0000000..5ea798a\n--- /dev/null\n+++ b/builtin/column.c\n@@ -0,0 +1,59 @@\n+#include \"builtin.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"parse-options.h\"\n+#include \"string-list.h\"\n+#include \"column.h\"\n+\n+static const char * const builtin_column_usage[] = {\n+\t\"git column [options]\",\n+\tNULL\n+};\n+static unsigned int colopts;\n+\n+static int column_config(const char *var, const char *value, void *cb)\n+{\n+\treturn git_column_config(var, value, cb, &colopts);\n+}\n+\n+int cmd_column(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct column_options copts;\n+\tconst char *command = NULL, *real_command = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"command\", &real_command, \"name\", \"lookup config vars\"),\n+\t\tOPT_COLUMN(0, \"mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"raw-mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"width\", &copts.width, \"Maximum width\"),\n+\t\tOPT_STRING(0, \"indent\", &copts.indent, \"string\", \"Padding space on left border\"),\n+\t\tOPT_INTEGER(0, \"nl\", &copts.nl, \"Padding space on right border\"),\n+\t\tOPT_INTEGER(0, \"padding\", &copts.padding, \"Padding space between columns\"),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* This one is special and must be the first one */\n+\tif (argc > 1 && !prefixcmp(argv[1], \"--command=\")) {\n+\t\tcommand = argv[1] + 10;\n+\t\tgit_config(column_config, (void *)command);\n+\t} else\n+\t\tgit_config(column_config, NULL);\n+\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.width = term_columns();\n+\tcopts.padding = 1;\n+\targc = parse_options(argc, argv, \"\", options, builtin_column_usage, 0);\n+\tif (argc)\n+\t\tusage_with_options(builtin_column_usage, options);\n+\tif (real_command || command) {\n+\t\tif (!real_command || !command || strcmp(real_command, command))\n+\t\t\tdie(_(\"--command must be the first argument\"));\n+\t}\n+\tfinalize_colopts(&colopts, -1);\n+\twhile (!strbuf_getline(&sb, stdin, '\\n'))\n+\t\tstring_list_append(&list, sb.buf);\n+\n+\tprint_columns(&list, colopts, &copts);\n+\treturn 0;\n+}\ndiff --git a/column.c b/column.c\nnew file mode 100644\nindex 0000000..b297037\n--- /dev/null\n+++ b/column.c\n@@ -0,0 +1,170 @@\n+#include \"cache.h\"\n+#include \"column.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+\n+/* Display without layout when not enabled */\n+static void display_plain(const struct string_list *list,\n+\t\t\t  const char *indent, const char *nl)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tprintf(\"%s%s%s\", indent, list->items[i].string, nl);\n+}\n+\n+void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t   const struct column_options *opts)\n+{\n+\tstruct column_options nopts;\n+\n+\tif (!list->nr)\n+\t\treturn;\n+\tassert((colopts & COL_ENABLE_MASK) != COL_AUTO);\n+\n+\tmemset(&nopts, 0, sizeof(nopts));\n+\tnopts.indent = opts && opts->indent ? opts->indent : \"\";\n+\tnopts.nl = opts && opts->nl ? opts->nl : \"\\n\";\n+\tnopts.padding = opts ? opts->padding : 1;\n+\tnopts.width = opts && opts->width ? opts->width : term_columns() - 1;\n+\tif (!column_active(colopts)) {\n+\t\tdisplay_plain(list, \"\", \"\\n\");\n+\t\treturn;\n+\t}\n+\tswitch (COL_LAYOUT(colopts)) {\n+\tcase COL_PLAIN:\n+\t\tdisplay_plain(list, nopts.indent, nopts.nl);\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid layout mode %d\", COL_LAYOUT(colopts));\n+\t}\n+}\n+\n+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)\n+{\n+\tif ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {\n+\t\tif (stdout_is_tty < 0)\n+\t\t\tstdout_is_tty = isatty(1);\n+\t\t*colopts &= ~COL_ENABLE_MASK;\n+\t\tif (stdout_is_tty)\n+\t\t\t*colopts |= COL_ENABLED;\n+\t}\n+\treturn 0;\n+}\n+\n+struct colopt {\n+\tconst char *name;\n+\tunsigned int value;\n+\tunsigned int mask;\n+};\n+\n+#define LAYOUT_SET 1\n+#define ENABLE_SET 2\n+\n+static int parse_option(const char *arg, int len, unsigned int *colopts,\n+\t\t\tint *group_set)\n+{\n+\tstruct colopt opts[] = {\n+\t\t{ \"always\", COL_ENABLED,  COL_ENABLE_MASK },\n+\t\t{ \"never\",  COL_DISABLED, COL_ENABLE_MASK },\n+\t\t{ \"auto\",   COL_AUTO,     COL_ENABLE_MASK },\n+\t\t{ \"plain\",  COL_PLAIN,    COL_LAYOUT_MASK },\n+\t};\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(opts); i++) {\n+\t\tint arg_len = len, name_len;\n+\t\tconst char *arg_str = arg;\n+\n+\t\tname_len = strlen(opts[i].name);\n+\t\tif (arg_len != name_len ||\n+\t\t    strncmp(arg_str, opts[i].name, name_len))\n+\t\t\tcontinue;\n+\n+\t\tswitch (opts[i].mask) {\n+\t\tcase COL_ENABLE_MASK:\n+\t\t\t*group_set |= ENABLE_SET;\n+\t\t\tbreak;\n+\t\tcase COL_LAYOUT_MASK:\n+\t\t\t*group_set |= LAYOUT_SET;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (opts[i].mask)\n+\t\t\t*colopts = (*colopts & ~opts[i].mask) | opts[i].value;\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"unsupported option '%s'\", arg);\n+}\n+\n+static int parse_config(unsigned int *colopts, const char *value)\n+{\n+\tconst char *sep = \" ,\";\n+\tint group_set = 0;\n+\n+\twhile (*value) {\n+\t\tint len = strcspn(value, sep);\n+\t\tif (len) {\n+\t\t\tif (parse_option(value, len, colopts, &group_set))\n+\t\t\t\treturn -1;\n+\n+\t\t\tvalue += len;\n+\t\t}\n+\t\tvalue += strspn(value, sep);\n+\t}\n+\t/*\n+\t * Setting layout implies \"always\" if neither always, never\n+\t * nor auto is specified.\n+\t *\n+\t * Current value in COL_ENABLE_MASK is disregarded. This means if\n+\t * you set column.ui = auto and pass --column=row, then \"auto\"\n+\t * will become \"always\".\n+\t */\n+\tif ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))\n+\t\t*colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n+\treturn 0;\n+}\n+\n+static int column_config(const char *var, const char *value,\n+\t\t\t const char *key, unsigned int *colopts)\n+{\n+\tif (parse_config(colopts, value))\n+\t\treturn error(\"invalid %s mode %s\", key, value);\n+\treturn 0;\n+}\n+\n+int git_column_config(const char *var, const char *value,\n+\t\t      const char *command, unsigned int *colopts)\n+{\n+\tif (!strcmp(var, \"column.ui\"))\n+\t\treturn column_config(var, value, \"column.ui\", colopts);\n+\n+\tif (command) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tint ret = 0;\n+\t\tstrbuf_addf(&sb, \"column.%s\", command);\n+\t\tif (!strcmp(var, sb.buf))\n+\t\t\tret = column_config(var, value, sb.buf, colopts);\n+\t\tstrbuf_release(&sb);\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int parseopt_column_callback(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tunsigned int *colopts = opt->value;\n+\t*colopts |= COL_PARSEOPT;\n+\t*colopts &= ~COL_ENABLE_MASK;\n+\tif (unset)\t\t/* --no-column == never */\n+\t\treturn 0;\n+\t/* --column == always unless \"arg\" states otherwise */\n+\t*colopts |= COL_ENABLED;\n+\tif (arg)\n+\t\treturn parse_config(colopts, arg);\n+\n+\treturn 0;\n+}\ndiff --git a/column.h b/column.h\nnew file mode 100644\nindex 0000000..b8719b3\n--- /dev/null\n+++ b/column.h\n@@ -0,0 +1,38 @@\n+#ifndef COLUMN_H\n+#define COLUMN_H\n+\n+#define COL_LAYOUT_MASK   0x000F\n+#define COL_ENABLE_MASK   0x0030   /* always, never or auto */\n+#define COL_PARSEOPT      0x0040   /* --column is given from cmdline */\n+\n+#define COL_DISABLED      0x0000   /* must be zero */\n+#define COL_ENABLED       0x0010\n+#define COL_AUTO          0x0020\n+\n+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)\n+#define COL_PLAIN             15   /* one column */\n+\n+#define explicitly_enable_column(c) \\\n+\t(((c) & COL_PARSEOPT) && column_active(c))\n+\n+struct column_options {\n+\tint width;\n+\tint padding;\n+\tconst char *indent;\n+\tconst char *nl;\n+};\n+\n+struct option;\n+extern int parseopt_column_callback(const struct option *, const char *, int);\n+extern int git_column_config(const char *var, const char *value,\n+\t\t\t     const char *command, unsigned int *colopts);\n+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);\n+static inline int column_active(unsigned int colopts)\n+{\n+\treturn (colopts & COL_ENABLE_MASK) == COL_ENABLED;\n+}\n+\n+extern void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t\t  const struct column_options *opts);\n+\n+#endif\ndiff --git a/command-list.txt b/command-list.txt\nindex a36ee9b..fe06f15 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -20,6 +20,7 @@ git-cherry-pick                         mainporcelain\n git-citool                              mainporcelain\n git-clean                               mainporcelain\n git-clone                               mainporcelain common\n+git-column                              purehelpers\n git-commit                              mainporcelain common\n git-commit-tree                         plumbingmanipulators\n git-config                              ancillarymanipulators\ndiff --git a/git.c b/git.c\nindex 3805616..ee727cb 100644\n--- a/git.c\n+++ b/git.c\n@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clone\", cmd_clone },\n+\t\t{ \"column\", cmd_column, RUN_SETUP_GENTLY },\n \t\t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n \t\t{ \"config\", cmd_config, RUN_SETUP_GENTLY },\ndiff --git a/parse-options.h b/parse-options.h\nindex 2e811dc..56fcafd 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);\n \t  PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }\n #define OPT__COLOR(var, h) \\\n \tOPT_COLOR_FLAG(0, \"color\", (var), (h))\n+#define OPT_COLUMN(s, l, v, h) \\\n+\t{ OPTION_CALLBACK, (s), (l), (v), \"style\", (h), PARSE_OPT_OPTARG, parseopt_column_callback }\n \n #endif\ndiff --git a/t/t9002-column.sh b/t/t9002-column.sh\nnew file mode 100755\nindex 0000000..a7f3cd9\n--- /dev/null\n+++ b/t/t9002-column.sh\n@@ -0,0 +1,45 @@\n+#!/bin/sh\n+\n+test_description='git column'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tcat >lista <<\\EOF\n+one\n+two\n+three\n+four\n+five\n+six\n+seven\n+eight\n+nine\n+ten\n+eleven\n+EOF\n+'\n+\n+test_expect_success 'never' '\n+\tgit column --indent=Z --mode=never <lista >actual &&\n+\ttest_cmp lista actual\n+'\n+\n+test_expect_success 'always' '\n+\tcat >expected <<\\EOF &&\n+Zone\n+Ztwo\n+Zthree\n+Zfour\n+Zfive\n+Zsix\n+Zseven\n+Zeight\n+Znine\n+Zten\n+Zeleven\n+EOF\n+\tgit column --indent=Z --mode=plain <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189185","messageId":"1334314481-14470-3-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 2/9] Stop starting pager recursively","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:34Z","receivedAt":"2012-04-13T10:54:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"git-column can be used as a pager for other git commands, something\nlike this:\n\n    GIT_PAGER=\"git -p column --mode='dense color'\" git -p branch\n\nThe problem with this is that \"git -p column\" also has $GIT_PAGER set so\nthe pager runs itself again as another pager. The end result is an\ninfinite loop of forking. Other git commands have the same problem if\nbeing abused this way.\n\nCheck if $GIT_PAGER is already set and stop launching another pager.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n pager.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/pager.c b/pager.c\nindex 05584de..4dcb08d 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -73,7 +73,7 @@ void setup_pager(void)\n {\n \tconst char *pager = git_pager(isatty(1));\n \n-\tif (!pager)\n+\tif (!pager || pager_in_use())\n \t\treturn;\n \n \t/*\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189186","messageId":"1334314481-14470-4-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 3/9] column: add columnar layout","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:35Z","receivedAt":"2012-04-13T10:54:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"COL_COLUMN and COL_ROW fill column by column (or row by row\nrespectively), given the terminal width and how many space between\ncolumns. All cells have equal width.\n\nStrings are supposed to be in UTF-8. Valid ANSI escape strings are OK.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt |    4 ++\n column.c                 |  114 ++++++++++++++++++++++++++++++++++++++++++++++\n column.h                 |    2 +\n t/t9002-column.sh        |   86 ++++++++++++++++++++++++++++++++++\n 4 files changed, 206 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 9aabef1..ab6ae3d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -848,6 +848,10 @@ column.ui::\n \tnever show in columns\n `auto`;;\n \tshow in columns if the output is to the terminal\n+`column`;;\n+\tfill columns before rows (default)\n+`row`;;\n+\tfill rows before columns\n `plain`;;\n \tshow in one column\n --\ndiff --git a/column.c b/column.c\nindex b297037..e44e2e9 100644\n--- a/column.c\n+++ b/column.c\n@@ -2,6 +2,59 @@\n #include \"column.h\"\n #include \"string-list.h\"\n #include \"parse-options.h\"\n+#include \"utf8.h\"\n+\n+#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \\\n+\t\t\t    (x) * (d)->rows + (y) : \\\n+\t\t\t    (y) * (d)->cols + (x))\n+\n+struct column_data {\n+\tconst struct string_list *list;\n+\tunsigned int colopts;\n+\tstruct column_options opts;\n+\n+\tint rows, cols;\n+\tint *len;\t\t/* cell length */\n+};\n+\n+/* return length of 's' in letters, ANSI escapes stripped */\n+static int item_length(unsigned int colopts, const char *s)\n+{\n+\tint len, i = 0;\n+\tstruct strbuf str = STRBUF_INIT;\n+\n+\tstrbuf_addstr(&str, s);\n+\twhile ((s = strstr(str.buf + i, \"\\033[\")) != NULL) {\n+\t\tint len = strspn(s + 2, \"0123456789;\");\n+\t\ti = s - str.buf;\n+\t\tstrbuf_remove(&str, i, len + 3); /* \\033[<len><func char> */\n+\t}\n+\tlen = utf8_strwidth(str.buf);\n+\tstrbuf_release(&str);\n+\treturn len;\n+}\n+\n+/*\n+ * Calculate cell width, rows and cols for a table of equal cells, given\n+ * table width and how many spaces between cells.\n+ */\n+static void layout(struct column_data *data, int *width)\n+{\n+\tint i;\n+\n+\t*width = 0;\n+\tfor (i = 0; i < data->list->nr; i++)\n+\t\tif (*width < data->len[i])\n+\t\t\t*width = data->len[i];\n+\n+\t*width += data->opts.padding;\n+\n+\tdata->cols = (data->opts.width - strlen(data->opts.indent)) / *width;\n+\tif (data->cols == 0)\n+\t\tdata->cols = 1;\n+\n+\tdata->rows = DIV_ROUND_UP(data->list->nr, data->cols);\n+}\n \n /* Display without layout when not enabled */\n static void display_plain(const struct string_list *list,\n@@ -13,6 +66,61 @@ static void display_plain(const struct string_list *list,\n \t\tprintf(\"%s%s%s\", indent, list->items[i].string, nl);\n }\n \n+/* Print a cell to stdout with all necessary leading/traling space */\n+static int display_cell(struct column_data *data, int initial_width,\n+\t\t\tconst char *empty_cell, int x, int y)\n+{\n+\tint i, len, newline;\n+\n+\ti = XY2LINEAR(data, x, y);\n+\tif (i >= data->list->nr)\n+\t\treturn -1;\n+\tlen = data->len[i];\n+\tif (COL_LAYOUT(data->colopts) == COL_COLUMN)\n+\t\tnewline = i + data->rows >= data->list->nr;\n+\telse\n+\t\tnewline = x == data->cols - 1 || i == data->list->nr - 1;\n+\n+\tprintf(\"%s%s%s\",\n+\t       x == 0 ? data->opts.indent : \"\",\n+\t       data->list->items[i].string,\n+\t       newline ? data->opts.nl : empty_cell + len);\n+\treturn 0;\n+}\n+\n+/* Display COL_COLUMN or COL_ROW */\n+static void display_table(const struct string_list *list,\n+\t\t\t  unsigned int colopts,\n+\t\t\t  const struct column_options *opts)\n+{\n+\tstruct column_data data;\n+\tint x, y, i, initial_width;\n+\tchar *empty_cell;\n+\n+\tmemset(&data, 0, sizeof(data));\n+\tdata.list = list;\n+\tdata.colopts = colopts;\n+\tdata.opts = *opts;\n+\n+\tdata.len = xmalloc(sizeof(*data.len) * list->nr);\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tdata.len[i] = item_length(colopts, list->items[i].string);\n+\n+\tlayout(&data, &initial_width);\n+\n+\tempty_cell = xmalloc(initial_width + 1);\n+\tmemset(empty_cell, ' ', initial_width);\n+\tempty_cell[initial_width] = '\\0';\n+\tfor (y = 0; y < data.rows; y++) {\n+\t\tfor (x = 0; x < data.cols; x++)\n+\t\t\tif (display_cell(&data, initial_width, empty_cell, x, y))\n+\t\t\t\tbreak;\n+\t}\n+\n+\tfree(data.len);\n+\tfree(empty_cell);\n+}\n+\n void print_columns(const struct string_list *list, unsigned int colopts,\n \t\t   const struct column_options *opts)\n {\n@@ -35,6 +143,10 @@ void print_columns(const struct string_list *list, unsigned int colopts,\n \tcase COL_PLAIN:\n \t\tdisplay_plain(list, nopts.indent, nopts.nl);\n \t\tbreak;\n+\tcase COL_ROW:\n+\tcase COL_COLUMN:\n+\t\tdisplay_table(list, colopts, &nopts);\n+\t\tbreak;\n \tdefault:\n \t\tdie(\"BUG: invalid layout mode %d\", COL_LAYOUT(colopts));\n \t}\n@@ -69,6 +181,8 @@ static int parse_option(const char *arg, int len, unsigned int *colopts,\n \t\t{ \"never\",  COL_DISABLED, COL_ENABLE_MASK },\n \t\t{ \"auto\",   COL_AUTO,     COL_ENABLE_MASK },\n \t\t{ \"plain\",  COL_PLAIN,    COL_LAYOUT_MASK },\n+\t\t{ \"column\", COL_COLUMN,   COL_LAYOUT_MASK },\n+\t\t{ \"row\",    COL_ROW,      COL_LAYOUT_MASK },\n \t};\n \tint i;\n \ndiff --git a/column.h b/column.h\nindex b8719b3..ec7e1d2 100644\n--- a/column.h\n+++ b/column.h\n@@ -10,6 +10,8 @@\n #define COL_AUTO          0x0020\n \n #define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)\n+#define COL_COLUMN             0   /* Fill columns before rows */\n+#define COL_ROW                1   /* Fill rows before columns */\n #define COL_PLAIN             15   /* one column */\n \n #define explicitly_enable_column(c) \\\ndiff --git a/t/t9002-column.sh b/t/t9002-column.sh\nindex a7f3cd9..ec288ae 100755\n--- a/t/t9002-column.sh\n+++ b/t/t9002-column.sh\n@@ -42,4 +42,90 @@ EOF\n \ttest_cmp expected actual\n '\n \n+test_expect_success '80 columns' '\n+\tcat >expected <<\\EOF &&\n+one    two    three  four   five   six    seven  eight  nine   ten    eleven\n+EOF\n+\tCOLUMNS=80 git column --mode=column <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'COLUMNS = 1' '\n+\tcat >expected <<\\EOF &&\n+one\n+two\n+three\n+four\n+five\n+six\n+seven\n+eight\n+nine\n+ten\n+eleven\n+EOF\n+\tCOLUMNS=1 git column --mode=column <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'width = 1' '\n+\tgit column --mode=column --width=1 <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+COLUMNS=20\n+export COLUMNS\n+\n+test_expect_success '20 columns' '\n+\tcat >expected <<\\EOF &&\n+one    seven\n+two    eight\n+three  nine\n+four   ten\n+five   eleven\n+six\n+EOF\n+\tgit column --mode=column <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '20 columns, padding 2' '\n+\tcat >expected <<\\EOF &&\n+one     seven\n+two     eight\n+three   nine\n+four    ten\n+five    eleven\n+six\n+EOF\n+\tgit column --mode=column --padding 2 <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '20 columns, indented' '\n+\tcat >expected <<\\EOF &&\n+  one    seven\n+  two    eight\n+  three  nine\n+  four   ten\n+  five   eleven\n+  six\n+EOF\n+\tgit column --mode=column --indent=\"  \" <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '20 columns, row first' '\n+\tcat >expected <<\\EOF &&\n+one    two\n+three  four\n+five   six\n+seven  eight\n+nine   ten\n+eleven\n+EOF\n+\tgit column --mode=row <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189187","messageId":"1334314481-14470-5-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 4/9] column: add dense layout support","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:36Z","receivedAt":"2012-04-13T10:54:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Normally all cells (and in turn columns) share the same width. This\nlayout mode can waste space because one long item can stretch our all\ncolumns.\n\nWith COL_DENSE enabled, column width is calculated indepdendently. All\ncolumns are shrunk to minimum, then it attempts to push cells of the\nlast row over to the next column with hope that everything still fits\neven there's one row less. The process is repeated until the new layout\ncannot fit in given width any more, or there's only one row left\n(perfect!).\n\nApparently, this mode consumes more cpu than the old one, but it makes\nbetter use of terminal space. For layouting one or two screens, cpu\nusage should not be detectable.\n\nThis patch introduces option handling code besides layout modes and\nenable/disable to expose this feature as \"dense\". The feature can be\nturned off by specifying \"nodense\".\n\nThanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt |    4 ++\n column.c                 |   84 +++++++++++++++++++++++++++++++++++++++++++++-\n column.h                 |    2 +\n t/t9002-column.sh        |   48 ++++++++++++++++++++++++++\n 4 files changed, 137 insertions(+), 1 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex ab6ae3d..01905a7 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -854,6 +854,10 @@ column.ui::\n \tfill rows before columns\n `plain`;;\n \tshow in one column\n+`dense`;;\n+\tmake unequal size columns to utilize more space\n+`nodense`;;\n+\tmake equal size columns\n --\n +\n \tThis option defaults to 'never'.\ndiff --git a/column.c b/column.c\nindex e44e2e9..0fb7582 100644\n--- a/column.c\n+++ b/column.c\n@@ -15,6 +15,7 @@ struct column_data {\n \n \tint rows, cols;\n \tint *len;\t\t/* cell length */\n+\tint *width;\t      /* index to the longest row in column */\n };\n \n /* return length of 's' in letters, ANSI escapes stripped */\n@@ -56,6 +57,57 @@ static void layout(struct column_data *data, int *width)\n \tdata->rows = DIV_ROUND_UP(data->list->nr, data->cols);\n }\n \n+static void compute_column_width(struct column_data *data)\n+{\n+\tint i, x, y;\n+\tfor (x = 0; x < data->cols; x++) {\n+\t\tdata->width[x] = XY2LINEAR(data, x, 0);\n+\t\tfor (y = 0; y < data->rows; y++) {\n+\t\t\ti = XY2LINEAR(data, x, y);\n+\t\t\tif (i < data->list->nr &&\n+\t\t\t    data->len[data->width[x]] < data->len[i])\n+\t\t\t\tdata->width[x] = i;\n+\t\t}\n+\t}\n+}\n+\n+/*\n+ * Shrink all columns by shortening them one row each time (and adding\n+ * more columns along the way). Hopefully the longest cell will be\n+ * moved to the next column, column is shrunk so we have more space\n+ * for new columns. The process ends when the whole thing no longer\n+ * fits in data->total_width.\n+ */\n+static void shrink_columns(struct column_data *data)\n+{\n+\tdata->width = xrealloc(data->width,\n+\t\t\t       sizeof(*data->width) * data->cols);\n+\twhile (data->rows > 1) {\n+\t\tint x, total_width, cols, rows;\n+\t\trows = data->rows;\n+\t\tcols = data->cols;\n+\n+\t\tdata->rows--;\n+\t\tdata->cols = DIV_ROUND_UP(data->list->nr, data->rows);\n+\t\tif (data->cols != cols)\n+\t\t\tdata->width = xrealloc(data->width,\n+\t\t\t\t\t       sizeof(*data->width) * data->cols);\n+\t\tcompute_column_width(data);\n+\n+\t\ttotal_width = strlen(data->opts.indent);\n+\t\tfor (x = 0; x < data->cols; x++) {\n+\t\t\ttotal_width += data->len[data->width[x]];\n+\t\t\ttotal_width += data->opts.padding;\n+\t\t}\n+\t\tif (total_width > data->opts.width) {\n+\t\t\tdata->rows = rows;\n+\t\t\tdata->cols = cols;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tcompute_column_width(data);\n+}\n+\n /* Display without layout when not enabled */\n static void display_plain(const struct string_list *list,\n \t\t\t  const char *indent, const char *nl)\n@@ -75,7 +127,18 @@ static int display_cell(struct column_data *data, int initial_width,\n \ti = XY2LINEAR(data, x, y);\n \tif (i >= data->list->nr)\n \t\treturn -1;\n+\n \tlen = data->len[i];\n+\tif (data->width && data->len[data->width[x]] < initial_width) {\n+\t\t/*\n+\t\t * empty_cell has initial_width chars, if real column\n+\t\t * is narrower, increase len a bit so we fill less\n+\t\t * space.\n+\t\t */\n+\t\tlen += initial_width - data->len[data->width[x]];\n+\t\tlen -= data->opts.padding;\n+\t}\n+\n \tif (COL_LAYOUT(data->colopts) == COL_COLUMN)\n \t\tnewline = i + data->rows >= data->list->nr;\n \telse\n@@ -108,6 +171,9 @@ static void display_table(const struct string_list *list,\n \n \tlayout(&data, &initial_width);\n \n+\tif (colopts & COL_DENSE)\n+\t\tshrink_columns(&data);\n+\n \tempty_cell = xmalloc(initial_width + 1);\n \tmemset(empty_cell, ' ', initial_width);\n \tempty_cell[initial_width] = '\\0';\n@@ -118,6 +184,7 @@ static void display_table(const struct string_list *list,\n \t}\n \n \tfree(data.len);\n+\tfree(data.width);\n \tfree(empty_cell);\n }\n \n@@ -183,13 +250,22 @@ static int parse_option(const char *arg, int len, unsigned int *colopts,\n \t\t{ \"plain\",  COL_PLAIN,    COL_LAYOUT_MASK },\n \t\t{ \"column\", COL_COLUMN,   COL_LAYOUT_MASK },\n \t\t{ \"row\",    COL_ROW,      COL_LAYOUT_MASK },\n+\t\t{ \"dense\",  COL_DENSE,    0 },\n \t};\n \tint i;\n \n \tfor (i = 0; i < ARRAY_SIZE(opts); i++) {\n-\t\tint arg_len = len, name_len;\n+\t\tint set = 1, arg_len = len, name_len;\n \t\tconst char *arg_str = arg;\n \n+\t\tif (!opts[i].mask) {\n+\t\t\tif (arg_len > 2 && !strncmp(arg_str, \"no\", 2)) {\n+\t\t\t\targ_str += 2;\n+\t\t\t\targ_len -= 2;\n+\t\t\t\tset = 0;\n+\t\t\t}\n+\t\t}\n+\n \t\tname_len = strlen(opts[i].name);\n \t\tif (arg_len != name_len ||\n \t\t    strncmp(arg_str, opts[i].name, name_len))\n@@ -206,6 +282,12 @@ static int parse_option(const char *arg, int len, unsigned int *colopts,\n \n \t\tif (opts[i].mask)\n \t\t\t*colopts = (*colopts & ~opts[i].mask) | opts[i].value;\n+\t\telse {\n+\t\t\tif (set)\n+\t\t\t\t*colopts |= opts[i].value;\n+\t\t\telse\n+\t\t\t\t*colopts &= ~opts[i].value;\n+\t\t}\n \t\treturn 0;\n \t}\n \ndiff --git a/column.h b/column.h\nindex ec7e1d2..4f178d8 100644\n--- a/column.h\n+++ b/column.h\n@@ -4,6 +4,8 @@\n #define COL_LAYOUT_MASK   0x000F\n #define COL_ENABLE_MASK   0x0030   /* always, never or auto */\n #define COL_PARSEOPT      0x0040   /* --column is given from cmdline */\n+#define COL_DENSE         0x0080   /* Shrink columns when possible,\n+\t\t\t\t      making space for more columns */\n \n #define COL_DISABLED      0x0000   /* must be zero */\n #define COL_ENABLED       0x0010\ndiff --git a/t/t9002-column.sh b/t/t9002-column.sh\nindex ec288ae..c7d7a65 100755\n--- a/t/t9002-column.sh\n+++ b/t/t9002-column.sh\n@@ -89,6 +89,30 @@ EOF\n \ttest_cmp expected actual\n '\n \n+test_expect_success '20 columns, nodense' '\n+\tcat >expected <<\\EOF &&\n+one    seven\n+two    eight\n+three  nine\n+four   ten\n+five   eleven\n+six\n+EOF\n+\tgit column --mode=column,nodense < lista > actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '20 columns, dense' '\n+\tcat >expected <<\\EOF &&\n+one   five  nine\n+two   six   ten\n+three seven eleven\n+four  eight\n+EOF\n+\tgit column --mode=column,dense < lista > actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_expect_success '20 columns, padding 2' '\n \tcat >expected <<\\EOF &&\n one     seven\n@@ -128,4 +152,28 @@ EOF\n \ttest_cmp expected actual\n '\n \n+test_expect_success '20 columns, row first, nodense' '\n+\tcat >expected <<\\EOF &&\n+one    two\n+three  four\n+five   six\n+seven  eight\n+nine   ten\n+eleven\n+EOF\n+\tgit column --mode=row,nodense <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '20 columns, row first, dense' '\n+\tcat >expected <<\\EOF &&\n+one   two    three\n+four  five   six\n+seven eight  nine\n+ten   eleven\n+EOF\n+\tgit column --mode=row,dense <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189188","messageId":"1334314481-14470-6-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 5/9] help: reuse print_columns() for help -a","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:37Z","receivedAt":"2012-04-13T10:54:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\"help -a\" also respects column.ui (and column.help if presents)\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/help.c |    7 +++++-\n help.c         |   59 +++++++++++++++++++++----------------------------------\n help.h         |    2 +-\n 3 files changed, 30 insertions(+), 38 deletions(-)\n\ndiff --git a/builtin/help.c b/builtin/help.c\nindex 61ff798..c64f152 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -9,6 +9,7 @@\n #include \"common-cmds.h\"\n #include \"parse-options.h\"\n #include \"run-command.h\"\n+#include \"column.h\"\n #include \"help.h\"\n \n static struct man_viewer_list {\n@@ -30,6 +31,7 @@ enum help_format {\n };\n \n static int show_all = 0;\n+static unsigned int colopts;\n static enum help_format help_format = HELP_FORMAT_NONE;\n static struct option builtin_help_options[] = {\n \tOPT_BOOLEAN('a', \"all\", &show_all, \"print all available commands\"),\n@@ -251,6 +253,8 @@ static int add_man_viewer_info(const char *var, const char *value)\n \n static int git_help_config(const char *var, const char *value, void *cb)\n {\n+\tif (!prefixcmp(var, \"column.\"))\n+\t\treturn git_column_config(var, value, \"help\", &colopts);\n \tif (!strcmp(var, \"help.format\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n@@ -424,8 +428,9 @@ int cmd_help(int argc, const char **argv, const char *prefix)\n \tparsed_help_format = help_format;\n \n \tif (show_all) {\n+\t\tgit_config(git_help_config, NULL);\n \t\tprintf(\"usage: %s\\n\\n\", git_usage_string);\n-\t\tlist_commands(\"git commands\", &main_cmds, &other_cmds);\n+\t\tlist_commands(\"git commands\", colopts, &main_cmds, &other_cmds);\n \t\tprintf(\"%s\\n\", git_more_info_string);\n \t\treturn 0;\n \t}\ndiff --git a/help.c b/help.c\nindex 14eefc9..a815ae6 100644\n--- a/help.c\n+++ b/help.c\n@@ -4,6 +4,8 @@\n #include \"levenshtein.h\"\n #include \"help.h\"\n #include \"common-cmds.h\"\n+#include \"string-list.h\"\n+#include \"column.h\"\n \n void add_cmdname(struct cmdnames *cmds, const char *name, int len)\n {\n@@ -70,31 +72,25 @@ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)\n \tcmds->cnt = cj;\n }\n \n-static void pretty_print_string_list(struct cmdnames *cmds, int longest)\n+static void pretty_print_string_list(struct cmdnames *cmds,\n+\t\t\t\t     unsigned int colopts)\n {\n-\tint cols = 1, rows;\n-\tint space = longest + 1; /* min 1 SP between words */\n-\tint max_cols = term_columns() - 1; /* don't print *on* the edge */\n-\tint i, j;\n-\n-\tif (space < max_cols)\n-\t\tcols = max_cols / space;\n-\trows = DIV_ROUND_UP(cmds->cnt, cols);\n-\n-\tfor (i = 0; i < rows; i++) {\n-\t\tprintf(\"  \");\n+\tstruct string_list list = STRING_LIST_INIT_NODUP;\n+\tstruct column_options copts;\n+\tint i;\n \n-\t\tfor (j = 0; j < cols; j++) {\n-\t\t\tint n = j * rows + i;\n-\t\t\tint size = space;\n-\t\t\tif (n >= cmds->cnt)\n-\t\t\t\tbreak;\n-\t\t\tif (j == cols-1 || n + rows >= cmds->cnt)\n-\t\t\t\tsize = 1;\n-\t\t\tprintf(\"%-*s\", size, cmds->names[n]->name);\n-\t\t}\n-\t\tputchar('\\n');\n-\t}\n+\tfor (i = 0; i < cmds->cnt; i++)\n+\t\tstring_list_append(&list, cmds->names[i]->name);\n+\t/*\n+\t * always enable column display, we only consult column.*\n+\t * about layout strategy and stuff\n+\t */\n+\tcolopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.indent = \"  \";\n+\tcopts.padding = 2;\n+\tprint_columns(&list, colopts, &copts);\n+\tstring_list_clear(&list, 0);\n }\n \n static int is_executable(const char *name)\n@@ -203,25 +199,16 @@ void load_command_list(const char *prefix,\n \texclude_cmds(other_cmds, main_cmds);\n }\n \n-void list_commands(const char *title, struct cmdnames *main_cmds,\n-\t\t   struct cmdnames *other_cmds)\n+void list_commands(const char *title, unsigned int colopts,\n+\t\t   struct cmdnames *main_cmds, struct cmdnames *other_cmds)\n {\n-\tint i, longest = 0;\n-\n-\tfor (i = 0; i < main_cmds->cnt; i++)\n-\t\tif (longest < main_cmds->names[i]->len)\n-\t\t\tlongest = main_cmds->names[i]->len;\n-\tfor (i = 0; i < other_cmds->cnt; i++)\n-\t\tif (longest < other_cmds->names[i]->len)\n-\t\t\tlongest = other_cmds->names[i]->len;\n-\n \tif (main_cmds->cnt) {\n \t\tconst char *exec_path = git_exec_path();\n \t\tprintf(\"available %s in '%s'\\n\", title, exec_path);\n \t\tprintf(\"----------------\");\n \t\tmput_char('-', strlen(title) + strlen(exec_path));\n \t\tputchar('\\n');\n-\t\tpretty_print_string_list(main_cmds, longest);\n+\t\tpretty_print_string_list(main_cmds, colopts);\n \t\tputchar('\\n');\n \t}\n \n@@ -230,7 +217,7 @@ void list_commands(const char *title, struct cmdnames *main_cmds,\n \t\tprintf(\"---------------------------------------\");\n \t\tmput_char('-', strlen(title));\n \t\tputchar('\\n');\n-\t\tpretty_print_string_list(other_cmds, longest);\n+\t\tpretty_print_string_list(other_cmds, colopts);\n \t\tputchar('\\n');\n \t}\n }\ndiff --git a/help.h b/help.h\nindex b6b12d5..854d2d4 100644\n--- a/help.h\n+++ b/help.h\n@@ -25,7 +25,7 @@ extern void add_cmdname(struct cmdnames *cmds, const char *name, int len);\n /* Here we require that excludes is a sorted list. */\n extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);\n extern int is_in_cmdlist(struct cmdnames *cmds, const char *name);\n-extern void list_commands(const char *title,\n+extern void list_commands(const char *title, unsigned int colopts,\n \t\t\t  struct cmdnames *main_cmds,\n \t\t\t  struct cmdnames *other_cmds);\n \n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189189","messageId":"1334314481-14470-7-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 6/9] branch: add --column","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:38Z","receivedAt":"2012-04-13T10:54:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt     |    4 ++\n Documentation/git-branch.txt |    9 +++++\n Makefile                     |    2 +-\n builtin/branch.c             |   32 +++++++++++++++--\n t/t3200-branch.sh            |   77 ++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 119 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 01905a7..c7e97a5 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -862,6 +862,10 @@ column.ui::\n +\n \tThis option defaults to 'never'.\n \n+column.branch::\n+\tSpecify whether to output branch listing in `git branch` in columns.\n+\tSee `column.ui` for details.\n+\n commit.status::\n \tA boolean to enable/disable inclusion of status information in the\n \tcommit message template when using an editor to prepare the commit\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 0427e80..ba5cccb 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n [verse]\n 'git branch' [--color[=<when>] | --no-color] [-r | -a]\n \t[--list] [-v [--abbrev=<length> | --no-abbrev]]\n+\t[--column[=<options>] | --no-column]\n \t[(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]\n 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -107,6 +108,14 @@ OPTIONS\n \tdefault to color output.\n \tSame as `--color=never`.\n \n+--column[=<options>]::\n+--no-column::\n+\tDisplay branch listing in columns. See configuration variable\n+\tcolumn.branch for option syntax.`--column` and `--no-column`\n+\twithout options are equivalent to 'always' and 'never' respectively.\n++\n+This option is only applicable in non-verbose mode.\n+\n -r::\n --remotes::\n \tList or delete (if used with -d) the remote-tracking branches.\ndiff --git a/Makefile b/Makefile\nindex b579b6b..857f375 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2168,7 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h\n builtin/commit.o builtin/revert.o wt-status.o: wt-status.h\n builtin/tar-tree.o archive-tar.o: tar.h\n connect.o transport.o url.o http-backend.o: url.h\n-column.o help.o pager.o: column.h\n+builtin/branch.o column.o help.o pager.o: column.h\n http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h\n http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h\n \ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex cb17bc3..9634c71 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -15,6 +15,8 @@\n #include \"branch.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n+#include \"string-list.h\"\n+#include \"column.h\"\n \n static const char * const builtin_branch_usage[] = {\n \t\"git branch [options] [-r | -a] [--merged | --no-merged]\",\n@@ -53,6 +55,9 @@ static enum merge_filter {\n } merge_filter;\n static unsigned char merge_filter_ref[20];\n \n+static struct string_list output = STRING_LIST_INIT_DUP;\n+static unsigned int colopts;\n+\n static int parse_branch_color_slot(const char *var, int ofs)\n {\n \tif (!strcasecmp(var+ofs, \"plain\"))\n@@ -70,6 +75,8 @@ static int parse_branch_color_slot(const char *var, int ofs)\n \n static int git_branch_config(const char *var, const char *value, void *cb)\n {\n+\tif (!prefixcmp(var, \"column.\"))\n+\t\treturn git_column_config(var, value, \"branch\", &colopts);\n \tif (!strcmp(var, \"color.branch\")) {\n \t\tbranch_use_color = git_config_colorbool(var, value);\n \t\treturn 0;\n@@ -474,7 +481,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,\n \telse if (verbose)\n \t\t/* \" f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h\" */\n \t\tadd_verbose_info(&out, item, verbose, abbrev);\n-\tprintf(\"%s\\n\", out.buf);\n+\tif (column_active(colopts)) {\n+\t\tassert(!verbose && \"--column and --verbose are incompatible\");\n+\t\tstring_list_append(&output, out.buf);\n+\t} else {\n+\t\tprintf(\"%s\\n\", out.buf);\n+\t}\n \tstrbuf_release(&name);\n \tstrbuf_release(&out);\n }\n@@ -727,6 +739,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\t\tPARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG,\n \t\t\topt_parse_merge_filter, (intptr_t) \"HEAD\",\n \t\t},\n+\t\tOPT_COLUMN(0, \"column\", &colopts, \"list branches in columns\"),\n \t\tOPT_END(),\n \t};\n \n@@ -749,6 +762,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t}\n \thashcpy(merge_filter_ref, head_sha1);\n \n+\n \targc = parse_options(argc, argv, prefix, options, builtin_branch_usage,\n \t\t\t     0);\n \n@@ -760,12 +774,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \n \tif (abbrev == -1)\n \t\tabbrev = DEFAULT_ABBREV;\n+\tfinalize_colopts(&colopts, -1);\n+\tif (verbose) {\n+\t\tif (explicitly_enable_column(colopts))\n+\t\t\tdie(_(\"--column and --verbose are incompatible\"));\n+\t\tcolopts = 0;\n+\t}\n \n \tif (delete)\n \t\treturn delete_branches(argc, argv, delete > 1, kinds);\n-\telse if (list)\n-\t\treturn print_ref_list(kinds, detached, verbose, abbrev,\n-\t\t\t\t      with_commit, argv);\n+\telse if (list) {\n+\t\tint ret = print_ref_list(kinds, detached, verbose, abbrev,\n+\t\t\t\t\t with_commit, argv);\n+\t\tprint_columns(&output, colopts, NULL);\n+\t\tstring_list_clear(&output, 0);\n+\t\treturn ret;\n+\t}\n \telse if (edit_description) {\n \t\tconst char *branch_name;\n \t\tstruct strbuf branch_ref = STRBUF_INIT;\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex dd1aceb..9f82d5e 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -160,6 +160,83 @@ test_expect_success 'git branch --list -d t should fail' '\n \ttest_path_is_missing .git/refs/heads/t\n '\n \n+test_expect_success 'git branch --column' '\n+\tCOLUMNS=81 git branch --column=column >actual &&\n+\tcat >expected <<\\EOF &&\n+  a/b/c     bam       foo       l       * master    n         o/p       r\n+  abc       bar       j/k       m/m       master2   o/o       q\n+EOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'git branch --column with an extremely long branch name' '\n+\tlong=this/is/a/part/of/long/branch/name &&\n+\tlong=z$long/$long/$long/$long &&\n+\ttest_when_finished \"git branch -d $long\" &&\n+\tgit branch $long &&\n+\tCOLUMNS=80 git branch --column=column >actual &&\n+\tcat >expected <<EOF &&\n+  a/b/c\n+  abc\n+  bam\n+  bar\n+  foo\n+  j/k\n+  l\n+  m/m\n+* master\n+  master2\n+  n\n+  o/o\n+  o/p\n+  q\n+  r\n+  $long\n+EOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'git branch with column.*' '\n+\tgit config column.ui column &&\n+\tgit config column.branch \"dense\" &&\n+\tCOLUMNS=80 git branch >actual &&\n+\tgit config --unset column.branch &&\n+\tgit config --unset column.ui &&\n+\tcat >expected <<\\EOF &&\n+  a/b/c   bam   foo   l   * master    n     o/p   r\n+  abc     bar   j/k   m/m   master2   o/o   q\n+EOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'git branch --column -v should fail' '\n+\ttest_must_fail git branch --column -v\n+'\n+\n+test_expect_success 'git branch -v with column.ui ignored' '\n+\tgit config column.ui column &&\n+\tCOLUMNS=80 git branch -v | cut -c -10 | sed \"s/ *$//\" >actual &&\n+\tgit config --unset column.ui &&\n+\tcat >expected <<\\EOF &&\n+  a/b/c\n+  abc\n+  bam\n+  bar\n+  foo\n+  j/k\n+  l\n+  m/m\n+* master\n+  master2\n+  n\n+  o/o\n+  o/p\n+  q\n+  r\n+EOF\n+\ttest_cmp expected actual\n+'\n+\n mv .git/config .git/config-saved\n \n test_expect_success 'git branch -m q q2 without config should succeed' '\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189190","messageId":"1334314481-14470-8-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 7/9] status: add --column","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:39Z","receivedAt":"2012-04-13T10:54:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt     |    4 ++++\n Documentation/git-status.txt |    7 +++++++\n Makefile                     |    2 +-\n builtin/commit.c             |    7 +++++++\n t/t7508-status.sh            |   24 ++++++++++++++++++++++++\n wt-status.c                  |   28 ++++++++++++++++++++++++++--\n wt-status.h                  |    1 +\n 7 files changed, 70 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex c7e97a5..2158f0c 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -866,6 +866,10 @@ column.branch::\n \tSpecify whether to output branch listing in `git branch` in columns.\n \tSee `column.ui` for details.\n \n+column.status::\n+\tSpecify whether to output untracked files in `git status` in columns.\n+\tSee `column.ui` for details.\n+\n commit.status::\n \tA boolean to enable/disable inclusion of status information in the\n \tcommit message template when using an editor to prepare the commit\ndiff --git a/Documentation/git-status.txt b/Documentation/git-status.txt\nindex 3d51717..2f87207 100644\n--- a/Documentation/git-status.txt\n+++ b/Documentation/git-status.txt\n@@ -77,6 +77,13 @@ configuration variable documented in linkgit:git-config[1].\n \tTerminate entries with NUL, instead of LF.  This implies\n \tthe `--porcelain` output format if no other format is given.\n \n+--column[=<options>]::\n+--no-column::\n+\tDisplay untracked files in columns. See configuration variable\n+\tcolumn.status for option syntax.`--column` and `--no-column`\n+\twithout options are equivalent to 'always' and 'never'\n+\trespectively.\n+\n \n OUTPUT\n ------\ndiff --git a/Makefile b/Makefile\nindex 857f375..297e830 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2168,7 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h\n builtin/commit.o builtin/revert.o wt-status.o: wt-status.h\n builtin/tar-tree.o archive-tar.o: tar.h\n connect.o transport.o url.o http-backend.o: url.h\n-builtin/branch.o column.o help.o pager.o: column.h\n+builtin/branch.o builtin/commit.o column.o help.o pager.o: column.h\n http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h\n http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 3714582..cc1a709 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -27,6 +27,7 @@\n #include \"quote.h\"\n #include \"submodule.h\"\n #include \"gpg-interface.h\"\n+#include \"column.h\"\n \n static const char * const builtin_commit_usage[] = {\n \t\"git commit [options] [--] <filepattern>...\",\n@@ -88,6 +89,7 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int no_post_rewrite, allow_empty_message;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg;\n static char *sign_commit;\n+static unsigned int colopts;\n \n /*\n  * The default commit message cleanup mode will remove the lines\n@@ -1145,6 +1147,8 @@ static int git_status_config(const char *k, const char *v, void *cb)\n {\n \tstruct wt_status *s = cb;\n \n+\tif (!prefixcmp(k, \"column.\"))\n+\t\treturn git_column_config(k, v, \"status\", &colopts);\n \tif (!strcmp(k, \"status.submodulesummary\")) {\n \t\tint is_bool;\n \t\ts->submodule_summary = git_config_bool_or_int(k, v, &is_bool);\n@@ -1210,6 +1214,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_STRING, 0, \"ignore-submodules\", &ignore_submodule_arg, \"when\",\n \t\t  \"ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)\",\n \t\t  PARSE_OPT_OPTARG, NULL, (intptr_t)\"all\" },\n+\t\tOPT_COLUMN(0, \"column\", &colopts, \"list untracked files in columns\"),\n \t\tOPT_END(),\n \t};\n \n@@ -1223,6 +1228,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix,\n \t\t\t     builtin_status_options,\n \t\t\t     builtin_status_usage, 0);\n+\tfinalize_colopts(&colopts, -1);\n+\ts.colopts = colopts;\n \n \tif (null_termination && status_format == STATUS_FORMAT_LONG)\n \t\tstatus_format = STATUS_FORMAT_PORCELAIN;\ndiff --git a/t/t7508-status.sh b/t/t7508-status.sh\nindex fc57b13..8f5cfac 100755\n--- a/t/t7508-status.sh\n+++ b/t/t7508-status.sh\n@@ -59,6 +59,30 @@ test_expect_success 'status (1)' '\n \ttest_i18ngrep \"use \\\"git rm --cached <file>\\.\\.\\.\\\" to unstage\" output\n '\n \n+test_expect_success 'status --column' '\n+\tCOLUMNS=50 git status --column=\"column dense\" >output &&\n+\tcat >expect <<\\EOF &&\n+# On branch master\n+# Changes to be committed:\n+#   (use \"git reset HEAD <file>...\" to unstage)\n+#\n+#\tnew file:   dir2/added\n+#\n+# Changes not staged for commit:\n+#   (use \"git add <file>...\" to update what will be committed)\n+#   (use \"git checkout -- <file>...\" to discard changes in working directory)\n+#\n+#\tmodified:   dir1/modified\n+#\n+# Untracked files:\n+#   (use \"git add <file>...\" to include in what will be committed)\n+#\n+#\tdir1/untracked dir2/untracked untracked\n+#\tdir2/modified  output\n+EOF\n+\ttest_cmp expect output\n+'\n+\n cat >expect <<\\EOF\n # On branch master\n # Changes to be committed:\ndiff --git a/wt-status.c b/wt-status.c\nindex 9ffc535..eeef17e 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -11,6 +11,7 @@\n #include \"remote.h\"\n #include \"refs.h\"\n #include \"submodule.h\"\n+#include \"column.h\"\n \n static char default_wt_status_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_NORMAL, /* WT_STATUS_HEADER */\n@@ -641,6 +642,8 @@ static void wt_status_print_other(struct wt_status *s,\n {\n \tint i;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstatic struct string_list output = STRING_LIST_INIT_DUP;\n+\tstruct column_options copts;\n \n \tif (!l->nr)\n \t\treturn;\n@@ -649,12 +652,33 @@ static void wt_status_print_other(struct wt_status *s,\n \n \tfor (i = 0; i < l->nr; i++) {\n \t\tstruct string_list_item *it;\n+\t\tconst char *path;\n \t\tit = &(l->items[i]);\n+\t\tpath = quote_path(it->string, strlen(it->string),\n+\t\t\t\t  &buf, s->prefix);\n+\t\tif (column_active(s->colopts)) {\n+\t\t\tstring_list_append(&output, path);\n+\t\t\tcontinue;\n+\t\t}\n \t\tstatus_printf(s, color(WT_STATUS_HEADER, s), \"\\t\");\n \t\tstatus_printf_more(s, color(WT_STATUS_UNTRACKED, s),\n-\t\t\t\"%s\\n\", quote_path(it->string, strlen(it->string),\n-\t\t\t\t\t    &buf, s->prefix));\n+\t\t\t\t   \"%s\\n\", path);\n \t}\n+\n+\tstrbuf_release(&buf);\n+\tif (!column_active(s->colopts))\n+\t\treturn;\n+\n+\tstrbuf_addf(&buf, \"%s#\\t%s\",\n+\t\t    color(WT_STATUS_HEADER, s),\n+\t\t    color(WT_STATUS_UNTRACKED, s));\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.padding = 1;\n+\tcopts.indent = buf.buf;\n+\tif (want_color(s->use_color))\n+\t\tcopts.nl = GIT_COLOR_RESET \"\\n\";\n+\tprint_columns(&output, s->colopts, &copts);\n+\tstring_list_clear(&output, 0);\n \tstrbuf_release(&buf);\n }\n \ndiff --git a/wt-status.h b/wt-status.h\nindex 682b4c8..6dd7207 100644\n--- a/wt-status.h\n+++ b/wt-status.h\n@@ -56,6 +56,7 @@ struct wt_status {\n \tenum untracked_status_type show_untracked_files;\n \tconst char *ignore_submodule_arg;\n \tchar color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];\n+\tint colopts;\n \n \t/* These are computed during processing of the individual sections */\n \tint commitable;\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189191","messageId":"1334314481-14470-9-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 8/9] column: support piping stdout to external git-column process","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:40Z","receivedAt":"2012-04-13T10:54:40Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"For too complicated output handling, it'd be easier to just spawn\ngit-column and redirect stdout to it. This patch provides helpers\nto do that.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n column.c |   69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n column.h |    3 ++\n 2 files changed, 72 insertions(+), 0 deletions(-)\n\ndiff --git a/column.c b/column.c\nindex 0fb7582..952ab38 100644\n--- a/column.c\n+++ b/column.c\n@@ -2,6 +2,7 @@\n #include \"column.h\"\n #include \"string-list.h\"\n #include \"parse-options.h\"\n+#include \"run-command.h\"\n #include \"utf8.h\"\n \n #define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \\\n@@ -364,3 +365,71 @@ int parseopt_column_callback(const struct option *opt,\n \n \treturn 0;\n }\n+\n+static int fd_out = -1;\n+static struct child_process column_process;\n+\n+int run_column_filter(int colopts, const struct column_options *opts)\n+{\n+\tconst char *av[10];\n+\tint ret, ac = 0;\n+\tstruct strbuf sb_colopt  = STRBUF_INIT;\n+\tstruct strbuf sb_width   = STRBUF_INIT;\n+\tstruct strbuf sb_padding = STRBUF_INIT;\n+\n+\tif (fd_out != -1)\n+\t\treturn -1;\n+\n+\tav[ac++] = \"column\";\n+\tstrbuf_addf(&sb_colopt, \"--raw-mode=%d\", colopts);\n+\tav[ac++] = sb_colopt.buf;\n+\tif (opts && opts->width) {\n+\t\tstrbuf_addf(&sb_width, \"--width=%d\", opts->width);\n+\t\tav[ac++] = sb_width.buf;\n+\t}\n+\tif (opts && opts->indent) {\n+\t\tav[ac++] = \"--indent\";\n+\t\tav[ac++] = opts->indent;\n+\t}\n+\tif (opts && opts->padding) {\n+\t\tstrbuf_addf(&sb_padding, \"--padding=%d\", opts->padding);\n+\t\tav[ac++] = sb_padding.buf;\n+\t}\n+\tav[ac] = NULL;\n+\n+\tfflush(stdout);\n+\tmemset(&column_process, 0, sizeof(column_process));\n+\tcolumn_process.in = -1;\n+\tcolumn_process.out = dup(1);\n+\tcolumn_process.git_cmd = 1;\n+\tcolumn_process.argv = av;\n+\n+\tret = start_command(&column_process);\n+\n+\tstrbuf_release(&sb_colopt);\n+\tstrbuf_release(&sb_width);\n+\tstrbuf_release(&sb_padding);\n+\n+\tif (ret)\n+\t\treturn -2;\n+\n+\tfd_out = dup(1);\n+\tclose(1);\n+\tdup2(column_process.in, 1);\n+\tclose(column_process.in);\n+\treturn 0;\n+}\n+\n+int stop_column_filter(void)\n+{\n+\tif (fd_out == -1)\n+\t\treturn -1;\n+\n+\tfflush(stdout);\n+\tclose(1);\n+\tfinish_command(&column_process);\n+\tdup2(fd_out, 1);\n+\tclose(fd_out);\n+\tfd_out = -1;\n+\treturn 0;\n+}\ndiff --git a/column.h b/column.h\nindex 4f178d8..0a61917 100644\n--- a/column.h\n+++ b/column.h\n@@ -39,4 +39,7 @@ static inline int column_active(unsigned int colopts)\n extern void print_columns(const struct string_list *list, unsigned int colopts,\n \t\t\t  const struct column_options *opts);\n \n+extern int run_column_filter(int colopts, const struct column_options *);\n+extern int stop_column_filter(void);\n+\n #endif\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189192","messageId":"1334314481-14470-10-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"1334314481-14470-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v9 9/9] tag: add --column","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-13T10:54:41Z","receivedAt":"2012-04-13T10:54:41Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt  |    4 ++++\n Documentation/git-tag.txt |    9 +++++++++\n Makefile                  |    2 +-\n builtin/tag.c             |   27 ++++++++++++++++++++++++---\n t/t7004-tag.sh            |   44 ++++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 82 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 2158f0c..75ecf36 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -870,6 +870,10 @@ column.status::\n \tSpecify whether to output untracked files in `git status` in columns.\n \tSee `column.ui` for details.\n \n+column.tag::\n+\tSpecify whether to output tag listing in `git tag` in columns.\n+\tSee `column.ui` for details.\n+\n commit.status::\n \tA boolean to enable/disable inclusion of status information in the\n \tcommit message template when using an editor to prepare the commit\ndiff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt\nindex 8d32b9a..e36a7c3 100644\n--- a/Documentation/git-tag.txt\n+++ b/Documentation/git-tag.txt\n@@ -13,6 +13,7 @@ SYNOPSIS\n \t<tagname> [<commit> | <object>]\n 'git tag' -d <tagname>...\n 'git tag' [-n[<num>]] -l [--contains <commit>] [--points-at <object>]\n+\t[--column[=<options>] | --no-column] [<pattern>...]\n \t[<pattern>...]\n 'git tag' -v <tagname>...\n \n@@ -84,6 +85,14 @@ OPTIONS\n \tusing fnmatch(3)).  Multiple patterns may be given; if any of\n \tthem matches, the tag is shown.\n \n+--column[=<options>]::\n+--no-column::\n+\tDisplay tag listing in columns. See configuration variable\n+\tcolumn.tag for option syntax.`--column` and `--no-column`\n+\twithout options are equivalent to 'always' and 'never' respectively.\n++\n+This option is only applicable when listing tags without annotation lines.\n+\n --contains <commit>::\n \tOnly list tags which contain the specified commit.\n \ndiff --git a/Makefile b/Makefile\nindex 297e830..53e27cc 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2168,7 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h\n builtin/commit.o builtin/revert.o wt-status.o: wt-status.h\n builtin/tar-tree.o archive-tar.o: tar.h\n connect.o transport.o url.o http-backend.o: url.h\n-builtin/branch.o builtin/commit.o column.o help.o pager.o: column.h\n+builtin/branch.o builtin/commit.o builtin/tag.o column.o help.o pager.o: column.h\n http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h\n http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex fe7e5e5..4fb6bd7 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -16,6 +16,7 @@\n #include \"revision.h\"\n #include \"gpg-interface.h\"\n #include \"sha1-array.h\"\n+#include \"column.h\"\n \n static const char * const git_tag_usage[] = {\n \t\"git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]\",\n@@ -33,6 +34,7 @@ struct tag_filter {\n };\n \n static struct sha1_array points_at;\n+static unsigned int colopts;\n \n static int match_pattern(const char **patterns, const char *ref)\n {\n@@ -263,6 +265,8 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \tint status = git_gpg_config(var, value, cb);\n \tif (status)\n \t\treturn status;\n+\tif (!prefixcmp(var, \"column.\"))\n+\t\treturn git_column_config(var, value, \"tag\", &colopts);\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -459,6 +463,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING('u', \"local-user\", &keyid, \"key-id\",\n \t\t\t\t\t\"use another key to sign the tag\"),\n \t\tOPT__FORCE(&force, \"replace the tag if exists\"),\n+\t\tOPT_COLUMN(0, \"column\", &colopts, \"show tag list in columns\"),\n \n \t\tOPT_GROUP(\"Tag listing options\"),\n \t\t{\n@@ -495,9 +500,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \n \tif (list + delete + verify > 1)\n \t\tusage_with_options(git_tag_usage, options);\n-\tif (list)\n-\t\treturn list_tags(argv, lines == -1 ? 0 : lines,\n-\t\t\t\t with_commit);\n+\tfinalize_colopts(&colopts, -1);\n+\tif (list && lines != -1) {\n+\t\tif (explicitly_enable_column(colopts))\n+\t\t\tdie(_(\"--column and -n are incompatible\"));\n+\t\tcolopts = 0;\n+\t}\n+\tif (list) {\n+\t\tint ret;\n+\t\tif (column_active(colopts)) {\n+\t\t\tstruct column_options copts;\n+\t\t\tmemset(&copts, 0, sizeof(copts));\n+\t\t\tcopts.padding = 2;\n+\t\t\trun_column_filter(colopts, &copts);\n+\t\t}\n+\t\tret = list_tags(argv, lines == -1 ? 0 : lines, with_commit);\n+\t\tif (column_active(colopts))\n+\t\t\tstop_column_filter();\n+\t\treturn ret;\n+\t}\n \tif (lines != -1)\n \t\tdie(_(\"-n option is only allowed with -l.\"));\n \tif (with_commit)\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex f8c247a..5189446 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -263,6 +263,50 @@ test_expect_success 'tag -l can accept multiple patterns' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'listing tags in column' '\n+\tCOLUMNS=40 git tag -l --column=row >actual &&\n+\tcat >expected <<\\EOF &&\n+a1      aa1     cba     t210    t211\n+v0.2.1  v1.0    v1.0.1  v1.1.3\n+EOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'listing tags in column with column.*' '\n+\tgit config column.tag row &&\n+\tgit config column.ui dense &&\n+\tCOLUMNS=40 git tag -l >actual &&\n+\tgit config --unset column.ui &&\n+\tgit config --unset column.tag &&\n+\tcat >expected <<\\EOF &&\n+a1      aa1   cba     t210    t211\n+v0.2.1  v1.0  v1.0.1  v1.1.3\n+EOF\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'listing tag with -n --column should fail' '\n+\ttest_must_fail git tag --column -n\n+'\n+\n+test_expect_success 'listing tags -n in column with column.ui ignored' '\n+\tgit config column.ui \"row dense\" &&\n+\tCOLUMNS=40 git tag -l -n >actual &&\n+\tgit config --unset column.ui &&\n+\tcat >expected <<\\EOF &&\n+a1              Foo\n+aa1             Foo\n+cba             Foo\n+t210            Foo\n+t211            Foo\n+v0.2.1          Foo\n+v1.0            Foo\n+v1.0.1          Foo\n+v1.1.3          Foo\n+EOF\n+\ttest_cmp expected actual\n+'\n+\n # creating and verifying lightweight tags:\n \n test_expect_success \\\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189368","messageId":"7v3984z16m.fsf@alter.siamese.dyndns.org","threadId":"30231","inReplyTo":"1334314481-14470-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v9 1/9] Add column layout skeleton and git-column","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-16T05:38:09Z","receivedAt":"2012-04-16T05:38:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> +static int column_config(const char *var, const char *value,\n> +\t\t\t const char *key, unsigned int *colopts)\n> +{\n> +\tif (parse_config(colopts, value))\n> +\t\treturn error(\"invalid %s mode %s\", key, value);\n> +\treturn 0;\n> +}\n> +\n> +int git_column_config(const char *var, const char *value,\n> +\t\t      const char *command, unsigned int *colopts)\n> +{\n> +\tif (!strcmp(var, \"column.ui\"))\n> +\t\treturn column_config(var, value, \"column.ui\", colopts);\n\nI do not think there is anything that reads column.ui from a configuration\nfile (or \"git -c column.ui\") in this step, but later patches seem to use\nit from their configuration callback (e.g. git_branch_config()) and at\nthat point you will segfault because you ignore the case where value is\nNULL.\n\n> +\tif (command) {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tint ret = 0;\n> +\t\tstrbuf_addf(&sb, \"column.%s\", command);\n> +\t\tif (!strcmp(var, sb.buf))\n> +\t\t\tret = column_config(var, value, sb.buf, colopts);\n> +\t\tstrbuf_release(&sb);\n> +\t\treturn ret;\n> +\t}\n\nThis whole thing looks overly wasteful.  How about doing it this way?\n\n\tconst char *it = skip_prefix(var, \"column.\");\n        if (!it)\n\t\treturn 0;\n\tif (!strcmp(it, \"ui\"))\n\t\tparse \"ui\";\n\telse if (!strcmp(it, command))\n\t\tparse \"command\";\n\nand make the third parameter to column_config() be without the constant\nprefix \"column.\"?\n"},{"id":"189404","messageId":"1334582784-25723-1-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"7v3984z16m.fsf@alter.siamese.dyndns.org","subject":"[PATCH] Add column layout skeleton and git-column","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-16T13:26:24Z","receivedAt":"2012-04-16T13:26:24Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"A column option string consists of many token separated by either\na space or a  comma. A token belongs to one of three groups:\n\n - enabling: always, never and auto\n - layout mode: currently plain (which does not layout at all)\n - other future tuning flags\n\ngit-column can be used to pipe output to from a command that wants\ncolumn layout, but not to mess with its own output code. Simpler output\ncode can be changed to use column layout code directly.\n\nThanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n 2012/4/16 Junio C Hamano <gitster@pobox.com>:\n > Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n >\n >> +static int column_config(const char *var, const char *value,\n >> +                      const char *key, unsigned int *colopts)\n >> +{\n >> +     if (parse_config(colopts, value))\n >> +             return error(\"invalid %s mode %s\", key, value);\n >> +     return 0;\n >> +}\n >> +\n >> +int git_column_config(const char *var, const char *value,\n >> +                   const char *command, unsigned int *colopts)\n >> +{\n >> +     if (!strcmp(var, \"column.ui\"))\n >> +             return column_config(var, value, \"column.ui\", colopts);\n >\n > I do not think there is anything that reads column.ui from a configuration\n > file (or \"git -c column.ui\") in this step, but later patches seem to use\n > it from their configuration callback (e.g. git_branch_config()) and at\n > that point you will segfault because you ignore the case where value is\n > NULL.\n\n fixed\n\n >> +     if (command) {\n >> +             struct strbuf sb = STRBUF_INIT;\n >> +             int ret = 0;\n >> +             strbuf_addf(&sb, \"column.%s\", command);\n >> +             if (!strcmp(var, sb.buf))\n >> +                     ret = column_config(var, value, sb.buf, colopts);\n >> +             strbuf_release(&sb);\n >> +             return ret;\n >> +     }\n >\n > This whole thing looks overly wasteful.  How about doing it this way?\n >\n >        const char *it = skip_prefix(var, \"column.\");\n >        if (!it)\n >                return 0;\n >        if (!strcmp(it, \"ui\"))\n >                parse \"ui\";\n >        else if (!strcmp(it, command))\n >                parse \"command\";\n >\n > and make the third parameter to column_config() be without the constant\n > prefix \"column.\"?\n\n Nice, and thanks I didn't know about skip_prefix. How about this?\n\n\n .gitignore                   |    1 +\n Documentation/config.txt     |   18 +++++\n Documentation/git-column.txt |   53 +++++++++++++\n Makefile                     |    3 +\n builtin.h                    |    1 +\n builtin/column.c             |   59 +++++++++++++++\n column.c                     |  167 ++++++++++++++++++++++++++++++++++++++++++\n column.h                     |   35 +++++++++\n command-list.txt             |    1 +\n git.c                        |    1 +\n parse-options.h              |    2 +\n t/t9002-column.sh            |   45 +++++++++++\n 12 files changed, 386 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-column.txt\n create mode 100644 builtin/column.c\n create mode 100644 column.c\n create mode 100644 column.h\n create mode 100755 t/t9002-column.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 87fcc5f..2540264 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -26,6 +26,7 @@\n /git-cherry-pick\n /git-clean\n /git-clone\n+/git-column\n /git-commit\n /git-commit-tree\n /git-config\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex abeb82b..147c41c 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -821,6 +821,24 @@ color.ui::\n \t`never` if you prefer git commands not to use color unless enabled\n \texplicitly with some other configuration or the `--color` option.\n \n+column.ui::\n+\tSpecify whether supported commands should output in columns.\n+\tThis variable consists of a list of tokens separated by spaces\n+\tor commas:\n++\n+--\n+`always`;;\n+\talways show in columns\n+`never`;;\n+\tnever show in columns\n+`auto`;;\n+\tshow in columns if the output is to the terminal\n+`plain`;;\n+\tshow in one column\n+--\n++\n+\tThis option defaults to 'never'.\n+\n commit.status::\n \tA boolean to enable/disable inclusion of status information in the\n \tcommit message template when using an editor to prepare the commit\ndiff --git a/Documentation/git-column.txt b/Documentation/git-column.txt\nnew file mode 100644\nindex 0000000..9be16ee\n--- /dev/null\n+++ b/Documentation/git-column.txt\n@@ -0,0 +1,53 @@\n+git-column(1)\n+=============\n+\n+NAME\n+----\n+git-column - Display data in columns\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]\n+\t     [--indent=<string>] [--nl=<string>] [--pading=<n>]\n+\n+DESCRIPTION\n+-----------\n+This command formats its input into multiple columns.\n+\n+OPTIONS\n+-------\n+--command=<name>::\n+\tLook up layout mode using configuration variable column.<name> and\n+\tcolumn.ui.\n+\n+--mode=<mode>::\n+\tSpecify layout mode. See configuration variable column.ui for option\n+\tsyntax.\n+\n+--raw-mode=<n>::\n+\tSame as --mode but take mode encoded as a number. This is mainly used\n+\tby other commands that have already parsed layout mode.\n+\n+--width=<width>::\n+\tSpecify the terminal width. By default 'git column' will detect the\n+\tterminal width, or fall back to 80 if it is unable to do so.\n+\n+--indent=<string>::\n+\tString to be printed at the beginning of each line.\n+\n+--nl=<N>::\n+\tString to be printed at the end of each line,\n+\tincluding newline character.\n+\n+--padding=<N>::\n+\tThe number of spaces between columns. One space by default.\n+\n+\n+Author\n+------\n+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex a0de4e9..0998f0d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o\n LIB_OBJS += bundle.o\n LIB_OBJS += cache-tree.o\n LIB_OBJS += color.o\n+LIB_OBJS += column.o\n LIB_OBJS += combine-diff.o\n LIB_OBJS += commit.o\n LIB_OBJS += compat/obstack.o\n@@ -774,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o\n BUILTIN_OBJS += builtin/checkout.o\n BUILTIN_OBJS += builtin/clean.o\n BUILTIN_OBJS += builtin/clone.o\n+BUILTIN_OBJS += builtin/column.o\n BUILTIN_OBJS += builtin/commit-tree.o\n BUILTIN_OBJS += builtin/commit.o\n BUILTIN_OBJS += builtin/config.o\n@@ -2166,6 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h\n builtin/commit.o builtin/revert.o wt-status.o: wt-status.h\n builtin/tar-tree.o archive-tar.o: tar.h\n connect.o transport.o url.o http-backend.o: url.h\n+column.o help.o pager.o: column.h\n http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h\n http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h\n \ndiff --git a/builtin.h b/builtin.h\nindex 857b9c8..338f540 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);\n extern int cmd_clone(int argc, const char **argv, const char *prefix);\n extern int cmd_clean(int argc, const char **argv, const char *prefix);\n+extern int cmd_column(int argc, const char **argv, const char *prefix);\n extern int cmd_commit(int argc, const char **argv, const char *prefix);\n extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/column.c b/builtin/column.c\nnew file mode 100644\nindex 0000000..5ea798a\n--- /dev/null\n+++ b/builtin/column.c\n@@ -0,0 +1,59 @@\n+#include \"builtin.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"parse-options.h\"\n+#include \"string-list.h\"\n+#include \"column.h\"\n+\n+static const char * const builtin_column_usage[] = {\n+\t\"git column [options]\",\n+\tNULL\n+};\n+static unsigned int colopts;\n+\n+static int column_config(const char *var, const char *value, void *cb)\n+{\n+\treturn git_column_config(var, value, cb, &colopts);\n+}\n+\n+int cmd_column(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct column_options copts;\n+\tconst char *command = NULL, *real_command = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"command\", &real_command, \"name\", \"lookup config vars\"),\n+\t\tOPT_COLUMN(0, \"mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"raw-mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"width\", &copts.width, \"Maximum width\"),\n+\t\tOPT_STRING(0, \"indent\", &copts.indent, \"string\", \"Padding space on left border\"),\n+\t\tOPT_INTEGER(0, \"nl\", &copts.nl, \"Padding space on right border\"),\n+\t\tOPT_INTEGER(0, \"padding\", &copts.padding, \"Padding space between columns\"),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* This one is special and must be the first one */\n+\tif (argc > 1 && !prefixcmp(argv[1], \"--command=\")) {\n+\t\tcommand = argv[1] + 10;\n+\t\tgit_config(column_config, (void *)command);\n+\t} else\n+\t\tgit_config(column_config, NULL);\n+\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.width = term_columns();\n+\tcopts.padding = 1;\n+\targc = parse_options(argc, argv, \"\", options, builtin_column_usage, 0);\n+\tif (argc)\n+\t\tusage_with_options(builtin_column_usage, options);\n+\tif (real_command || command) {\n+\t\tif (!real_command || !command || strcmp(real_command, command))\n+\t\t\tdie(_(\"--command must be the first argument\"));\n+\t}\n+\tfinalize_colopts(&colopts, -1);\n+\twhile (!strbuf_getline(&sb, stdin, '\\n'))\n+\t\tstring_list_append(&list, sb.buf);\n+\n+\tprint_columns(&list, colopts, &copts);\n+\treturn 0;\n+}\ndiff --git a/column.c b/column.c\nnew file mode 100644\nindex 0000000..c2b887d\n--- /dev/null\n+++ b/column.c\n@@ -0,0 +1,167 @@\n+#include \"cache.h\"\n+#include \"column.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+\n+/* Display without layout when not enabled */\n+static void display_plain(const struct string_list *list,\n+\t\t\t  const char *indent, const char *nl)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tprintf(\"%s%s%s\", indent, list->items[i].string, nl);\n+}\n+\n+void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t   const struct column_options *opts)\n+{\n+\tstruct column_options nopts;\n+\n+\tif (!list->nr)\n+\t\treturn;\n+\tassert(COL_ENABLE(colopts) != COL_AUTO);\n+\n+\tmemset(&nopts, 0, sizeof(nopts));\n+\tnopts.indent = opts && opts->indent ? opts->indent : \"\";\n+\tnopts.nl = opts && opts->nl ? opts->nl : \"\\n\";\n+\tnopts.padding = opts ? opts->padding : 1;\n+\tnopts.width = opts && opts->width ? opts->width : term_columns() - 1;\n+\tif (!COL_ENABLE(colopts)) {\n+\t\tdisplay_plain(list, \"\", \"\\n\");\n+\t\treturn;\n+\t}\n+\tswitch (COL_LAYOUT(colopts)) {\n+\tcase COL_PLAIN:\n+\t\tdisplay_plain(list, nopts.indent, nopts.nl);\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid layout mode %d\", COL_LAYOUT(colopts));\n+\t}\n+}\n+\n+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)\n+{\n+\tif (COL_ENABLE(*colopts) == COL_AUTO) {\n+\t\tif (stdout_is_tty < 0)\n+\t\t\tstdout_is_tty = isatty(1);\n+\t\t*colopts &= ~COL_ENABLE_MASK;\n+\t\tif (stdout_is_tty)\n+\t\t\t*colopts |= COL_ENABLED;\n+\t}\n+\treturn 0;\n+}\n+\n+struct colopt {\n+\tconst char *name;\n+\tunsigned int value;\n+\tunsigned int mask;\n+};\n+\n+#define LAYOUT_SET 1\n+#define ENABLE_SET 2\n+\n+static int parse_option(const char *arg, int len, unsigned int *colopts,\n+\t\t\tint *group_set)\n+{\n+\tstruct colopt opts[] = {\n+\t\t{ \"always\", COL_ENABLED,  COL_ENABLE_MASK },\n+\t\t{ \"never\",  COL_DISABLED, COL_ENABLE_MASK },\n+\t\t{ \"auto\",   COL_AUTO,     COL_ENABLE_MASK },\n+\t\t{ \"plain\",  COL_PLAIN,    COL_LAYOUT_MASK },\n+\t};\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(opts); i++) {\n+\t\tint arg_len = len, name_len;\n+\t\tconst char *arg_str = arg;\n+\n+\t\tname_len = strlen(opts[i].name);\n+\t\tif (arg_len != name_len ||\n+\t\t    strncmp(arg_str, opts[i].name, name_len))\n+\t\t\tcontinue;\n+\n+\t\tswitch (opts[i].mask) {\n+\t\tcase COL_ENABLE_MASK:\n+\t\t\t*group_set |= ENABLE_SET;\n+\t\t\tbreak;\n+\t\tcase COL_LAYOUT_MASK:\n+\t\t\t*group_set |= LAYOUT_SET;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (opts[i].mask)\n+\t\t\t*colopts = (*colopts & ~opts[i].mask) | opts[i].value;\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"unsupported option '%s'\", arg);\n+}\n+\n+static int parse_config(unsigned int *colopts, const char *value)\n+{\n+\tconst char *sep = \" ,\";\n+\tint group_set = 0;\n+\n+\twhile (*value) {\n+\t\tint len = strcspn(value, sep);\n+\t\tif (len) {\n+\t\t\tif (parse_option(value, len, colopts, &group_set))\n+\t\t\t\treturn -1;\n+\n+\t\t\tvalue += len;\n+\t\t}\n+\t\tvalue += strspn(value, sep);\n+\t}\n+\t/*\n+\t * Setting layout implies \"always\" if neither always, never\n+\t * nor auto is specified.\n+\t *\n+\t * Current COL_ENABLE() value is disregarded. This means if\n+\t * you set column.ui = auto and pass --column=row, then \"auto\"\n+\t * will become \"always\".\n+\t */\n+\tif ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))\n+\t\t*colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n+\treturn 0;\n+}\n+\n+static int column_config(const char *var, const char *value,\n+\t\t\t const char *key, unsigned int *colopts)\n+{\n+\tif (value && parse_config(colopts, value))\n+\t\treturn error(\"invalid column.%s mode %s\", key, value);\n+\treturn 0;\n+}\n+\n+int git_column_config(const char *var, const char *value,\n+\t\t      const char *command, unsigned int *colopts)\n+{\n+\tconst char *it = skip_prefix(var, \"column.\");\n+\tif (!it)\n+\t\treturn 0;\n+\n+\tif (!strcmp(it, \"ui\"))\n+\t\treturn column_config(var, value, \"ui\", colopts);\n+\n+\tif (command && !strcmp(it, command))\n+\t\treturn column_config(var, value, it, colopts);\n+\n+\treturn 0;\n+}\n+\n+int parseopt_column_callback(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tunsigned int *colopts = opt->value;\n+\t*colopts |= COL_PARSEOPT;\n+\t*colopts &= ~COL_ENABLE_MASK;\n+\tif (unset)\t\t/* --no-column == never */\n+\t\treturn 0;\n+\t/* --column == always unless \"arg\" states otherwise */\n+\t*colopts |= COL_ENABLED;\n+\tif (arg)\n+\t\treturn parse_config(colopts, arg);\n+\n+\treturn 0;\n+}\ndiff --git a/column.h b/column.h\nnew file mode 100644\nindex 0000000..778a71c\n--- /dev/null\n+++ b/column.h\n@@ -0,0 +1,35 @@\n+#ifndef COLUMN_H\n+#define COLUMN_H\n+\n+#define COL_LAYOUT_MASK   0x000F\n+#define COL_ENABLE_MASK   0x0030   /* always, never or auto */\n+#define COL_PARSEOPT      0x0040   /* --column is given from cmdline */\n+\n+#define COL_ENABLE(c) ((c) & COL_ENABLE_MASK)\n+#define COL_DISABLED      0x0000   /* must be zero */\n+#define COL_ENABLED       0x0010\n+#define COL_AUTO          0x0020\n+\n+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)\n+#define COL_PLAIN             15   /* one column */\n+\n+#define explicitly_enable_column(c) \\\n+\t(((c) & COL_PARSEOPT) && COL_ENABLE(c) == COL_ENABLED)\n+\n+struct column_options {\n+\tint width;\n+\tint padding;\n+\tconst char *indent;\n+\tconst char *nl;\n+};\n+\n+struct option;\n+extern int parseopt_column_callback(const struct option *, const char *, int);\n+extern int git_column_config(const char *var, const char *value,\n+\t\t\t     const char *command, unsigned int *colopts);\n+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);\n+\n+extern void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t\t  const struct column_options *opts);\n+\n+#endif\ndiff --git a/command-list.txt b/command-list.txt\nindex a36ee9b..fe06f15 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -20,6 +20,7 @@ git-cherry-pick                         mainporcelain\n git-citool                              mainporcelain\n git-clean                               mainporcelain\n git-clone                               mainporcelain common\n+git-column                              purehelpers\n git-commit                              mainporcelain common\n git-commit-tree                         plumbingmanipulators\n git-config                              ancillarymanipulators\ndiff --git a/git.c b/git.c\nindex 3805616..ee727cb 100644\n--- a/git.c\n+++ b/git.c\n@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clone\", cmd_clone },\n+\t\t{ \"column\", cmd_column, RUN_SETUP_GENTLY },\n \t\t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n \t\t{ \"config\", cmd_config, RUN_SETUP_GENTLY },\ndiff --git a/parse-options.h b/parse-options.h\nindex 2e811dc..56fcafd 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);\n \t  PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }\n #define OPT__COLOR(var, h) \\\n \tOPT_COLOR_FLAG(0, \"color\", (var), (h))\n+#define OPT_COLUMN(s, l, v, h) \\\n+\t{ OPTION_CALLBACK, (s), (l), (v), \"style\", (h), PARSE_OPT_OPTARG, parseopt_column_callback }\n \n #endif\ndiff --git a/t/t9002-column.sh b/t/t9002-column.sh\nnew file mode 100755\nindex 0000000..a7f3cd9\n--- /dev/null\n+++ b/t/t9002-column.sh\n@@ -0,0 +1,45 @@\n+#!/bin/sh\n+\n+test_description='git column'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tcat >lista <<\\EOF\n+one\n+two\n+three\n+four\n+five\n+six\n+seven\n+eight\n+nine\n+ten\n+eleven\n+EOF\n+'\n+\n+test_expect_success 'never' '\n+\tgit column --indent=Z --mode=never <lista >actual &&\n+\ttest_cmp lista actual\n+'\n+\n+test_expect_success 'always' '\n+\tcat >expected <<\\EOF &&\n+Zone\n+Ztwo\n+Zthree\n+Zfour\n+Zfive\n+Zsix\n+Zseven\n+Zeight\n+Znine\n+Zten\n+Zeleven\n+EOF\n+\tgit column --indent=Z --mode=plain <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"189425","messageId":"7v4nsjy85r.fsf@alter.siamese.dyndns.org","threadId":"30231","inReplyTo":"1334582784-25723-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] Add column layout skeleton and git-column","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-16T16:05:04Z","receivedAt":"2012-04-16T16:05:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:\n\n> +static int parse_config(unsigned int *colopts, const char *value)\n> +{\n> +\tconst char *sep = \" ,\";\n> +\tint group_set = 0;\n> +\n> +\twhile (*value) {\n> +\t\tint len = strcspn(value, sep);\n> +\t\tif (len) {\n> +\t\t\tif (parse_option(value, len, colopts, &group_set))\n> +\t\t\t\treturn -1;\n> +\n> +\t\t\tvalue += len;\n> +\t\t}\n> +\t\tvalue += strspn(value, sep);\n> +\t}\n> +\t/*\n> +\t * Setting layout implies \"always\" if neither always, never\n> +\t * nor auto is specified.\n> +\t *\n> +\t * Current COL_ENABLE() value is disregarded. This means if\n> +\t * you set column.ui = auto and pass --column=row, then \"auto\"\n> +\t * will become \"always\".\n> +\t */\n> +\tif ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))\n> +\t\t*colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n> +\treturn 0;\n> +}\n> +\n> +static int column_config(const char *var, const char *value,\n> +\t\t\t const char *key, unsigned int *colopts)\n> +{\n> +\tif (value && parse_config(colopts, value))\n> +\t\treturn error(\"invalid column.%s mode %s\", key, value);\n\nIf column.* can never be a boolean, this code shouldn't silently ignore\n\n\t[column]\n        \tui\n\nbut explicitly error out.  See git_config_string() for an example if you\nwant to go this route.\n\nOn the other hand, it might make sense to equate a \"true\" with \"a sane\ndefault\" (i.e. the user declares that s/he trusts whatever default the Git\nimplementation deems sane), in which case\n\n\tif (!value)\n\t\tvalue = \"auto,whatever,column,default,mode\";\n\tif (parse_config(colopts, value))\n        \treturn error(\"invalid column.%s mode...\", ...);\n\nmight be a better way to go.  If you take this route, a single-token\ncolumn.* becomes a variable that takes boolean and more, so the existing\n`never` has to gain `no`, `false`, etc. as synonyms, and `true`, `1`,\netc. needs to be interpreted the same as `auto` for consistency.\n"},{"id":"189792","messageId":"1334977648-872-1-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"7v4nsjy85r.fsf@alter.siamese.dyndns.org","subject":"[PATCH] Add column layout skeleton and git-column","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-21T03:07:28Z","receivedAt":"2012-04-21T03:07:28Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"A column option string consists of many token separated by either\na space or a  comma. A token belongs to one of three groups:\n\n - enabling: always, never and auto\n - layout mode: currently plain (which does not layout at all)\n - other future tuning flags\n\ngit-column can be used to pipe output to from a command that wants\ncolumn layout, but not to mess with its own output code. Simpler output\ncode can be changed to use column layout code directly.\n\nThanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n 2012/4/16 Junio C Hamano <gitster@pobox.com>:\n >> +static int column_config(const char *var, const char *value,\n >> +                      const char *key, unsigned int *colopts)\n >> +{\n >> +     if (value && parse_config(colopts, value))\n >> +             return error(\"invalid column.%s mode %s\", key, value);\n >\n > If column.* can never be a boolean, this code shouldn't silently ignore\n >\n >        [column]\n >                ui\n >\n > but explicitly error out.  See git_config_string() for an example if you\n > want to go this route.\n >\n > On the other hand, it might make sense to equate a \"true\" with \"a sane\n > default\" (i.e. the user declares that s/he trusts whatever default the Git\n > implementation deems sane), in which case\n >\n >        if (!value)\n >                value = \"auto,whatever,column,default,mode\";\n >        if (parse_config(colopts, value))\n >                return error(\"invalid column.%s mode...\", ...);\n >\n > might be a better way to go.  If you take this route, a single-token\n > column.* becomes a variable that takes boolean and more, so the existing\n > `never` has to gain `no`, `false`, etc. as synonyms, and `true`, `1`,\n > etc. needs to be interpreted the same as `auto` for consistency.\n\n Thanks. I'll go with the simple way: error out.\n\n .gitignore                   |    1 +\n Documentation/config.txt     |   18 +++++\n Documentation/git-column.txt |   53 +++++++++++++\n Makefile                     |    3 +\n builtin.h                    |    1 +\n builtin/column.c             |   59 +++++++++++++++\n column.c                     |  169 ++++++++++++++++++++++++++++++++++++++++++\n column.h                     |   35 +++++++++\n command-list.txt             |    1 +\n git.c                        |    1 +\n parse-options.h              |    2 +\n t/t9002-column.sh            |   45 +++++++++++\n 12 files changed, 388 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-column.txt\n create mode 100644 builtin/column.c\n create mode 100644 column.c\n create mode 100644 column.h\n create mode 100755 t/t9002-column.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 87fcc5f..2540264 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -26,6 +26,7 @@\n /git-cherry-pick\n /git-clean\n /git-clone\n+/git-column\n /git-commit\n /git-commit-tree\n /git-config\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e55dae1..9aabef1 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -836,6 +836,24 @@ color.ui::\n \t`never` if you prefer git commands not to use color unless enabled\n \texplicitly with some other configuration or the `--color` option.\n \n+column.ui::\n+\tSpecify whether supported commands should output in columns.\n+\tThis variable consists of a list of tokens separated by spaces\n+\tor commas:\n++\n+--\n+`always`;;\n+\talways show in columns\n+`never`;;\n+\tnever show in columns\n+`auto`;;\n+\tshow in columns if the output is to the terminal\n+`plain`;;\n+\tshow in one column\n+--\n++\n+\tThis option defaults to 'never'.\n+\n commit.status::\n \tA boolean to enable/disable inclusion of status information in the\n \tcommit message template when using an editor to prepare the commit\ndiff --git a/Documentation/git-column.txt b/Documentation/git-column.txt\nnew file mode 100644\nindex 0000000..9be16ee\n--- /dev/null\n+++ b/Documentation/git-column.txt\n@@ -0,0 +1,53 @@\n+git-column(1)\n+=============\n+\n+NAME\n+----\n+git-column - Display data in columns\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]\n+\t     [--indent=<string>] [--nl=<string>] [--pading=<n>]\n+\n+DESCRIPTION\n+-----------\n+This command formats its input into multiple columns.\n+\n+OPTIONS\n+-------\n+--command=<name>::\n+\tLook up layout mode using configuration variable column.<name> and\n+\tcolumn.ui.\n+\n+--mode=<mode>::\n+\tSpecify layout mode. See configuration variable column.ui for option\n+\tsyntax.\n+\n+--raw-mode=<n>::\n+\tSame as --mode but take mode encoded as a number. This is mainly used\n+\tby other commands that have already parsed layout mode.\n+\n+--width=<width>::\n+\tSpecify the terminal width. By default 'git column' will detect the\n+\tterminal width, or fall back to 80 if it is unable to do so.\n+\n+--indent=<string>::\n+\tString to be printed at the beginning of each line.\n+\n+--nl=<N>::\n+\tString to be printed at the end of each line,\n+\tincluding newline character.\n+\n+--padding=<N>::\n+\tThe number of spaces between columns. One space by default.\n+\n+\n+Author\n+------\n+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 1fb1705..b579b6b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o\n LIB_OBJS += bundle.o\n LIB_OBJS += cache-tree.o\n LIB_OBJS += color.o\n+LIB_OBJS += column.o\n LIB_OBJS += combine-diff.o\n LIB_OBJS += commit.o\n LIB_OBJS += compat/obstack.o\n@@ -774,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o\n BUILTIN_OBJS += builtin/checkout.o\n BUILTIN_OBJS += builtin/clean.o\n BUILTIN_OBJS += builtin/clone.o\n+BUILTIN_OBJS += builtin/column.o\n BUILTIN_OBJS += builtin/commit-tree.o\n BUILTIN_OBJS += builtin/commit.o\n BUILTIN_OBJS += builtin/config.o\n@@ -2166,6 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h\n builtin/commit.o builtin/revert.o wt-status.o: wt-status.h\n builtin/tar-tree.o archive-tar.o: tar.h\n connect.o transport.o url.o http-backend.o: url.h\n+column.o help.o pager.o: column.h\n http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h\n http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h\n \ndiff --git a/builtin.h b/builtin.h\nindex 857b9c8..338f540 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);\n extern int cmd_clone(int argc, const char **argv, const char *prefix);\n extern int cmd_clean(int argc, const char **argv, const char *prefix);\n+extern int cmd_column(int argc, const char **argv, const char *prefix);\n extern int cmd_commit(int argc, const char **argv, const char *prefix);\n extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/column.c b/builtin/column.c\nnew file mode 100644\nindex 0000000..5ea798a\n--- /dev/null\n+++ b/builtin/column.c\n@@ -0,0 +1,59 @@\n+#include \"builtin.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"parse-options.h\"\n+#include \"string-list.h\"\n+#include \"column.h\"\n+\n+static const char * const builtin_column_usage[] = {\n+\t\"git column [options]\",\n+\tNULL\n+};\n+static unsigned int colopts;\n+\n+static int column_config(const char *var, const char *value, void *cb)\n+{\n+\treturn git_column_config(var, value, cb, &colopts);\n+}\n+\n+int cmd_column(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct column_options copts;\n+\tconst char *command = NULL, *real_command = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"command\", &real_command, \"name\", \"lookup config vars\"),\n+\t\tOPT_COLUMN(0, \"mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"raw-mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"width\", &copts.width, \"Maximum width\"),\n+\t\tOPT_STRING(0, \"indent\", &copts.indent, \"string\", \"Padding space on left border\"),\n+\t\tOPT_INTEGER(0, \"nl\", &copts.nl, \"Padding space on right border\"),\n+\t\tOPT_INTEGER(0, \"padding\", &copts.padding, \"Padding space between columns\"),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* This one is special and must be the first one */\n+\tif (argc > 1 && !prefixcmp(argv[1], \"--command=\")) {\n+\t\tcommand = argv[1] + 10;\n+\t\tgit_config(column_config, (void *)command);\n+\t} else\n+\t\tgit_config(column_config, NULL);\n+\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.width = term_columns();\n+\tcopts.padding = 1;\n+\targc = parse_options(argc, argv, \"\", options, builtin_column_usage, 0);\n+\tif (argc)\n+\t\tusage_with_options(builtin_column_usage, options);\n+\tif (real_command || command) {\n+\t\tif (!real_command || !command || strcmp(real_command, command))\n+\t\t\tdie(_(\"--command must be the first argument\"));\n+\t}\n+\tfinalize_colopts(&colopts, -1);\n+\twhile (!strbuf_getline(&sb, stdin, '\\n'))\n+\t\tstring_list_append(&list, sb.buf);\n+\n+\tprint_columns(&list, colopts, &copts);\n+\treturn 0;\n+}\ndiff --git a/column.c b/column.c\nnew file mode 100644\nindex 0000000..416a440\n--- /dev/null\n+++ b/column.c\n@@ -0,0 +1,169 @@\n+#include \"cache.h\"\n+#include \"column.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+\n+/* Display without layout when not enabled */\n+static void display_plain(const struct string_list *list,\n+\t\t\t  const char *indent, const char *nl)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tprintf(\"%s%s%s\", indent, list->items[i].string, nl);\n+}\n+\n+void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t   const struct column_options *opts)\n+{\n+\tstruct column_options nopts;\n+\n+\tif (!list->nr)\n+\t\treturn;\n+\tassert(COL_ENABLE(colopts) != COL_AUTO);\n+\n+\tmemset(&nopts, 0, sizeof(nopts));\n+\tnopts.indent = opts && opts->indent ? opts->indent : \"\";\n+\tnopts.nl = opts && opts->nl ? opts->nl : \"\\n\";\n+\tnopts.padding = opts ? opts->padding : 1;\n+\tnopts.width = opts && opts->width ? opts->width : term_columns() - 1;\n+\tif (!COL_ENABLE(colopts)) {\n+\t\tdisplay_plain(list, \"\", \"\\n\");\n+\t\treturn;\n+\t}\n+\tswitch (COL_LAYOUT(colopts)) {\n+\tcase COL_PLAIN:\n+\t\tdisplay_plain(list, nopts.indent, nopts.nl);\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid layout mode %d\", COL_LAYOUT(colopts));\n+\t}\n+}\n+\n+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)\n+{\n+\tif (COL_ENABLE(*colopts) == COL_AUTO) {\n+\t\tif (stdout_is_tty < 0)\n+\t\t\tstdout_is_tty = isatty(1);\n+\t\t*colopts &= ~COL_ENABLE_MASK;\n+\t\tif (stdout_is_tty)\n+\t\t\t*colopts |= COL_ENABLED;\n+\t}\n+\treturn 0;\n+}\n+\n+struct colopt {\n+\tconst char *name;\n+\tunsigned int value;\n+\tunsigned int mask;\n+};\n+\n+#define LAYOUT_SET 1\n+#define ENABLE_SET 2\n+\n+static int parse_option(const char *arg, int len, unsigned int *colopts,\n+\t\t\tint *group_set)\n+{\n+\tstruct colopt opts[] = {\n+\t\t{ \"always\", COL_ENABLED,  COL_ENABLE_MASK },\n+\t\t{ \"never\",  COL_DISABLED, COL_ENABLE_MASK },\n+\t\t{ \"auto\",   COL_AUTO,     COL_ENABLE_MASK },\n+\t\t{ \"plain\",  COL_PLAIN,    COL_LAYOUT_MASK },\n+\t};\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(opts); i++) {\n+\t\tint arg_len = len, name_len;\n+\t\tconst char *arg_str = arg;\n+\n+\t\tname_len = strlen(opts[i].name);\n+\t\tif (arg_len != name_len ||\n+\t\t    strncmp(arg_str, opts[i].name, name_len))\n+\t\t\tcontinue;\n+\n+\t\tswitch (opts[i].mask) {\n+\t\tcase COL_ENABLE_MASK:\n+\t\t\t*group_set |= ENABLE_SET;\n+\t\t\tbreak;\n+\t\tcase COL_LAYOUT_MASK:\n+\t\t\t*group_set |= LAYOUT_SET;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (opts[i].mask)\n+\t\t\t*colopts = (*colopts & ~opts[i].mask) | opts[i].value;\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"unsupported option '%s'\", arg);\n+}\n+\n+static int parse_config(unsigned int *colopts, const char *value)\n+{\n+\tconst char *sep = \" ,\";\n+\tint group_set = 0;\n+\n+\twhile (*value) {\n+\t\tint len = strcspn(value, sep);\n+\t\tif (len) {\n+\t\t\tif (parse_option(value, len, colopts, &group_set))\n+\t\t\t\treturn -1;\n+\n+\t\t\tvalue += len;\n+\t\t}\n+\t\tvalue += strspn(value, sep);\n+\t}\n+\t/*\n+\t * Setting layout implies \"always\" if neither always, never\n+\t * nor auto is specified.\n+\t *\n+\t * Current COL_ENABLE() value is disregarded. This means if\n+\t * you set column.ui = auto and pass --column=row, then \"auto\"\n+\t * will become \"always\".\n+\t */\n+\tif ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))\n+\t\t*colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n+\treturn 0;\n+}\n+\n+static int column_config(const char *var, const char *value,\n+\t\t\t const char *key, unsigned int *colopts)\n+{\n+\tif (!value)\n+\t\treturn config_error_nonbool(var);\n+\tif (parse_config(colopts, value))\n+\t\treturn error(\"invalid column.%s mode %s\", key, value);\n+\treturn 0;\n+}\n+\n+int git_column_config(const char *var, const char *value,\n+\t\t      const char *command, unsigned int *colopts)\n+{\n+\tconst char *it = skip_prefix(var, \"column.\");\n+\tif (!it)\n+\t\treturn 0;\n+\n+\tif (!strcmp(it, \"ui\"))\n+\t\treturn column_config(var, value, \"ui\", colopts);\n+\n+\tif (command && !strcmp(it, command))\n+\t\treturn column_config(var, value, it, colopts);\n+\n+\treturn 0;\n+}\n+\n+int parseopt_column_callback(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tunsigned int *colopts = opt->value;\n+\t*colopts |= COL_PARSEOPT;\n+\t*colopts &= ~COL_ENABLE_MASK;\n+\tif (unset)\t\t/* --no-column == never */\n+\t\treturn 0;\n+\t/* --column == always unless \"arg\" states otherwise */\n+\t*colopts |= COL_ENABLED;\n+\tif (arg)\n+\t\treturn parse_config(colopts, arg);\n+\n+\treturn 0;\n+}\ndiff --git a/column.h b/column.h\nnew file mode 100644\nindex 0000000..778a71c\n--- /dev/null\n+++ b/column.h\n@@ -0,0 +1,35 @@\n+#ifndef COLUMN_H\n+#define COLUMN_H\n+\n+#define COL_LAYOUT_MASK   0x000F\n+#define COL_ENABLE_MASK   0x0030   /* always, never or auto */\n+#define COL_PARSEOPT      0x0040   /* --column is given from cmdline */\n+\n+#define COL_ENABLE(c) ((c) & COL_ENABLE_MASK)\n+#define COL_DISABLED      0x0000   /* must be zero */\n+#define COL_ENABLED       0x0010\n+#define COL_AUTO          0x0020\n+\n+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)\n+#define COL_PLAIN             15   /* one column */\n+\n+#define explicitly_enable_column(c) \\\n+\t(((c) & COL_PARSEOPT) && COL_ENABLE(c) == COL_ENABLED)\n+\n+struct column_options {\n+\tint width;\n+\tint padding;\n+\tconst char *indent;\n+\tconst char *nl;\n+};\n+\n+struct option;\n+extern int parseopt_column_callback(const struct option *, const char *, int);\n+extern int git_column_config(const char *var, const char *value,\n+\t\t\t     const char *command, unsigned int *colopts);\n+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);\n+\n+extern void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t\t  const struct column_options *opts);\n+\n+#endif\ndiff --git a/command-list.txt b/command-list.txt\nindex a36ee9b..fe06f15 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -20,6 +20,7 @@ git-cherry-pick                         mainporcelain\n git-citool                              mainporcelain\n git-clean                               mainporcelain\n git-clone                               mainporcelain common\n+git-column                              purehelpers\n git-commit                              mainporcelain common\n git-commit-tree                         plumbingmanipulators\n git-config                              ancillarymanipulators\ndiff --git a/git.c b/git.c\nindex 3805616..ee727cb 100644\n--- a/git.c\n+++ b/git.c\n@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clone\", cmd_clone },\n+\t\t{ \"column\", cmd_column, RUN_SETUP_GENTLY },\n \t\t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n \t\t{ \"config\", cmd_config, RUN_SETUP_GENTLY },\ndiff --git a/parse-options.h b/parse-options.h\nindex 2e811dc..56fcafd 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);\n \t  PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }\n #define OPT__COLOR(var, h) \\\n \tOPT_COLOR_FLAG(0, \"color\", (var), (h))\n+#define OPT_COLUMN(s, l, v, h) \\\n+\t{ OPTION_CALLBACK, (s), (l), (v), \"style\", (h), PARSE_OPT_OPTARG, parseopt_column_callback }\n \n #endif\ndiff --git a/t/t9002-column.sh b/t/t9002-column.sh\nnew file mode 100755\nindex 0000000..a7f3cd9\n--- /dev/null\n+++ b/t/t9002-column.sh\n@@ -0,0 +1,45 @@\n+#!/bin/sh\n+\n+test_description='git column'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tcat >lista <<\\EOF\n+one\n+two\n+three\n+four\n+five\n+six\n+seven\n+eight\n+nine\n+ten\n+eleven\n+EOF\n+'\n+\n+test_expect_success 'never' '\n+\tgit column --indent=Z --mode=never <lista >actual &&\n+\ttest_cmp lista actual\n+'\n+\n+test_expect_success 'always' '\n+\tcat >expected <<\\EOF &&\n+Zone\n+Ztwo\n+Zthree\n+Zfour\n+Zfive\n+Zsix\n+Zseven\n+Zeight\n+Znine\n+Zten\n+Zeleven\n+EOF\n+\tgit column --indent=Z --mode=plain <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.7.8.36.g69ee2\n"},{"id":"189795","messageId":"xmqqlilpzpiz.fsf@junio.mtv.corp.google.com","threadId":"30231","inReplyTo":"1334977648-872-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] Add column layout skeleton and git-column","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-21T04:18:12Z","receivedAt":"2012-04-21T04:18:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n>  Thanks. I'll go with the simple way: error out.\n\nOK.\n\n> diff --git a/column.c b/column.c\n> new file mode 100644\n> index 0000000..416a440\n> --- /dev/null\n> +++ b/column.c\n> @@ -0,0 +1,169 @@\n> +#include \"cache.h\"\n> +#include \"column.h\"\n> +#include \"string-list.h\"\n> +#include \"parse-options.h\"\n> +\n> +/* Display without layout when not enabled */\n> +static void display_plain(const struct string_list *list,\n> +\t\t\t  const char *indent, const char *nl)\n> +{\n> +\tint i;\n> +\n> +\tfor (i = 0; i < list->nr; i++)\n> +\t\tprintf(\"%s%s%s\", indent, list->items[i].string, nl);\n> +}\n> +\n> +void print_columns(const struct string_list *list, unsigned int colopts,\n> +\t\t   const struct column_options *opts)\n> +{\n> +\tstruct column_options nopts;\n> +\n> +\tif (!list->nr)\n> +\t\treturn;\n> +\tassert(COL_ENABLE(colopts) != COL_AUTO);\n\nAm I looking at the correct version?  Somehow I thought you eliminated\nthis confusingly named macro in the previous round.  Two steps forward,\none step backward???\n"},{"id":"189796","messageId":"1334983472-3172-1-git-send-email-pclouds@gmail.com","threadId":"30231","inReplyTo":"xmqqlilpzpiz.fsf@junio.mtv.corp.google.com","subject":"[PATCH] Add column layout skeleton and git-column","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-04-21T04:44:32Z","receivedAt":"2012-04-21T04:44:32Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"A column option string consists of many token separated by either\na space or a  comma. A token belongs to one of three groups:\n\n - enabling: always, never and auto\n - layout mode: currently plain (which does not layout at all)\n - other future tuning flags\n\ngit-column can be used to pipe output to from a command that wants\ncolumn layout, but not to mess with its own output code. Simpler output\ncode can be changed to use column layout code directly.\n\nThanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk>\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n 2012/4/21 Junio C Hamano <gitster@pobox.com>:\n > Am I looking at the correct version?  Somehow I thought you eliminated\n > this confusingly named macro in the previous round.  Two steps forward,\n > one step backward???\n\n Aah.. the beautiful result of having the same series (but probably\n different versions) across several machines.. This version should be\n the right one with fixes.\n\n .gitignore                   |    1 +\n Documentation/config.txt     |   18 +++++\n Documentation/git-column.txt |   53 +++++++++++++\n Makefile                     |    3 +\n builtin.h                    |    1 +\n builtin/column.c             |   59 +++++++++++++++\n column.c                     |  169 ++++++++++++++++++++++++++++++++++++++++++\n column.h                     |   38 ++++++++++\n command-list.txt             |    1 +\n git.c                        |    1 +\n parse-options.h              |    2 +\n t/t9002-column.sh            |   45 +++++++++++\n 12 files changed, 391 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-column.txt\n create mode 100644 builtin/column.c\n create mode 100644 column.c\n create mode 100644 column.h\n create mode 100755 t/t9002-column.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 87fcc5f..2540264 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -26,6 +26,7 @@\n /git-cherry-pick\n /git-clean\n /git-clone\n+/git-column\n /git-commit\n /git-commit-tree\n /git-config\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex e55dae1..9aabef1 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -836,6 +836,24 @@ color.ui::\n \t`never` if you prefer git commands not to use color unless enabled\n \texplicitly with some other configuration or the `--color` option.\n \n+column.ui::\n+\tSpecify whether supported commands should output in columns.\n+\tThis variable consists of a list of tokens separated by spaces\n+\tor commas:\n++\n+--\n+`always`;;\n+\talways show in columns\n+`never`;;\n+\tnever show in columns\n+`auto`;;\n+\tshow in columns if the output is to the terminal\n+`plain`;;\n+\tshow in one column\n+--\n++\n+\tThis option defaults to 'never'.\n+\n commit.status::\n \tA boolean to enable/disable inclusion of status information in the\n \tcommit message template when using an editor to prepare the commit\ndiff --git a/Documentation/git-column.txt b/Documentation/git-column.txt\nnew file mode 100644\nindex 0000000..9be16ee\n--- /dev/null\n+++ b/Documentation/git-column.txt\n@@ -0,0 +1,53 @@\n+git-column(1)\n+=============\n+\n+NAME\n+----\n+git-column - Display data in columns\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]\n+\t     [--indent=<string>] [--nl=<string>] [--pading=<n>]\n+\n+DESCRIPTION\n+-----------\n+This command formats its input into multiple columns.\n+\n+OPTIONS\n+-------\n+--command=<name>::\n+\tLook up layout mode using configuration variable column.<name> and\n+\tcolumn.ui.\n+\n+--mode=<mode>::\n+\tSpecify layout mode. See configuration variable column.ui for option\n+\tsyntax.\n+\n+--raw-mode=<n>::\n+\tSame as --mode but take mode encoded as a number. This is mainly used\n+\tby other commands that have already parsed layout mode.\n+\n+--width=<width>::\n+\tSpecify the terminal width. By default 'git column' will detect the\n+\tterminal width, or fall back to 80 if it is unable to do so.\n+\n+--indent=<string>::\n+\tString to be printed at the beginning of each line.\n+\n+--nl=<N>::\n+\tString to be printed at the end of each line,\n+\tincluding newline character.\n+\n+--padding=<N>::\n+\tThe number of spaces between columns. One space by default.\n+\n+\n+Author\n+------\n+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex 1fb1705..b579b6b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o\n LIB_OBJS += bundle.o\n LIB_OBJS += cache-tree.o\n LIB_OBJS += color.o\n+LIB_OBJS += column.o\n LIB_OBJS += combine-diff.o\n LIB_OBJS += commit.o\n LIB_OBJS += compat/obstack.o\n@@ -774,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o\n BUILTIN_OBJS += builtin/checkout.o\n BUILTIN_OBJS += builtin/clean.o\n BUILTIN_OBJS += builtin/clone.o\n+BUILTIN_OBJS += builtin/column.o\n BUILTIN_OBJS += builtin/commit-tree.o\n BUILTIN_OBJS += builtin/commit.o\n BUILTIN_OBJS += builtin/config.o\n@@ -2166,6 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h\n builtin/commit.o builtin/revert.o wt-status.o: wt-status.h\n builtin/tar-tree.o archive-tar.o: tar.h\n connect.o transport.o url.o http-backend.o: url.h\n+column.o help.o pager.o: column.h\n http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h\n http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h\n \ndiff --git a/builtin.h b/builtin.h\nindex 857b9c8..338f540 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);\n extern int cmd_clone(int argc, const char **argv, const char *prefix);\n extern int cmd_clean(int argc, const char **argv, const char *prefix);\n+extern int cmd_column(int argc, const char **argv, const char *prefix);\n extern int cmd_commit(int argc, const char **argv, const char *prefix);\n extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_config(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/column.c b/builtin/column.c\nnew file mode 100644\nindex 0000000..5ea798a\n--- /dev/null\n+++ b/builtin/column.c\n@@ -0,0 +1,59 @@\n+#include \"builtin.h\"\n+#include \"cache.h\"\n+#include \"strbuf.h\"\n+#include \"parse-options.h\"\n+#include \"string-list.h\"\n+#include \"column.h\"\n+\n+static const char * const builtin_column_usage[] = {\n+\t\"git column [options]\",\n+\tNULL\n+};\n+static unsigned int colopts;\n+\n+static int column_config(const char *var, const char *value, void *cb)\n+{\n+\treturn git_column_config(var, value, cb, &colopts);\n+}\n+\n+int cmd_column(int argc, const char **argv, const char *prefix)\n+{\n+\tstruct string_list list = STRING_LIST_INIT_DUP;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct column_options copts;\n+\tconst char *command = NULL, *real_command = NULL;\n+\tstruct option options[] = {\n+\t\tOPT_STRING(0, \"command\", &real_command, \"name\", \"lookup config vars\"),\n+\t\tOPT_COLUMN(0, \"mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"raw-mode\", &colopts, \"layout to use\"),\n+\t\tOPT_INTEGER(0, \"width\", &copts.width, \"Maximum width\"),\n+\t\tOPT_STRING(0, \"indent\", &copts.indent, \"string\", \"Padding space on left border\"),\n+\t\tOPT_INTEGER(0, \"nl\", &copts.nl, \"Padding space on right border\"),\n+\t\tOPT_INTEGER(0, \"padding\", &copts.padding, \"Padding space between columns\"),\n+\t\tOPT_END()\n+\t};\n+\n+\t/* This one is special and must be the first one */\n+\tif (argc > 1 && !prefixcmp(argv[1], \"--command=\")) {\n+\t\tcommand = argv[1] + 10;\n+\t\tgit_config(column_config, (void *)command);\n+\t} else\n+\t\tgit_config(column_config, NULL);\n+\n+\tmemset(&copts, 0, sizeof(copts));\n+\tcopts.width = term_columns();\n+\tcopts.padding = 1;\n+\targc = parse_options(argc, argv, \"\", options, builtin_column_usage, 0);\n+\tif (argc)\n+\t\tusage_with_options(builtin_column_usage, options);\n+\tif (real_command || command) {\n+\t\tif (!real_command || !command || strcmp(real_command, command))\n+\t\t\tdie(_(\"--command must be the first argument\"));\n+\t}\n+\tfinalize_colopts(&colopts, -1);\n+\twhile (!strbuf_getline(&sb, stdin, '\\n'))\n+\t\tstring_list_append(&list, sb.buf);\n+\n+\tprint_columns(&list, colopts, &copts);\n+\treturn 0;\n+}\ndiff --git a/column.c b/column.c\nnew file mode 100644\nindex 0000000..3349f58\n--- /dev/null\n+++ b/column.c\n@@ -0,0 +1,169 @@\n+#include \"cache.h\"\n+#include \"column.h\"\n+#include \"string-list.h\"\n+#include \"parse-options.h\"\n+\n+/* Display without layout when not enabled */\n+static void display_plain(const struct string_list *list,\n+\t\t\t  const char *indent, const char *nl)\n+{\n+\tint i;\n+\n+\tfor (i = 0; i < list->nr; i++)\n+\t\tprintf(\"%s%s%s\", indent, list->items[i].string, nl);\n+}\n+\n+void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t   const struct column_options *opts)\n+{\n+\tstruct column_options nopts;\n+\n+\tif (!list->nr)\n+\t\treturn;\n+\tassert((colopts & COL_ENABLE_MASK) != COL_AUTO);\n+\n+\tmemset(&nopts, 0, sizeof(nopts));\n+\tnopts.indent = opts && opts->indent ? opts->indent : \"\";\n+\tnopts.nl = opts && opts->nl ? opts->nl : \"\\n\";\n+\tnopts.padding = opts ? opts->padding : 1;\n+\tnopts.width = opts && opts->width ? opts->width : term_columns() - 1;\n+\tif (!column_active(colopts)) {\n+\t\tdisplay_plain(list, \"\", \"\\n\");\n+\t\treturn;\n+\t}\n+\tswitch (COL_LAYOUT(colopts)) {\n+\tcase COL_PLAIN:\n+\t\tdisplay_plain(list, nopts.indent, nopts.nl);\n+\t\tbreak;\n+\tdefault:\n+\t\tdie(\"BUG: invalid layout mode %d\", COL_LAYOUT(colopts));\n+\t}\n+}\n+\n+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)\n+{\n+\tif ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {\n+\t\tif (stdout_is_tty < 0)\n+\t\t\tstdout_is_tty = isatty(1);\n+\t\t*colopts &= ~COL_ENABLE_MASK;\n+\t\tif (stdout_is_tty)\n+\t\t\t*colopts |= COL_ENABLED;\n+\t}\n+\treturn 0;\n+}\n+\n+struct colopt {\n+\tconst char *name;\n+\tunsigned int value;\n+\tunsigned int mask;\n+};\n+\n+#define LAYOUT_SET 1\n+#define ENABLE_SET 2\n+\n+static int parse_option(const char *arg, int len, unsigned int *colopts,\n+\t\t\tint *group_set)\n+{\n+\tstruct colopt opts[] = {\n+\t\t{ \"always\", COL_ENABLED,  COL_ENABLE_MASK },\n+\t\t{ \"never\",  COL_DISABLED, COL_ENABLE_MASK },\n+\t\t{ \"auto\",   COL_AUTO,     COL_ENABLE_MASK },\n+\t\t{ \"plain\",  COL_PLAIN,    COL_LAYOUT_MASK },\n+\t};\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(opts); i++) {\n+\t\tint arg_len = len, name_len;\n+\t\tconst char *arg_str = arg;\n+\n+\t\tname_len = strlen(opts[i].name);\n+\t\tif (arg_len != name_len ||\n+\t\t    strncmp(arg_str, opts[i].name, name_len))\n+\t\t\tcontinue;\n+\n+\t\tswitch (opts[i].mask) {\n+\t\tcase COL_ENABLE_MASK:\n+\t\t\t*group_set |= ENABLE_SET;\n+\t\t\tbreak;\n+\t\tcase COL_LAYOUT_MASK:\n+\t\t\t*group_set |= LAYOUT_SET;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (opts[i].mask)\n+\t\t\t*colopts = (*colopts & ~opts[i].mask) | opts[i].value;\n+\t\treturn 0;\n+\t}\n+\n+\treturn error(\"unsupported option '%s'\", arg);\n+}\n+\n+static int parse_config(unsigned int *colopts, const char *value)\n+{\n+\tconst char *sep = \" ,\";\n+\tint group_set = 0;\n+\n+\twhile (*value) {\n+\t\tint len = strcspn(value, sep);\n+\t\tif (len) {\n+\t\t\tif (parse_option(value, len, colopts, &group_set))\n+\t\t\t\treturn -1;\n+\n+\t\t\tvalue += len;\n+\t\t}\n+\t\tvalue += strspn(value, sep);\n+\t}\n+\t/*\n+\t * Setting layout implies \"always\" if neither always, never\n+\t * nor auto is specified.\n+\t *\n+\t * Current value in COL_ENABLE_MASK is disregarded. This means if\n+\t * you set column.ui = auto and pass --column=row, then \"auto\"\n+\t * will become \"always\".\n+\t */\n+\tif ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))\n+\t\t*colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;\n+\treturn 0;\n+}\n+\n+static int column_config(const char *var, const char *value,\n+\t\t\t const char *key, unsigned int *colopts)\n+{\n+\tif (!value)\n+\t\treturn config_error_nonbool(var);\n+\tif (parse_config(colopts, value))\n+\t\treturn error(\"invalid column.%s mode %s\", key, value);\n+\treturn 0;\n+}\n+\n+int git_column_config(const char *var, const char *value,\n+\t\t      const char *command, unsigned int *colopts)\n+{\n+\tconst char *it = skip_prefix(var, \"column.\");\n+\tif (!it)\n+\t\treturn 0;\n+\n+\tif (!strcmp(it, \"ui\"))\n+\t\treturn column_config(var, value, \"ui\", colopts);\n+\n+\tif (command && !strcmp(it, command))\n+\t\treturn column_config(var, value, it, colopts);\n+\n+\treturn 0;\n+}\n+\n+int parseopt_column_callback(const struct option *opt,\n+\t\t\t     const char *arg, int unset)\n+{\n+\tunsigned int *colopts = opt->value;\n+\t*colopts |= COL_PARSEOPT;\n+\t*colopts &= ~COL_ENABLE_MASK;\n+\tif (unset)\t\t/* --no-column == never */\n+\t\treturn 0;\n+\t/* --column == always unless \"arg\" states otherwise */\n+\t*colopts |= COL_ENABLED;\n+\tif (arg)\n+\t\treturn parse_config(colopts, arg);\n+\n+\treturn 0;\n+}\ndiff --git a/column.h b/column.h\nnew file mode 100644\nindex 0000000..b8719b3\n--- /dev/null\n+++ b/column.h\n@@ -0,0 +1,38 @@\n+#ifndef COLUMN_H\n+#define COLUMN_H\n+\n+#define COL_LAYOUT_MASK   0x000F\n+#define COL_ENABLE_MASK   0x0030   /* always, never or auto */\n+#define COL_PARSEOPT      0x0040   /* --column is given from cmdline */\n+\n+#define COL_DISABLED      0x0000   /* must be zero */\n+#define COL_ENABLED       0x0010\n+#define COL_AUTO          0x0020\n+\n+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)\n+#define COL_PLAIN             15   /* one column */\n+\n+#define explicitly_enable_column(c) \\\n+\t(((c) & COL_PARSEOPT) && column_active(c))\n+\n+struct column_options {\n+\tint width;\n+\tint padding;\n+\tconst char *indent;\n+\tconst char *nl;\n+};\n+\n+struct option;\n+extern int parseopt_column_callback(const struct option *, const char *, int);\n+extern int git_column_config(const char *var, const char *value,\n+\t\t\t     const char *command, unsigned int *colopts);\n+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);\n+static inline int column_active(unsigned int colopts)\n+{\n+\treturn (colopts & COL_ENABLE_MASK) == COL_ENABLED;\n+}\n+\n+extern void print_columns(const struct string_list *list, unsigned int colopts,\n+\t\t\t  const struct column_options *opts);\n+\n+#endif\ndiff --git a/command-list.txt b/command-list.txt\nindex a36ee9b..fe06f15 100644\n--- a/command-list.txt\n+++ b/command-list.txt\n@@ -20,6 +20,7 @@ git-cherry-pick                         mainporcelain\n git-citool                              mainporcelain\n git-clean                               mainporcelain\n git-clone                               mainporcelain common\n+git-column                              purehelpers\n git-commit                              mainporcelain common\n git-commit-tree                         plumbingmanipulators\n git-config                              ancillarymanipulators\ndiff --git a/git.c b/git.c\nindex 3805616..ee727cb 100644\n--- a/git.c\n+++ b/git.c\n@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"clone\", cmd_clone },\n+\t\t{ \"column\", cmd_column, RUN_SETUP_GENTLY },\n \t\t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n \t\t{ \"config\", cmd_config, RUN_SETUP_GENTLY },\ndiff --git a/parse-options.h b/parse-options.h\nindex 2e811dc..56fcafd 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);\n \t  PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }\n #define OPT__COLOR(var, h) \\\n \tOPT_COLOR_FLAG(0, \"color\", (var), (h))\n+#define OPT_COLUMN(s, l, v, h) \\\n+\t{ OPTION_CALLBACK, (s), (l), (v), \"style\", (h), PARSE_OPT_OPTARG, parseopt_column_callback }\n \n #endif\ndiff --git a/t/t9002-column.sh b/t/t9002-column.sh\nnew file mode 100755\nindex 0000000..a7f3cd9\n--- /dev/null\n+++ b/t/t9002-column.sh\n@@ -0,0 +1,45 @@\n+#!/bin/sh\n+\n+test_description='git column'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\tcat >lista <<\\EOF\n+one\n+two\n+three\n+four\n+five\n+six\n+seven\n+eight\n+nine\n+ten\n+eleven\n+EOF\n+'\n+\n+test_expect_success 'never' '\n+\tgit column --indent=Z --mode=never <lista >actual &&\n+\ttest_cmp lista actual\n+'\n+\n+test_expect_success 'always' '\n+\tcat >expected <<\\EOF &&\n+Zone\n+Ztwo\n+Zthree\n+Zfour\n+Zfive\n+Zsix\n+Zseven\n+Zeight\n+Znine\n+Zten\n+Zeleven\n+EOF\n+\tgit column --indent=Z --mode=plain <lista >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.7.8.36.g69ee2\n"},{"id":"189798","messageId":"xmqqfwbxzlpk.fsf@junio.mtv.corp.google.com","threadId":"30231","inReplyTo":"1334983472-3172-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] Add column layout skeleton and git-column","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-04-21T05:40:39Z","receivedAt":"2012-04-21T05:40:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n>  2012/4/21 Junio C Hamano <gitster@pobox.com>:\n>  > Am I looking at the correct version?  Somehow I thought you eliminated\n>  > this confusingly named macro in the previous round.  Two steps forward,\n>  > one step backward???\n>\n>  Aah.. the beautiful result of having the same series (but probably\n>  different versions) across several machines.. This version should be\n>  the right one with fixes.\n\nYeah, this looks more like it.\n\nWill replace 1/9 with this and requeue.\n"}]}