From d34e4502fa540ed0654df7dd75b3689c48015f1a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 10 Nov 2020 23:42:11 +0000 Subject: [PATCH 01/11] add -i (built-in): do show an error message for incorrect inputs There is a neat feature in `git add -i` where it allows users to select items via unique prefixes. In the built-in version of `git add -i`, we specifically sort the items (unless they are already sorted) and then perform a binary search to figure out whether the input constitutes a unique prefix. Unfortunately, by mistake this code misidentifies matches even if the input string is not actually a prefix of any item. For example, in the initial menu, where there is a `status` and an `update` command, the input `tadaa` was mistaken as a prefix of `update`. Let's fix this by looking a bit closer whether the input is actually a prefix of the item at the found insert index. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-interactive.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index 555c4abf32..8ca503d803 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -194,7 +194,8 @@ static ssize_t find_unique(const char *string, struct prefix_item_list *list) else if (index + 1 < list->sorted.nr && starts_with(list->sorted.items[index + 1].string, string)) return -1; - else if (index < list->sorted.nr) + else if (index < list->sorted.nr && + starts_with(list->sorted.items[index].string, string)) item = list->sorted.items[index].util; else return -1; From cb581b16ef947a00f8e83f17c9fbe5b5aeed1d65 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 10 Nov 2020 23:42:12 +0000 Subject: [PATCH 02/11] add -i (built-in): send error messages to stderr The Perl version of that command already does that since a301973641f (add -p: print errors in separate color, 2009-02-05). The built-in version's development started by reimplementing the initial version from 5cde71d64af (git-add --interactive, 2006-12-10) for simplicity, though, which still printed error messages to stdout. Let's fix that by imitating the Perl version's behavior in the built-in version of that command. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-interactive.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index 8ca503d803..0f24992ca4 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -365,7 +365,7 @@ static ssize_t list_and_choose(struct add_i_state *s, if (from < 0 || from >= items->items.nr || (singleton && from + 1 != to)) { - color_fprintf_ln(stdout, s->error_color, + color_fprintf_ln(stderr, s->error_color, _("Huh (%s)?"), p); break; } else if (singleton) { From decc9ee4eaf2c33c28e2958439d276904a2ce279 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 11 Nov 2020 12:28:16 +0000 Subject: [PATCH 03/11] add -p (built-in): imitate `xdl_format_hunk_hdr()` generating hunk headers In libxdiff, imitating GNU diff, the hunk headers only show the line count if it is different from 1. When splitting hunks, the Perl version of `git add -p` already imitates this. Let's do the same in the built-in version of said command. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-patch.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/add-patch.c b/add-patch.c index bd94bd3a7c..55bd9d4f37 100644 --- a/add-patch.c +++ b/add-patch.c @@ -661,9 +661,14 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, else new_offset += delta; - strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@", - old_offset, header->old_count, - new_offset, header->new_count); + strbuf_addf(out, "@@ -%lu", old_offset); + if (header->old_count != 1) + strbuf_addf(out, ",%lu", header->old_count); + strbuf_addf(out, " +%lu", new_offset); + if (header->new_count != 1) + strbuf_addf(out, ",%lu", header->new_count); + strbuf_addstr(out, " @@"); + if (len) strbuf_add(out, p, len); else if (colored) From 6f1a5caa0b69278bc7eb79d3474a1e881bd0c663 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 11 Nov 2020 12:28:17 +0000 Subject: [PATCH 04/11] add -i: use `reset_color` consistently We already maintain a list of colors in the `add_i_state`, therefore we should use them. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-patch.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/add-patch.c b/add-patch.c index 55bd9d4f37..1db674414b 100644 --- a/add-patch.c +++ b/add-patch.c @@ -672,7 +672,7 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, if (len) strbuf_add(out, p, len); else if (colored) - strbuf_addf(out, "%s\n", GIT_COLOR_RESET); + strbuf_addf(out, "%s\n", s->s.reset_color); else strbuf_addch(out, '\n'); } @@ -1065,7 +1065,7 @@ static void recolor_hunk(struct add_p_state *s, struct hunk *hunk) s->s.file_new_color : s->s.context_color); strbuf_add(&s->colored, plain + current, eol - current); - strbuf_addstr(&s->colored, GIT_COLOR_RESET); + strbuf_addstr(&s->colored, s->s.reset_color); if (next > eol) strbuf_add(&s->colored, plain + eol, next - eol); current = next; From c62cd1720f5c9cf57955a78576ba7e862b7352e6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 11 Nov 2020 12:28:18 +0000 Subject: [PATCH 05/11] add -i (built-in): prevent the `reset` "color" from being configured The Perl version of that command sneakily uses `git config --get-color` to figure out the ANSI sequence to reset the color, but passes the empty string and therefore cannot actually match any config entry. This was missed when re-implementing the command as a built-in command. Let's fix this, preventing the `reset` sequence from being overridden via the config. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-interactive.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index 0f24992ca4..f3a1d7456e 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -44,7 +44,6 @@ void init_add_i_state(struct add_i_state *s, struct repository *r) init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED); init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE); init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED); - init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET); init_color(r, s, "fraginfo", s->fraginfo_color, diff_get_color(s->use_color, DIFF_FRAGINFO)); init_color(r, s, "context", s->context_color, @@ -54,6 +53,9 @@ void init_add_i_state(struct add_i_state *s, struct repository *r) init_color(r, s, "new", s->file_new_color, diff_get_color(s->use_color, DIFF_FILE_NEW)); + strlcpy(s->reset_color, + s->use_color ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + FREE_AND_NULL(s->interactive_diff_filter); git_config_get_string("interactive.difffilter", &s->interactive_diff_filter); From 25d9e5ccba3fbcdb4109b6b2b2a7e721b0af6d77 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 11 Nov 2020 12:28:19 +0000 Subject: [PATCH 06/11] add -i (built-in): use correct names to load color.diff.* config The builtin version of add-interactive mistakenly loads diff colors from color.interactive.* instead of color.diff.*. It also accidentally spells `frag` as `fraginfo`. Let's fix that. Note also that we don't respect the historical `diff.color.*`. The perl version never did, and those have been deprecated since 2007. Reported-by: Philippe Blain Co-authored-by: Jeff King Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-interactive.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index f3a1d7456e..9126684348 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -12,10 +12,10 @@ #include "prompt.h" static void init_color(struct repository *r, struct add_i_state *s, - const char *slot_name, char *dst, + const char *section_and_slot, char *dst, const char *default_color) { - char *key = xstrfmt("color.interactive.%s", slot_name); + char *key = xstrfmt("color.%s", section_and_slot); const char *value; if (!s->use_color) @@ -40,17 +40,20 @@ void init_add_i_state(struct add_i_state *s, struct repository *r) git_config_colorbool("color.interactive", value); s->use_color = want_color(s->use_color); - init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD); - init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED); - init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE); - init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED); - init_color(r, s, "fraginfo", s->fraginfo_color, + init_color(r, s, "interactive.header", s->header_color, GIT_COLOR_BOLD); + init_color(r, s, "interactive.help", s->help_color, GIT_COLOR_BOLD_RED); + init_color(r, s, "interactive.prompt", s->prompt_color, + GIT_COLOR_BOLD_BLUE); + init_color(r, s, "interactive.error", s->error_color, + GIT_COLOR_BOLD_RED); + + init_color(r, s, "diff.frag", s->fraginfo_color, diff_get_color(s->use_color, DIFF_FRAGINFO)); - init_color(r, s, "context", s->context_color, + init_color(r, s, "diff.context", s->context_color, diff_get_color(s->use_color, DIFF_CONTEXT)); - init_color(r, s, "old", s->file_old_color, + init_color(r, s, "diff.old", s->file_old_color, diff_get_color(s->use_color, DIFF_FILE_OLD)); - init_color(r, s, "new", s->file_new_color, + init_color(r, s, "diff.new", s->file_new_color, diff_get_color(s->use_color, DIFF_FILE_NEW)); strlcpy(s->reset_color, From 6681e3603216f0e279fea467a0e6d829fffb82a5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 16 Nov 2020 16:08:28 +0000 Subject: [PATCH 07/11] add -p (built-in): do not color the progress indicator separately The Perl version of this command colors the progress indicator and the prompt message in one go. Let's do the same in the built-in version so that the same upcoming test (which will compare the output of `git add -p` against a known-good version) will pass both for the Perl version as well as for the built-in version. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-patch.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/add-patch.c b/add-patch.c index 1db674414b..8ad0937e6c 100644 --- a/add-patch.c +++ b/add-patch.c @@ -1461,15 +1461,15 @@ static int patch_update_file(struct add_p_state *s, else prompt_mode_type = PROMPT_HUNK; - color_fprintf(stdout, s->s.prompt_color, - "(%"PRIuMAX"/%"PRIuMAX") ", + printf("%s(%"PRIuMAX"/%"PRIuMAX") ", s->s.prompt_color, (uintmax_t)hunk_index + 1, (uintmax_t)(file_diff->hunk_nr ? file_diff->hunk_nr : 1)); - color_fprintf(stdout, s->s.prompt_color, - _(s->mode->prompt_mode[prompt_mode_type]), - s->buf.buf); + printf(_(s->mode->prompt_mode[prompt_mode_type]), + s->buf.buf); + if (*s->s.reset_color) + fputs(s->s.reset_color, stdout); fflush(stdout); if (read_single_character(s) == EOF) break; From afae3cb6b036e8fb218e5699c8ad3c18b6d4b992 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 16 Nov 2020 16:08:29 +0000 Subject: [PATCH 08/11] add -i (built-in): use the same indentation as the Perl version When copying the spaces used to indent non-flat lists in `git add -i`, one space was appended by mistake. This makes the output of the built-in version of `git add -i` inconsistent with the Perl version. Let's adjust the built-in version to produce the same output as the Perl version. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-interactive.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/add-interactive.c b/add-interactive.c index 9126684348..c298a8b80f 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -1137,7 +1137,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) print_file_item_data.color = data.color; print_file_item_data.reset = data.reset; - strbuf_addstr(&header, " "); + strbuf_addstr(&header, " "); strbuf_addf(&header, print_file_item_data.modified_fmt, _("staged"), _("unstaged"), _("path")); opts.list_opts.header = header.buf; From 0cb8939fb6795c9f94f9184935b6a83aebdfc47a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 16 Nov 2020 16:08:30 +0000 Subject: [PATCH 09/11] add -i (Perl version): color header to match the C version Both versions of `add -i` indent non-flat lists by five spaces. However when using color the C version prints these spaces after the ANSI color codes whereas the Perl version prints them before the color codes. Change the Perl version to match the C version to allow for introducing a test that verifies that both versions produce the exact same output. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 8a72018712..b42e15a575 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -483,10 +483,8 @@ sub list_and_choose { my $last_lf = 0; if ($opts->{HEADER}) { - if (!$opts->{LIST_FLAT}) { - print " "; - } - print colored $header_color, "$opts->{HEADER}\n"; + my $indent = $opts->{LIST_FLAT} ? "" : " "; + print colored $header_color, "$indent$opts->{HEADER}\n"; } for ($i = 0; $i < @stuff; $i++) { my $chosen = $chosen[$i] ? '*' : ' '; From 890b68b2637fcadb4b1017ecf50248d616c96b8b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 16 Nov 2020 16:08:31 +0000 Subject: [PATCH 10/11] add -p: prefer color.diff.context over color.diff.plain Git's diff machinery allows users to override the colors to use in diffs, even the plain-colored context lines. As of 8dbf3eb6850 (diff.h: rename DIFF_PLAIN color slot to DIFF_CONTEXT, 2015-05-27), the preferred name of the config setting is `color.diff.context`, although Git still allows `color.diff.plain`. In the context of `git add -p`, this logic is a bit hard to replicate: `git_diff_basic_config()` reads all config values sequentially and if it sees _any_ `color.diff.context` or `color.diff.plain`, it accepts the new color. The Perl version of `git add -p` needs to go through `git config --get-color`, though, which allows only one key to be specified. The same goes for the built-in version of `git add -p`, which has to go through `repo_config_get_value()`. The best we can do here is to look for `.context` and if none is found, fall back to looking for `.plain`, and if still not found, fall back to the hard-coded default (which in this case is simply the empty string, as context lines are typically rendered without colored). This still leads to inconsistencies when both config names are used: the initial diff will be colored by the diff machinery. Once edited by a user, a hunk has to be re-colored by `git add -p`, though, which would then use the other setting to color the context lines. In practice, this is not _all_ that bad. The `git config` manual says this in the `color.diff.`: `context` (context text - `plain` is a historical synonym) We should therefore assume that users use either one or the other, but not both names. Besides, it is relatively uncommon to look at a hunk after editing it because it is immediately staged by default. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- add-interactive.c | 6 ++++-- git-add--interactive.perl | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/add-interactive.c b/add-interactive.c index c298a8b80f..54dfdc56f5 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -49,8 +49,10 @@ void init_add_i_state(struct add_i_state *s, struct repository *r) init_color(r, s, "diff.frag", s->fraginfo_color, diff_get_color(s->use_color, DIFF_FRAGINFO)); - init_color(r, s, "diff.context", s->context_color, - diff_get_color(s->use_color, DIFF_CONTEXT)); + init_color(r, s, "diff.context", s->context_color, "fall back"); + if (!strcmp(s->context_color, "fall back")) + init_color(r, s, "diff.plain", s->context_color, + diff_get_color(s->use_color, DIFF_CONTEXT)); init_color(r, s, "diff.old", s->file_old_color, diff_get_color(s->use_color, DIFF_FILE_OLD)); init_color(r, s, "diff.new", s->file_new_color, diff --git a/git-add--interactive.perl b/git-add--interactive.perl index b42e15a575..f68b68f9ce 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -30,9 +30,9 @@ my ($fraginfo_color) = $diff_use_color ? ( $repo->get_color('color.diff.frag', 'cyan'), ) : (); -my ($diff_plain_color) = +my ($diff_context_color) = $diff_use_color ? ( - $repo->get_color('color.diff.plain', ''), + $repo->get_color($repo->config('color.diff.context') ? 'color.diff.context' : 'color.diff.plain', ''), ) : (); my ($diff_old_color) = $diff_use_color ? ( @@ -1046,7 +1046,7 @@ sub color_diff { colored((/^@/ ? $fraginfo_color : /^\+/ ? $diff_new_color : /^-/ ? $diff_old_color : - $diff_plain_color), + $diff_context_color), $_); } @_; } From 96386faa03d5a714122b4a39e7257d278e232469 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 16 Nov 2020 16:08:32 +0000 Subject: [PATCH 11/11] add -i: verify in the tests that colors can be overridden Now that the Perl version produces the same output as the built-in version (mostly fixing bugs in the latter), let's add a regression test to verify that it stays this way. Note that we only `grep` for the colored error message instead of verifying that the entire `stderr` consists of just this one line: when running the test script using the `-x` option to trace the commands, the sub-shell in `force_color` causes those commands to be traced into `err.raw` (unless running in Bash where we set the `BASH_XTRACEFD` variable to avoid that). Also note that the color reset in the `+new` line might look funny and unnecessary, as the corresponding `old` line does not reset the color after the diff marker only to turn the color back on right away. However, this is a (necessary) side effect of the white-space check: in `emit_line_ws_markup()`, we first emit the diff marker via `emit_line_0()` and then the rest of the line via `ws_check_emit()`. To leave them somewhat decoupled, the color has to be reset after the diff marker to allow for the rest of the line to start with another color (or inverted, in case of white-space issues). Finally, we have to simulate hunk editing: the `git add -p` command cannot rely on the internal diff machinery for coloring after letting the user edit a hunk; It has to "re-color" the edited hunk. This is the primary reason why that command is interested in the exact values of the `color.diff.*` settings in the first place. To test this re-coloring, we therefore have to pretend to edit a hunk and then show that hunk in the regression test. Co-authored-by: Jeff King Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t3701-add-interactive.sh | 84 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index ca04fac417..cc3f434a97 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -589,6 +589,90 @@ test_expect_success 'diffs can be colorized' ' grep "$(printf "\\033")" output ' +test_expect_success 'colors can be overridden' ' + git reset --hard && + test_when_finished "git rm -f color-test" && + test_write_lines context old more-context >color-test && + git add color-test && + test_write_lines context new more-context another-one >color-test && + + echo trigger an error message >input && + force_color git \ + -c color.interactive.error=blue \ + add -i 2>err.raw err && + grep "Huh (trigger)?" err && + + test_write_lines help quit >input && + force_color git \ + -c color.interactive.header=red \ + -c color.interactive.help=green \ + -c color.interactive.prompt=yellow \ + add -i >actual.raw actual && + cat >expect <<-\EOF && + staged unstaged path + 1: +3/-0 +2/-1 color-test + + *** Commands *** + 1: status 2: update 3: revert 4: add untracked + 5: patch 6: diff 7: quit 8: help + What now> status - show paths with changes + update - add working tree state to the staged set of changes + revert - revert staged set of changes back to the HEAD version + patch - pick hunks and update selectively + diff - view diff between HEAD and index + add untracked - add contents of untracked files to the staged set of changes + *** Commands *** + 1: status 2: update 3: revert 4: add untracked + 5: patch 6: diff 7: quit 8: help + What now> Bye. + EOF + test_cmp expect actual && + + : exercise recolor_hunk by editing and then look at the hunk again && + test_write_lines s e K q >input && + force_color git \ + -c color.interactive.prompt=yellow \ + -c color.diff.meta=italic \ + -c color.diff.frag=magenta \ + -c color.diff.context=cyan \ + -c color.diff.old=bold \ + -c color.diff.new=blue \ + -c core.editor=touch \ + add -p >actual.raw actual.decoded && + sed "s/index [0-9a-f]*\\.\\.[0-9a-f]* 100644//" actual && + cat >expect <<-\EOF && + diff --git a/color-test b/color-test + + --- a/color-test + +++ b/color-test + @@ -1,3 +1,4 @@ + context + -old + +new + more-context + +another-one + (1/1) Stage this hunk [y,n,q,a,d,s,e,?]? Split into 2 hunks. + @@ -1,3 +1,3 @@ + context + -old + +new + more-context + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? @@ -3 +3,2 @@ + more-context + +another-one + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,3 +1,3 @@ + context + -old + +new + more-context + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? + EOF + test_cmp expect actual +' + test_expect_success 'colorized diffs respect diff.wsErrorHighlight' ' git reset --hard &&