From 7e29b8254f08af820b2f0c3770836638ffe517ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 21 Apr 2012 11:44:32 +0700 Subject: [PATCH 01/10] Add column layout skeleton and git-column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A column option string consists of many token separated by either a space or a comma. A token belongs to one of three groups: - enabling: always, never and auto - layout mode: currently plain (which does not layout at all) - other future tuning flags git-column can be used to pipe output to from a command that wants column layout, but not to mess with its own output code. Simpler output code can be changed to use column layout code directly. Thanks-to: Ramsay Jones Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- .gitignore | 1 + Documentation/config.txt | 18 ++++ Documentation/git-column.txt | 53 +++++++++++ Makefile | 3 + builtin.h | 1 + builtin/column.c | 59 ++++++++++++ column.c | 169 +++++++++++++++++++++++++++++++++++ column.h | 38 ++++++++ command-list.txt | 1 + git.c | 1 + parse-options.h | 2 + t/t9002-column.sh | 45 ++++++++++ 12 files changed, 391 insertions(+) create mode 100644 Documentation/git-column.txt create mode 100644 builtin/column.c create mode 100644 column.c create mode 100644 column.h create mode 100755 t/t9002-column.sh diff --git a/.gitignore b/.gitignore index 87fcc5f6ff..25402643f3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ /git-cherry-pick /git-clean /git-clone +/git-column /git-commit /git-commit-tree /git-config diff --git a/Documentation/config.txt b/Documentation/config.txt index e55dae1806..9aabef124c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -836,6 +836,24 @@ color.ui:: `never` if you prefer git commands not to use color unless enabled explicitly with some other configuration or the `--color` option. +column.ui:: + Specify whether supported commands should output in columns. + This variable consists of a list of tokens separated by spaces + or commas: ++ +-- +`always`;; + always show in columns +`never`;; + never show in columns +`auto`;; + show in columns if the output is to the terminal +`plain`;; + show in one column +-- ++ + This option defaults to 'never'. + commit.status:: A boolean to enable/disable inclusion of status information in the commit message template when using an editor to prepare the commit diff --git a/Documentation/git-column.txt b/Documentation/git-column.txt new file mode 100644 index 0000000000..9be16eea0e --- /dev/null +++ b/Documentation/git-column.txt @@ -0,0 +1,53 @@ +git-column(1) +============= + +NAME +---- +git-column - Display data in columns + +SYNOPSIS +-------- +[verse] +'git column' [--command=] [--[raw-]mode=] [--width=] + [--indent=] [--nl=] [--pading=] + +DESCRIPTION +----------- +This command formats its input into multiple columns. + +OPTIONS +------- +--command=:: + Look up layout mode using configuration variable column. and + column.ui. + +--mode=:: + Specify layout mode. See configuration variable column.ui for option + syntax. + +--raw-mode=:: + Same as --mode but take mode encoded as a number. This is mainly used + by other commands that have already parsed layout mode. + +--width=:: + Specify the terminal width. By default 'git column' will detect the + terminal width, or fall back to 80 if it is unable to do so. + +--indent=:: + String to be printed at the beginning of each line. + +--nl=:: + String to be printed at the end of each line, + including newline character. + +--padding=:: + The number of spaces between columns. One space by default. + + +Author +------ +Written by Nguyen Thai Ngoc Duy + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Makefile b/Makefile index 1fb1705317..b579b6b7de 100644 --- a/Makefile +++ b/Makefile @@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o LIB_OBJS += bundle.o LIB_OBJS += cache-tree.o LIB_OBJS += color.o +LIB_OBJS += column.o LIB_OBJS += combine-diff.o LIB_OBJS += commit.o LIB_OBJS += compat/obstack.o @@ -774,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o BUILTIN_OBJS += builtin/checkout.o BUILTIN_OBJS += builtin/clean.o BUILTIN_OBJS += builtin/clone.o +BUILTIN_OBJS += builtin/column.o BUILTIN_OBJS += builtin/commit-tree.o BUILTIN_OBJS += builtin/commit.o BUILTIN_OBJS += builtin/config.o @@ -2166,6 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h connect.o transport.o url.o http-backend.o: url.h +column.o help.o pager.o: column.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h diff --git a/builtin.h b/builtin.h index 857b9c8aa8..338f540e39 100644 --- a/builtin.h +++ b/builtin.h @@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); extern int cmd_clone(int argc, const char **argv, const char *prefix); extern int cmd_clean(int argc, const char **argv, const char *prefix); +extern int cmd_column(int argc, const char **argv, const char *prefix); extern int cmd_commit(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); extern int cmd_config(int argc, const char **argv, const char *prefix); diff --git a/builtin/column.c b/builtin/column.c new file mode 100644 index 0000000000..5ea798a7ca --- /dev/null +++ b/builtin/column.c @@ -0,0 +1,59 @@ +#include "builtin.h" +#include "cache.h" +#include "strbuf.h" +#include "parse-options.h" +#include "string-list.h" +#include "column.h" + +static const char * const builtin_column_usage[] = { + "git column [options]", + NULL +}; +static unsigned int colopts; + +static int column_config(const char *var, const char *value, void *cb) +{ + return git_column_config(var, value, cb, &colopts); +} + +int cmd_column(int argc, const char **argv, const char *prefix) +{ + struct string_list list = STRING_LIST_INIT_DUP; + struct strbuf sb = STRBUF_INIT; + struct column_options copts; + const char *command = NULL, *real_command = NULL; + struct option options[] = { + OPT_STRING(0, "command", &real_command, "name", "lookup config vars"), + OPT_COLUMN(0, "mode", &colopts, "layout to use"), + OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"), + OPT_INTEGER(0, "width", &copts.width, "Maximum width"), + OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"), + OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"), + OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"), + OPT_END() + }; + + /* This one is special and must be the first one */ + if (argc > 1 && !prefixcmp(argv[1], "--command=")) { + command = argv[1] + 10; + git_config(column_config, (void *)command); + } else + git_config(column_config, NULL); + + memset(&copts, 0, sizeof(copts)); + copts.width = term_columns(); + copts.padding = 1; + argc = parse_options(argc, argv, "", options, builtin_column_usage, 0); + if (argc) + usage_with_options(builtin_column_usage, options); + if (real_command || command) { + if (!real_command || !command || strcmp(real_command, command)) + die(_("--command must be the first argument")); + } + finalize_colopts(&colopts, -1); + while (!strbuf_getline(&sb, stdin, '\n')) + string_list_append(&list, sb.buf); + + print_columns(&list, colopts, &copts); + return 0; +} diff --git a/column.c b/column.c new file mode 100644 index 0000000000..3349f5855b --- /dev/null +++ b/column.c @@ -0,0 +1,169 @@ +#include "cache.h" +#include "column.h" +#include "string-list.h" +#include "parse-options.h" + +/* Display without layout when not enabled */ +static void display_plain(const struct string_list *list, + const char *indent, const char *nl) +{ + int i; + + for (i = 0; i < list->nr; i++) + printf("%s%s%s", indent, list->items[i].string, nl); +} + +void print_columns(const struct string_list *list, unsigned int colopts, + const struct column_options *opts) +{ + struct column_options nopts; + + if (!list->nr) + return; + assert((colopts & COL_ENABLE_MASK) != COL_AUTO); + + memset(&nopts, 0, sizeof(nopts)); + nopts.indent = opts && opts->indent ? opts->indent : ""; + nopts.nl = opts && opts->nl ? opts->nl : "\n"; + nopts.padding = opts ? opts->padding : 1; + nopts.width = opts && opts->width ? opts->width : term_columns() - 1; + if (!column_active(colopts)) { + display_plain(list, "", "\n"); + return; + } + switch (COL_LAYOUT(colopts)) { + case COL_PLAIN: + display_plain(list, nopts.indent, nopts.nl); + break; + default: + die("BUG: invalid layout mode %d", COL_LAYOUT(colopts)); + } +} + +int finalize_colopts(unsigned int *colopts, int stdout_is_tty) +{ + if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) { + if (stdout_is_tty < 0) + stdout_is_tty = isatty(1); + *colopts &= ~COL_ENABLE_MASK; + if (stdout_is_tty) + *colopts |= COL_ENABLED; + } + return 0; +} + +struct colopt { + const char *name; + unsigned int value; + unsigned int mask; +}; + +#define LAYOUT_SET 1 +#define ENABLE_SET 2 + +static int parse_option(const char *arg, int len, unsigned int *colopts, + int *group_set) +{ + struct colopt opts[] = { + { "always", COL_ENABLED, COL_ENABLE_MASK }, + { "never", COL_DISABLED, COL_ENABLE_MASK }, + { "auto", COL_AUTO, COL_ENABLE_MASK }, + { "plain", COL_PLAIN, COL_LAYOUT_MASK }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(opts); i++) { + int arg_len = len, name_len; + const char *arg_str = arg; + + name_len = strlen(opts[i].name); + if (arg_len != name_len || + strncmp(arg_str, opts[i].name, name_len)) + continue; + + switch (opts[i].mask) { + case COL_ENABLE_MASK: + *group_set |= ENABLE_SET; + break; + case COL_LAYOUT_MASK: + *group_set |= LAYOUT_SET; + break; + } + + if (opts[i].mask) + *colopts = (*colopts & ~opts[i].mask) | opts[i].value; + return 0; + } + + return error("unsupported option '%s'", arg); +} + +static int parse_config(unsigned int *colopts, const char *value) +{ + const char *sep = " ,"; + int group_set = 0; + + while (*value) { + int len = strcspn(value, sep); + if (len) { + if (parse_option(value, len, colopts, &group_set)) + return -1; + + value += len; + } + value += strspn(value, sep); + } + /* + * Setting layout implies "always" if neither always, never + * nor auto is specified. + * + * Current value in COL_ENABLE_MASK is disregarded. This means if + * you set column.ui = auto and pass --column=row, then "auto" + * will become "always". + */ + if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET)) + *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED; + return 0; +} + +static int column_config(const char *var, const char *value, + const char *key, unsigned int *colopts) +{ + if (!value) + return config_error_nonbool(var); + if (parse_config(colopts, value)) + return error("invalid column.%s mode %s", key, value); + return 0; +} + +int git_column_config(const char *var, const char *value, + const char *command, unsigned int *colopts) +{ + const char *it = skip_prefix(var, "column."); + if (!it) + return 0; + + if (!strcmp(it, "ui")) + return column_config(var, value, "ui", colopts); + + if (command && !strcmp(it, command)) + return column_config(var, value, it, colopts); + + return 0; +} + +int parseopt_column_callback(const struct option *opt, + const char *arg, int unset) +{ + unsigned int *colopts = opt->value; + *colopts |= COL_PARSEOPT; + *colopts &= ~COL_ENABLE_MASK; + if (unset) /* --no-column == never */ + return 0; + /* --column == always unless "arg" states otherwise */ + *colopts |= COL_ENABLED; + if (arg) + return parse_config(colopts, arg); + + return 0; +} diff --git a/column.h b/column.h new file mode 100644 index 0000000000..b8719b3976 --- /dev/null +++ b/column.h @@ -0,0 +1,38 @@ +#ifndef COLUMN_H +#define COLUMN_H + +#define COL_LAYOUT_MASK 0x000F +#define COL_ENABLE_MASK 0x0030 /* always, never or auto */ +#define COL_PARSEOPT 0x0040 /* --column is given from cmdline */ + +#define COL_DISABLED 0x0000 /* must be zero */ +#define COL_ENABLED 0x0010 +#define COL_AUTO 0x0020 + +#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK) +#define COL_PLAIN 15 /* one column */ + +#define explicitly_enable_column(c) \ + (((c) & COL_PARSEOPT) && column_active(c)) + +struct column_options { + int width; + int padding; + const char *indent; + const char *nl; +}; + +struct option; +extern int parseopt_column_callback(const struct option *, const char *, int); +extern int git_column_config(const char *var, const char *value, + const char *command, unsigned int *colopts); +extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty); +static inline int column_active(unsigned int colopts) +{ + return (colopts & COL_ENABLE_MASK) == COL_ENABLED; +} + +extern void print_columns(const struct string_list *list, unsigned int colopts, + const struct column_options *opts); + +#endif diff --git a/command-list.txt b/command-list.txt index a36ee9b015..fe06f158fe 100644 --- a/command-list.txt +++ b/command-list.txt @@ -20,6 +20,7 @@ git-cherry-pick mainporcelain git-citool mainporcelain git-clean mainporcelain git-clone mainporcelain common +git-column purehelpers git-commit mainporcelain common git-commit-tree plumbingmanipulators git-config ancillarymanipulators diff --git a/git.c b/git.c index 3805616630..ee727cbe1a 100644 --- a/git.c +++ b/git.c @@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv) { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, { "clone", cmd_clone }, + { "column", cmd_column, RUN_SETUP_GENTLY }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "config", cmd_config, RUN_SETUP_GENTLY }, diff --git a/parse-options.h b/parse-options.h index 2e811dc7da..56fcafda5a 100644 --- a/parse-options.h +++ b/parse-options.h @@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int); PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } #define OPT__COLOR(var, h) \ OPT_COLOR_FLAG(0, "color", (var), (h)) +#define OPT_COLUMN(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback } #endif diff --git a/t/t9002-column.sh b/t/t9002-column.sh new file mode 100755 index 0000000000..a7f3cd9285 --- /dev/null +++ b/t/t9002-column.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='git column' +. ./test-lib.sh + +test_expect_success 'setup' ' + cat >lista <<\EOF +one +two +three +four +five +six +seven +eight +nine +ten +eleven +EOF +' + +test_expect_success 'never' ' + git column --indent=Z --mode=never actual && + test_cmp lista actual +' + +test_expect_success 'always' ' + cat >expected <<\EOF && +Zone +Ztwo +Zthree +Zfour +Zfive +Zsix +Zseven +Zeight +Znine +Zten +Zeleven +EOF + git column --indent=Z --mode=plain actual && + test_cmp expected actual +' + +test_done From 88e8f908f2b0c56f9ccf8134d8ff9f689af9cc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:34 +0700 Subject: [PATCH 02/10] Stop starting pager recursively MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-column can be used as a pager for other git commands, something like this: GIT_PAGER="git -p column --mode='dense color'" git -p branch The problem with this is that "git -p column" also has $GIT_PAGER set so the pager runs itself again as another pager. The end result is an infinite loop of forking. Other git commands have the same problem if being abused this way. Check if $GIT_PAGER is already set and stop launching another pager. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- pager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pager.c b/pager.c index 05584dead6..4dcb08d967 100644 --- a/pager.c +++ b/pager.c @@ -73,7 +73,7 @@ void setup_pager(void) { const char *pager = git_pager(isatty(1)); - if (!pager) + if (!pager || pager_in_use()) return; /* From 077539d734cdc4b0a3d2ea87fc487fa5c21d0311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:35 +0700 Subject: [PATCH 03/10] column: add columnar layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit COL_COLUMN and COL_ROW fill column by column (or row by row respectively), given the terminal width and how many space between columns. All cells have equal width. Strings are supposed to be in UTF-8. Valid ANSI escape strings are OK. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++ column.c | 114 +++++++++++++++++++++++++++++++++++++++ column.h | 2 + t/t9002-column.sh | 86 +++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index 9aabef124c..ab6ae3da72 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -848,6 +848,10 @@ column.ui:: never show in columns `auto`;; show in columns if the output is to the terminal +`column`;; + fill columns before rows (default) +`row`;; + fill rows before columns `plain`;; show in one column -- diff --git a/column.c b/column.c index 3349f5855b..05fa3e361c 100644 --- a/column.c +++ b/column.c @@ -2,6 +2,59 @@ #include "column.h" #include "string-list.h" #include "parse-options.h" +#include "utf8.h" + +#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \ + (x) * (d)->rows + (y) : \ + (y) * (d)->cols + (x)) + +struct column_data { + const struct string_list *list; + unsigned int colopts; + struct column_options opts; + + int rows, cols; + int *len; /* cell length */ +}; + +/* return length of 's' in letters, ANSI escapes stripped */ +static int item_length(unsigned int colopts, const char *s) +{ + int len, i = 0; + struct strbuf str = STRBUF_INIT; + + strbuf_addstr(&str, s); + while ((s = strstr(str.buf + i, "\033[")) != NULL) { + int len = strspn(s + 2, "0123456789;"); + i = s - str.buf; + strbuf_remove(&str, i, len + 3); /* \033[ */ + } + len = utf8_strwidth(str.buf); + strbuf_release(&str); + return len; +} + +/* + * Calculate cell width, rows and cols for a table of equal cells, given + * table width and how many spaces between cells. + */ +static void layout(struct column_data *data, int *width) +{ + int i; + + *width = 0; + for (i = 0; i < data->list->nr; i++) + if (*width < data->len[i]) + *width = data->len[i]; + + *width += data->opts.padding; + + data->cols = (data->opts.width - strlen(data->opts.indent)) / *width; + if (data->cols == 0) + data->cols = 1; + + data->rows = DIV_ROUND_UP(data->list->nr, data->cols); +} /* Display without layout when not enabled */ static void display_plain(const struct string_list *list, @@ -13,6 +66,61 @@ static void display_plain(const struct string_list *list, printf("%s%s%s", indent, list->items[i].string, nl); } +/* Print a cell to stdout with all necessary leading/traling space */ +static int display_cell(struct column_data *data, int initial_width, + const char *empty_cell, int x, int y) +{ + int i, len, newline; + + i = XY2LINEAR(data, x, y); + if (i >= data->list->nr) + return -1; + len = data->len[i]; + if (COL_LAYOUT(data->colopts) == COL_COLUMN) + newline = i + data->rows >= data->list->nr; + else + newline = x == data->cols - 1 || i == data->list->nr - 1; + + printf("%s%s%s", + x == 0 ? data->opts.indent : "", + data->list->items[i].string, + newline ? data->opts.nl : empty_cell + len); + return 0; +} + +/* Display COL_COLUMN or COL_ROW */ +static void display_table(const struct string_list *list, + unsigned int colopts, + const struct column_options *opts) +{ + struct column_data data; + int x, y, i, initial_width; + char *empty_cell; + + memset(&data, 0, sizeof(data)); + data.list = list; + data.colopts = colopts; + data.opts = *opts; + + data.len = xmalloc(sizeof(*data.len) * list->nr); + for (i = 0; i < list->nr; i++) + data.len[i] = item_length(colopts, list->items[i].string); + + layout(&data, &initial_width); + + empty_cell = xmalloc(initial_width + 1); + memset(empty_cell, ' ', initial_width); + empty_cell[initial_width] = '\0'; + for (y = 0; y < data.rows; y++) { + for (x = 0; x < data.cols; x++) + if (display_cell(&data, initial_width, empty_cell, x, y)) + break; + } + + free(data.len); + free(empty_cell); +} + void print_columns(const struct string_list *list, unsigned int colopts, const struct column_options *opts) { @@ -35,6 +143,10 @@ void print_columns(const struct string_list *list, unsigned int colopts, case COL_PLAIN: display_plain(list, nopts.indent, nopts.nl); break; + case COL_ROW: + case COL_COLUMN: + display_table(list, colopts, &nopts); + break; default: die("BUG: invalid layout mode %d", COL_LAYOUT(colopts)); } @@ -69,6 +181,8 @@ static int parse_option(const char *arg, int len, unsigned int *colopts, { "never", COL_DISABLED, COL_ENABLE_MASK }, { "auto", COL_AUTO, COL_ENABLE_MASK }, { "plain", COL_PLAIN, COL_LAYOUT_MASK }, + { "column", COL_COLUMN, COL_LAYOUT_MASK }, + { "row", COL_ROW, COL_LAYOUT_MASK }, }; int i; diff --git a/column.h b/column.h index b8719b3976..ec7e1d26e5 100644 --- a/column.h +++ b/column.h @@ -10,6 +10,8 @@ #define COL_AUTO 0x0020 #define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK) +#define COL_COLUMN 0 /* Fill columns before rows */ +#define COL_ROW 1 /* Fill rows before columns */ #define COL_PLAIN 15 /* one column */ #define explicitly_enable_column(c) \ diff --git a/t/t9002-column.sh b/t/t9002-column.sh index a7f3cd9285..ec288aeb95 100755 --- a/t/t9002-column.sh +++ b/t/t9002-column.sh @@ -42,4 +42,90 @@ EOF test_cmp expected actual ' +test_expect_success '80 columns' ' + cat >expected <<\EOF && +one two three four five six seven eight nine ten eleven +EOF + COLUMNS=80 git column --mode=column actual && + test_cmp expected actual +' + +test_expect_success 'COLUMNS = 1' ' + cat >expected <<\EOF && +one +two +three +four +five +six +seven +eight +nine +ten +eleven +EOF + COLUMNS=1 git column --mode=column actual && + test_cmp expected actual +' + +test_expect_success 'width = 1' ' + git column --mode=column --width=1 actual && + test_cmp expected actual +' + +COLUMNS=20 +export COLUMNS + +test_expect_success '20 columns' ' + cat >expected <<\EOF && +one seven +two eight +three nine +four ten +five eleven +six +EOF + git column --mode=column actual && + test_cmp expected actual +' + +test_expect_success '20 columns, padding 2' ' + cat >expected <<\EOF && +one seven +two eight +three nine +four ten +five eleven +six +EOF + git column --mode=column --padding 2 actual && + test_cmp expected actual +' + +test_expect_success '20 columns, indented' ' + cat >expected <<\EOF && + one seven + two eight + three nine + four ten + five eleven + six +EOF + git column --mode=column --indent=" " actual && + test_cmp expected actual +' + +test_expect_success '20 columns, row first' ' + cat >expected <<\EOF && +one two +three four +five six +seven eight +nine ten +eleven +EOF + git column --mode=row actual && + test_cmp expected actual +' + test_done From f78b1c5f8237d68fffe7c04d22bf93f2ad0bc730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 27 Apr 2012 11:25:25 +0200 Subject: [PATCH 04/10] t9002: work around shells that are unable to set COLUMNS to 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In t9002-column.sh, file with expected output was shared between two test cases, but set in the first one. Since the first test case can now be skipped, setting up the expected output is moved outside of the test case. Signed-off-by: Zbigniew Jędrzejewski-Szmek Signed-off-by: Junio C Hamano --- t/t9002-column.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/t/t9002-column.sh b/t/t9002-column.sh index ec288aeb95..fb71949ebd 100755 --- a/t/t9002-column.sh +++ b/t/t9002-column.sh @@ -50,8 +50,7 @@ EOF test_cmp expected actual ' -test_expect_success 'COLUMNS = 1' ' - cat >expected <<\EOF && +cat >expected <<\EOF one two three @@ -64,6 +63,8 @@ nine ten eleven EOF + +test_expect_success COLUMNS_CAN_BE_1 'COLUMNS = 1' ' COLUMNS=1 git column --mode=column actual && test_cmp expected actual ' From 3f8eccbe166ecff79a986b7ce87df4963cc873b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:36 +0700 Subject: [PATCH 05/10] column: add dense layout support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Normally all cells (and in turn columns) share the same width. This layout mode can waste space because one long item can stretch our all columns. With COL_DENSE enabled, column width is calculated indepdendently. All columns are shrunk to minimum, then it attempts to push cells of the last row over to the next column with hope that everything still fits even there's one row less. The process is repeated until the new layout cannot fit in given width any more, or there's only one row left (perfect!). Apparently, this mode consumes more cpu than the old one, but it makes better use of terminal space. For layouting one or two screens, cpu usage should not be detectable. This patch introduces option handling code besides layout modes and enable/disable to expose this feature as "dense". The feature can be turned off by specifying "nodense". Thanks-to: Ramsay Jones Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++ column.c | 84 +++++++++++++++++++++++++++++++++++++++- column.h | 2 + t/t9002-column.sh | 48 +++++++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index ab6ae3da72..01905a74ce 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -854,6 +854,10 @@ column.ui:: fill rows before columns `plain`;; show in one column +`dense`;; + make unequal size columns to utilize more space +`nodense`;; + make equal size columns -- + This option defaults to 'never'. diff --git a/column.c b/column.c index 05fa3e361c..d6c115c1bf 100644 --- a/column.c +++ b/column.c @@ -15,6 +15,7 @@ struct column_data { int rows, cols; int *len; /* cell length */ + int *width; /* index to the longest row in column */ }; /* return length of 's' in letters, ANSI escapes stripped */ @@ -56,6 +57,57 @@ static void layout(struct column_data *data, int *width) data->rows = DIV_ROUND_UP(data->list->nr, data->cols); } +static void compute_column_width(struct column_data *data) +{ + int i, x, y; + for (x = 0; x < data->cols; x++) { + data->width[x] = XY2LINEAR(data, x, 0); + for (y = 0; y < data->rows; y++) { + i = XY2LINEAR(data, x, y); + if (i < data->list->nr && + data->len[data->width[x]] < data->len[i]) + data->width[x] = i; + } + } +} + +/* + * Shrink all columns by shortening them one row each time (and adding + * more columns along the way). Hopefully the longest cell will be + * moved to the next column, column is shrunk so we have more space + * for new columns. The process ends when the whole thing no longer + * fits in data->total_width. + */ +static void shrink_columns(struct column_data *data) +{ + data->width = xrealloc(data->width, + sizeof(*data->width) * data->cols); + while (data->rows > 1) { + int x, total_width, cols, rows; + rows = data->rows; + cols = data->cols; + + data->rows--; + data->cols = DIV_ROUND_UP(data->list->nr, data->rows); + if (data->cols != cols) + data->width = xrealloc(data->width, + sizeof(*data->width) * data->cols); + compute_column_width(data); + + total_width = strlen(data->opts.indent); + for (x = 0; x < data->cols; x++) { + total_width += data->len[data->width[x]]; + total_width += data->opts.padding; + } + if (total_width > data->opts.width) { + data->rows = rows; + data->cols = cols; + break; + } + } + compute_column_width(data); +} + /* Display without layout when not enabled */ static void display_plain(const struct string_list *list, const char *indent, const char *nl) @@ -75,7 +127,18 @@ static int display_cell(struct column_data *data, int initial_width, i = XY2LINEAR(data, x, y); if (i >= data->list->nr) return -1; + len = data->len[i]; + if (data->width && data->len[data->width[x]] < initial_width) { + /* + * empty_cell has initial_width chars, if real column + * is narrower, increase len a bit so we fill less + * space. + */ + len += initial_width - data->len[data->width[x]]; + len -= data->opts.padding; + } + if (COL_LAYOUT(data->colopts) == COL_COLUMN) newline = i + data->rows >= data->list->nr; else @@ -108,6 +171,9 @@ static void display_table(const struct string_list *list, layout(&data, &initial_width); + if (colopts & COL_DENSE) + shrink_columns(&data); + empty_cell = xmalloc(initial_width + 1); memset(empty_cell, ' ', initial_width); empty_cell[initial_width] = '\0'; @@ -118,6 +184,7 @@ static void display_table(const struct string_list *list, } free(data.len); + free(data.width); free(empty_cell); } @@ -183,13 +250,22 @@ static int parse_option(const char *arg, int len, unsigned int *colopts, { "plain", COL_PLAIN, COL_LAYOUT_MASK }, { "column", COL_COLUMN, COL_LAYOUT_MASK }, { "row", COL_ROW, COL_LAYOUT_MASK }, + { "dense", COL_DENSE, 0 }, }; int i; for (i = 0; i < ARRAY_SIZE(opts); i++) { - int arg_len = len, name_len; + int set = 1, arg_len = len, name_len; const char *arg_str = arg; + if (!opts[i].mask) { + if (arg_len > 2 && !strncmp(arg_str, "no", 2)) { + arg_str += 2; + arg_len -= 2; + set = 0; + } + } + name_len = strlen(opts[i].name); if (arg_len != name_len || strncmp(arg_str, opts[i].name, name_len)) @@ -206,6 +282,12 @@ static int parse_option(const char *arg, int len, unsigned int *colopts, if (opts[i].mask) *colopts = (*colopts & ~opts[i].mask) | opts[i].value; + else { + if (set) + *colopts |= opts[i].value; + else + *colopts &= ~opts[i].value; + } return 0; } diff --git a/column.h b/column.h index ec7e1d26e5..4f178d84a8 100644 --- a/column.h +++ b/column.h @@ -4,6 +4,8 @@ #define COL_LAYOUT_MASK 0x000F #define COL_ENABLE_MASK 0x0030 /* always, never or auto */ #define COL_PARSEOPT 0x0040 /* --column is given from cmdline */ +#define COL_DENSE 0x0080 /* Shrink columns when possible, + making space for more columns */ #define COL_DISABLED 0x0000 /* must be zero */ #define COL_ENABLED 0x0010 diff --git a/t/t9002-column.sh b/t/t9002-column.sh index fb71949ebd..89983527b6 100755 --- a/t/t9002-column.sh +++ b/t/t9002-column.sh @@ -90,6 +90,30 @@ EOF test_cmp expected actual ' +test_expect_success '20 columns, nodense' ' + cat >expected <<\EOF && +one seven +two eight +three nine +four ten +five eleven +six +EOF + git column --mode=column,nodense < lista > actual && + test_cmp expected actual +' + +test_expect_success '20 columns, dense' ' + cat >expected <<\EOF && +one five nine +two six ten +three seven eleven +four eight +EOF + git column --mode=column,dense < lista > actual && + test_cmp expected actual +' + test_expect_success '20 columns, padding 2' ' cat >expected <<\EOF && one seven @@ -129,4 +153,28 @@ EOF test_cmp expected actual ' +test_expect_success '20 columns, row first, nodense' ' + cat >expected <<\EOF && +one two +three four +five six +seven eight +nine ten +eleven +EOF + git column --mode=row,nodense actual && + test_cmp expected actual +' + +test_expect_success '20 columns, row first, dense' ' + cat >expected <<\EOF && +one two three +four five six +seven eight nine +ten eleven +EOF + git column --mode=row,dense actual && + test_cmp expected actual +' + test_done From dbfae689690bead0e49130b149c869834795cf81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:37 +0700 Subject: [PATCH 06/10] help: reuse print_columns() for help -a MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "help -a" also respects column.ui (and column.help if presents) Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/help.c | 7 +++++- help.c | 59 ++++++++++++++++++++------------------------------ help.h | 2 +- 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/builtin/help.c b/builtin/help.c index 61ff79839b..c64f1522d8 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -9,6 +9,7 @@ #include "common-cmds.h" #include "parse-options.h" #include "run-command.h" +#include "column.h" #include "help.h" static struct man_viewer_list { @@ -30,6 +31,7 @@ enum help_format { }; static int show_all = 0; +static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; static struct option builtin_help_options[] = { OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), @@ -251,6 +253,8 @@ static int add_man_viewer_info(const char *var, const char *value) static int git_help_config(const char *var, const char *value, void *cb) { + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "help", &colopts); if (!strcmp(var, "help.format")) { if (!value) return config_error_nonbool(var); @@ -424,8 +428,9 @@ int cmd_help(int argc, const char **argv, const char *prefix) parsed_help_format = help_format; if (show_all) { + git_config(git_help_config, NULL); printf("usage: %s\n\n", git_usage_string); - list_commands("git commands", &main_cmds, &other_cmds); + list_commands("git commands", colopts, &main_cmds, &other_cmds); printf("%s\n", git_more_info_string); return 0; } diff --git a/help.c b/help.c index 14eefc91ce..a815ae62a6 100644 --- a/help.c +++ b/help.c @@ -4,6 +4,8 @@ #include "levenshtein.h" #include "help.h" #include "common-cmds.h" +#include "string-list.h" +#include "column.h" void add_cmdname(struct cmdnames *cmds, const char *name, int len) { @@ -70,31 +72,25 @@ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) cmds->cnt = cj; } -static void pretty_print_string_list(struct cmdnames *cmds, int longest) +static void pretty_print_string_list(struct cmdnames *cmds, + unsigned int colopts) { - int cols = 1, rows; - int space = longest + 1; /* min 1 SP between words */ - int max_cols = term_columns() - 1; /* don't print *on* the edge */ - int i, j; + struct string_list list = STRING_LIST_INIT_NODUP; + struct column_options copts; + int i; - if (space < max_cols) - cols = max_cols / space; - rows = DIV_ROUND_UP(cmds->cnt, cols); - - for (i = 0; i < rows; i++) { - printf(" "); - - for (j = 0; j < cols; j++) { - int n = j * rows + i; - int size = space; - if (n >= cmds->cnt) - break; - if (j == cols-1 || n + rows >= cmds->cnt) - size = 1; - printf("%-*s", size, cmds->names[n]->name); - } - putchar('\n'); - } + for (i = 0; i < cmds->cnt; i++) + string_list_append(&list, cmds->names[i]->name); + /* + * always enable column display, we only consult column.* + * about layout strategy and stuff + */ + colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED; + memset(&copts, 0, sizeof(copts)); + copts.indent = " "; + copts.padding = 2; + print_columns(&list, colopts, &copts); + string_list_clear(&list, 0); } static int is_executable(const char *name) @@ -203,25 +199,16 @@ void load_command_list(const char *prefix, exclude_cmds(other_cmds, main_cmds); } -void list_commands(const char *title, struct cmdnames *main_cmds, - struct cmdnames *other_cmds) +void list_commands(const char *title, unsigned int colopts, + struct cmdnames *main_cmds, struct cmdnames *other_cmds) { - int i, longest = 0; - - for (i = 0; i < main_cmds->cnt; i++) - if (longest < main_cmds->names[i]->len) - longest = main_cmds->names[i]->len; - for (i = 0; i < other_cmds->cnt; i++) - if (longest < other_cmds->names[i]->len) - longest = other_cmds->names[i]->len; - if (main_cmds->cnt) { const char *exec_path = git_exec_path(); printf("available %s in '%s'\n", title, exec_path); printf("----------------"); mput_char('-', strlen(title) + strlen(exec_path)); putchar('\n'); - pretty_print_string_list(main_cmds, longest); + pretty_print_string_list(main_cmds, colopts); putchar('\n'); } @@ -230,7 +217,7 @@ void list_commands(const char *title, struct cmdnames *main_cmds, printf("---------------------------------------"); mput_char('-', strlen(title)); putchar('\n'); - pretty_print_string_list(other_cmds, longest); + pretty_print_string_list(other_cmds, colopts); putchar('\n'); } } diff --git a/help.h b/help.h index b6b12d5754..854d2d43d7 100644 --- a/help.h +++ b/help.h @@ -25,7 +25,7 @@ extern void add_cmdname(struct cmdnames *cmds, const char *name, int len); /* Here we require that excludes is a sorted list. */ extern void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes); extern int is_in_cmdlist(struct cmdnames *cmds, const char *name); -extern void list_commands(const char *title, +extern void list_commands(const char *title, unsigned int colopts, struct cmdnames *main_cmds, struct cmdnames *other_cmds); From ebe31ef2ed539ec93f87c6705852f29347410b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:38 +0700 Subject: [PATCH 07/10] branch: add --column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++ Documentation/git-branch.txt | 9 +++++ Makefile | 2 +- builtin/branch.c | 32 +++++++++++++-- t/t3200-branch.sh | 77 ++++++++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 5 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 01905a74ce..c7e97a5644 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -862,6 +862,10 @@ column.ui:: + This option defaults to 'never'. +column.branch:: + Specify whether to output branch listing in `git branch` in columns. + See `column.ui` for details. + commit.status:: A boolean to enable/disable inclusion of status information in the commit message template when using an editor to prepare the commit diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 0427e80a35..ba5cccb519 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git branch' [--color[=] | --no-color] [-r | -a] [--list] [-v [--abbrev= | --no-abbrev]] + [--column[=] | --no-column] [(--merged | --no-merged | --contains) []] [...] 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] [] 'git branch' (-m | -M) [] @@ -107,6 +108,14 @@ OPTIONS default to color output. Same as `--color=never`. +--column[=]:: +--no-column:: + Display branch listing in columns. See configuration variable + column.branch for option syntax.`--column` and `--no-column` + without options are equivalent to 'always' and 'never' respectively. ++ +This option is only applicable in non-verbose mode. + -r:: --remotes:: List or delete (if used with -d) the remote-tracking branches. diff --git a/Makefile b/Makefile index b579b6b7de..857f375521 100644 --- a/Makefile +++ b/Makefile @@ -2168,7 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h connect.o transport.o url.o http-backend.o: url.h -column.o help.o pager.o: column.h +builtin/branch.o column.o help.o pager.o: column.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h diff --git a/builtin/branch.c b/builtin/branch.c index cb17bc3675..9634c71ea2 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -15,6 +15,8 @@ #include "branch.h" #include "diff.h" #include "revision.h" +#include "string-list.h" +#include "column.h" static const char * const builtin_branch_usage[] = { "git branch [options] [-r | -a] [--merged | --no-merged]", @@ -53,6 +55,9 @@ static enum merge_filter { } merge_filter; static unsigned char merge_filter_ref[20]; +static struct string_list output = STRING_LIST_INIT_DUP; +static unsigned int colopts; + static int parse_branch_color_slot(const char *var, int ofs) { if (!strcasecmp(var+ofs, "plain")) @@ -70,6 +75,8 @@ static int parse_branch_color_slot(const char *var, int ofs) static int git_branch_config(const char *var, const char *value, void *cb) { + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "branch", &colopts); if (!strcmp(var, "color.branch")) { branch_use_color = git_config_colorbool(var, value); return 0; @@ -474,7 +481,12 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, else if (verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ add_verbose_info(&out, item, verbose, abbrev); - printf("%s\n", out.buf); + if (column_active(colopts)) { + assert(!verbose && "--column and --verbose are incompatible"); + string_list_append(&output, out.buf); + } else { + printf("%s\n", out.buf); + } strbuf_release(&name); strbuf_release(&out); } @@ -727,6 +739,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NONEG, opt_parse_merge_filter, (intptr_t) "HEAD", }, + OPT_COLUMN(0, "column", &colopts, "list branches in columns"), OPT_END(), }; @@ -749,6 +762,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) } hashcpy(merge_filter_ref, head_sha1); + argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); @@ -760,12 +774,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (abbrev == -1) abbrev = DEFAULT_ABBREV; + finalize_colopts(&colopts, -1); + if (verbose) { + if (explicitly_enable_column(colopts)) + die(_("--column and --verbose are incompatible")); + colopts = 0; + } if (delete) return delete_branches(argc, argv, delete > 1, kinds); - else if (list) - return print_ref_list(kinds, detached, verbose, abbrev, - with_commit, argv); + else if (list) { + int ret = print_ref_list(kinds, detached, verbose, abbrev, + with_commit, argv); + print_columns(&output, colopts, NULL); + string_list_clear(&output, 0); + return ret; + } else if (edit_description) { const char *branch_name; struct strbuf branch_ref = STRBUF_INIT; diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index dd1acebd88..9f82d5ed1a 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -160,6 +160,83 @@ test_expect_success 'git branch --list -d t should fail' ' test_path_is_missing .git/refs/heads/t ' +test_expect_success 'git branch --column' ' + COLUMNS=81 git branch --column=column >actual && + cat >expected <<\EOF && + a/b/c bam foo l * master n o/p r + abc bar j/k m/m master2 o/o q +EOF + test_cmp expected actual +' + +test_expect_success 'git branch --column with an extremely long branch name' ' + long=this/is/a/part/of/long/branch/name && + long=z$long/$long/$long/$long && + test_when_finished "git branch -d $long" && + git branch $long && + COLUMNS=80 git branch --column=column >actual && + cat >expected <actual && + git config --unset column.branch && + git config --unset column.ui && + cat >expected <<\EOF && + a/b/c bam foo l * master n o/p r + abc bar j/k m/m master2 o/o q +EOF + test_cmp expected actual +' + +test_expect_success 'git branch --column -v should fail' ' + test_must_fail git branch --column -v +' + +test_expect_success 'git branch -v with column.ui ignored' ' + git config column.ui column && + COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual && + git config --unset column.ui && + cat >expected <<\EOF && + a/b/c + abc + bam + bar + foo + j/k + l + m/m +* master + master2 + n + o/o + o/p + q + r +EOF + test_cmp expected actual +' + mv .git/config .git/config-saved test_expect_success 'git branch -m q q2 without config should succeed' ' From 323d0530918f015fa28572f4477723b607bce000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:39 +0700 Subject: [PATCH 08/10] status: add --column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ Documentation/git-status.txt | 7 +++++++ Makefile | 2 +- builtin/commit.c | 7 +++++++ t/t7508-status.sh | 24 ++++++++++++++++++++++++ wt-status.c | 28 ++++++++++++++++++++++++++-- wt-status.h | 1 + 7 files changed, 70 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index c7e97a5644..2158f0cbe8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -866,6 +866,10 @@ column.branch:: Specify whether to output branch listing in `git branch` in columns. See `column.ui` for details. +column.status:: + Specify whether to output untracked files in `git status` in columns. + See `column.ui` for details. + commit.status:: A boolean to enable/disable inclusion of status information in the commit message template when using an editor to prepare the commit diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 3d51717bbe..2f87207e6f 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -77,6 +77,13 @@ configuration variable documented in linkgit:git-config[1]. Terminate entries with NUL, instead of LF. This implies the `--porcelain` output format if no other format is given. +--column[=]:: +--no-column:: + Display untracked files in columns. See configuration variable + column.status for option syntax.`--column` and `--no-column` + without options are equivalent to 'always' and 'never' + respectively. + OUTPUT ------ diff --git a/Makefile b/Makefile index 857f375521..297e830ec3 100644 --- a/Makefile +++ b/Makefile @@ -2168,7 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h connect.o transport.o url.o http-backend.o: url.h -builtin/branch.o column.o help.o pager.o: column.h +builtin/branch.o builtin/commit.o column.o help.o pager.o: column.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h diff --git a/builtin/commit.c b/builtin/commit.c index 3714582e19..cc1a709029 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -27,6 +27,7 @@ #include "quote.h" #include "submodule.h" #include "gpg-interface.h" +#include "column.h" static const char * const builtin_commit_usage[] = { "git commit [options] [--] ...", @@ -88,6 +89,7 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; static char *sign_commit; +static unsigned int colopts; /* * The default commit message cleanup mode will remove the lines @@ -1145,6 +1147,8 @@ static int git_status_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; + if (!prefixcmp(k, "column.")) + return git_column_config(k, v, "status", &colopts); if (!strcmp(k, "status.submodulesummary")) { int is_bool; s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); @@ -1210,6 +1214,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when", "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_COLUMN(0, "column", &colopts, "list untracked files in columns"), OPT_END(), }; @@ -1223,6 +1228,8 @@ int cmd_status(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_status_options, builtin_status_usage, 0); + finalize_colopts(&colopts, -1); + s.colopts = colopts; if (null_termination && status_format == STATUS_FORMAT_LONG) status_format = STATUS_FORMAT_PORCELAIN; diff --git a/t/t7508-status.sh b/t/t7508-status.sh index fc57b135c5..8f5cfac331 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -59,6 +59,30 @@ test_expect_success 'status (1)' ' test_i18ngrep "use \"git rm --cached \.\.\.\" to unstage" output ' +test_expect_success 'status --column' ' + COLUMNS=50 git status --column="column dense" >output && + cat >expect <<\EOF && +# On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +# new file: dir2/added +# +# Changes not staged for commit: +# (use "git add ..." to update what will be committed) +# (use "git checkout -- ..." to discard changes in working directory) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add ..." to include in what will be committed) +# +# dir1/untracked dir2/untracked untracked +# dir2/modified output +EOF + test_cmp expect output +' + cat >expect <<\EOF # On branch master # Changes to be committed: diff --git a/wt-status.c b/wt-status.c index 9ffc535f1a..eeef17e7b6 100644 --- a/wt-status.c +++ b/wt-status.c @@ -11,6 +11,7 @@ #include "remote.h" #include "refs.h" #include "submodule.h" +#include "column.h" static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ @@ -641,6 +642,8 @@ static void wt_status_print_other(struct wt_status *s, { int i; struct strbuf buf = STRBUF_INIT; + static struct string_list output = STRING_LIST_INIT_DUP; + struct column_options copts; if (!l->nr) return; @@ -649,12 +652,33 @@ static void wt_status_print_other(struct wt_status *s, for (i = 0; i < l->nr; i++) { struct string_list_item *it; + const char *path; it = &(l->items[i]); + path = quote_path(it->string, strlen(it->string), + &buf, s->prefix); + if (column_active(s->colopts)) { + string_list_append(&output, path); + continue; + } status_printf(s, color(WT_STATUS_HEADER, s), "\t"); status_printf_more(s, color(WT_STATUS_UNTRACKED, s), - "%s\n", quote_path(it->string, strlen(it->string), - &buf, s->prefix)); + "%s\n", path); } + + strbuf_release(&buf); + if (!column_active(s->colopts)) + return; + + strbuf_addf(&buf, "%s#\t%s", + color(WT_STATUS_HEADER, s), + color(WT_STATUS_UNTRACKED, s)); + memset(&copts, 0, sizeof(copts)); + copts.padding = 1; + copts.indent = buf.buf; + if (want_color(s->use_color)) + copts.nl = GIT_COLOR_RESET "\n"; + print_columns(&output, s->colopts, &copts); + string_list_clear(&output, 0); strbuf_release(&buf); } diff --git a/wt-status.h b/wt-status.h index 682b4c8f7d..6dd7207e25 100644 --- a/wt-status.h +++ b/wt-status.h @@ -56,6 +56,7 @@ struct wt_status { enum untracked_status_type show_untracked_files; const char *ignore_submodule_arg; char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN]; + int colopts; /* These are computed during processing of the individual sections */ int commitable; From b27004eb32139977f8fa5c01eb235535661f9201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:40 +0700 Subject: [PATCH 09/10] column: support piping stdout to external git-column process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For too complicated output handling, it'd be easier to just spawn git-column and redirect stdout to it. This patch provides helpers to do that. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- column.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ column.h | 3 +++ 2 files changed, 72 insertions(+) diff --git a/column.c b/column.c index d6c115c1bf..9367ba5db5 100644 --- a/column.c +++ b/column.c @@ -2,6 +2,7 @@ #include "column.h" #include "string-list.h" #include "parse-options.h" +#include "run-command.h" #include "utf8.h" #define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \ @@ -363,3 +364,71 @@ int parseopt_column_callback(const struct option *opt, return 0; } + +static int fd_out = -1; +static struct child_process column_process; + +int run_column_filter(int colopts, const struct column_options *opts) +{ + const char *av[10]; + int ret, ac = 0; + struct strbuf sb_colopt = STRBUF_INIT; + struct strbuf sb_width = STRBUF_INIT; + struct strbuf sb_padding = STRBUF_INIT; + + if (fd_out != -1) + return -1; + + av[ac++] = "column"; + strbuf_addf(&sb_colopt, "--raw-mode=%d", colopts); + av[ac++] = sb_colopt.buf; + if (opts && opts->width) { + strbuf_addf(&sb_width, "--width=%d", opts->width); + av[ac++] = sb_width.buf; + } + if (opts && opts->indent) { + av[ac++] = "--indent"; + av[ac++] = opts->indent; + } + if (opts && opts->padding) { + strbuf_addf(&sb_padding, "--padding=%d", opts->padding); + av[ac++] = sb_padding.buf; + } + av[ac] = NULL; + + fflush(stdout); + memset(&column_process, 0, sizeof(column_process)); + column_process.in = -1; + column_process.out = dup(1); + column_process.git_cmd = 1; + column_process.argv = av; + + ret = start_command(&column_process); + + strbuf_release(&sb_colopt); + strbuf_release(&sb_width); + strbuf_release(&sb_padding); + + if (ret) + return -2; + + fd_out = dup(1); + close(1); + dup2(column_process.in, 1); + close(column_process.in); + return 0; +} + +int stop_column_filter(void) +{ + if (fd_out == -1) + return -1; + + fflush(stdout); + close(1); + finish_command(&column_process); + dup2(fd_out, 1); + close(fd_out); + fd_out = -1; + return 0; +} diff --git a/column.h b/column.h index 4f178d84a8..0a61917fa7 100644 --- a/column.h +++ b/column.h @@ -39,4 +39,7 @@ static inline int column_active(unsigned int colopts) extern void print_columns(const struct string_list *list, unsigned int colopts, const struct column_options *opts); +extern int run_column_filter(int colopts, const struct column_options *); +extern int stop_column_filter(void); + #endif From d96e3c150f2b4508f2e7d23ce9183d5b807c2155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Fri, 13 Apr 2012 17:54:41 +0700 Subject: [PATCH 10/10] tag: add --column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ Documentation/git-tag.txt | 9 ++++++++ Makefile | 2 +- builtin/tag.c | 27 +++++++++++++++++++++--- t/t7004-tag.sh | 44 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 4 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 2158f0cbe8..75ecf36157 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -870,6 +870,10 @@ column.status:: Specify whether to output untracked files in `git status` in columns. See `column.ui` for details. +column.tag:: + Specify whether to output tag listing in `git tag` in columns. + See `column.ui` for details. + commit.status:: A boolean to enable/disable inclusion of status information in the commit message template when using an editor to prepare the commit diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 8d32b9a814..e36a7c3d1e 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -13,6 +13,7 @@ SYNOPSIS [ | ] 'git tag' -d ... 'git tag' [-n[]] -l [--contains ] [--points-at ] + [--column[=] | --no-column] [...] [...] 'git tag' -v ... @@ -84,6 +85,14 @@ OPTIONS using fnmatch(3)). Multiple patterns may be given; if any of them matches, the tag is shown. +--column[=]:: +--no-column:: + Display tag listing in columns. See configuration variable + column.tag for option syntax.`--column` and `--no-column` + without options are equivalent to 'always' and 'never' respectively. ++ +This option is only applicable when listing tags without annotation lines. + --contains :: Only list tags which contain the specified commit. diff --git a/Makefile b/Makefile index 297e830ec3..53e27cc955 100644 --- a/Makefile +++ b/Makefile @@ -2168,7 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h connect.o transport.o url.o http-backend.o: url.h -builtin/branch.o builtin/commit.o column.o help.o pager.o: column.h +builtin/branch.o builtin/commit.o builtin/tag.o column.o help.o pager.o: column.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h diff --git a/builtin/tag.c b/builtin/tag.c index fe7e5e5b3d..4fb6bd7b3d 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -16,6 +16,7 @@ #include "revision.h" #include "gpg-interface.h" #include "sha1-array.h" +#include "column.h" static const char * const git_tag_usage[] = { "git tag [-a|-s|-u ] [-f] [-m |-F ] []", @@ -33,6 +34,7 @@ struct tag_filter { }; static struct sha1_array points_at; +static unsigned int colopts; static int match_pattern(const char **patterns, const char *ref) { @@ -263,6 +265,8 @@ static int git_tag_config(const char *var, const char *value, void *cb) int status = git_gpg_config(var, value, cb); if (status) return status; + if (!prefixcmp(var, "column.")) + return git_column_config(var, value, "tag", &colopts); return git_default_config(var, value, cb); } @@ -459,6 +463,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_STRING('u', "local-user", &keyid, "key-id", "use another key to sign the tag"), OPT__FORCE(&force, "replace the tag if exists"), + OPT_COLUMN(0, "column", &colopts, "show tag list in columns"), OPT_GROUP("Tag listing options"), { @@ -495,9 +500,25 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (list + delete + verify > 1) usage_with_options(git_tag_usage, options); - if (list) - return list_tags(argv, lines == -1 ? 0 : lines, - with_commit); + finalize_colopts(&colopts, -1); + if (list && lines != -1) { + if (explicitly_enable_column(colopts)) + die(_("--column and -n are incompatible")); + colopts = 0; + } + if (list) { + int ret; + if (column_active(colopts)) { + struct column_options copts; + memset(&copts, 0, sizeof(copts)); + copts.padding = 2; + run_column_filter(colopts, &copts); + } + ret = list_tags(argv, lines == -1 ? 0 : lines, with_commit); + if (column_active(colopts)) + stop_column_filter(); + return ret; + } if (lines != -1) die(_("-n option is only allowed with -l.")); if (with_commit) diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index f8c247a750..5189446534 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -263,6 +263,50 @@ test_expect_success 'tag -l can accept multiple patterns' ' test_cmp expect actual ' +test_expect_success 'listing tags in column' ' + COLUMNS=40 git tag -l --column=row >actual && + cat >expected <<\EOF && +a1 aa1 cba t210 t211 +v0.2.1 v1.0 v1.0.1 v1.1.3 +EOF + test_cmp expected actual +' + +test_expect_success 'listing tags in column with column.*' ' + git config column.tag row && + git config column.ui dense && + COLUMNS=40 git tag -l >actual && + git config --unset column.ui && + git config --unset column.tag && + cat >expected <<\EOF && +a1 aa1 cba t210 t211 +v0.2.1 v1.0 v1.0.1 v1.1.3 +EOF + test_cmp expected actual +' + +test_expect_success 'listing tag with -n --column should fail' ' + test_must_fail git tag --column -n +' + +test_expect_success 'listing tags -n in column with column.ui ignored' ' + git config column.ui "row dense" && + COLUMNS=40 git tag -l -n >actual && + git config --unset column.ui && + cat >expected <<\EOF && +a1 Foo +aa1 Foo +cba Foo +t210 Foo +t211 Foo +v0.2.1 Foo +v1.0 Foo +v1.0.1 Foo +v1.1.3 Foo +EOF + test_cmp expected actual +' + # creating and verifying lightweight tags: test_expect_success \