]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'js/add-i-delete'
authorJunio C Hamano <gitster@pobox.com>
Sun, 3 Jul 2022 04:56:08 +0000 (21:56 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 3 Jul 2022 04:56:08 +0000 (21:56 -0700)
Rewrite of "git add -i" in C that appeared in Git 2.25 didn't
correctly record a removed file to the index, which was fixed.

* js/add-i-delete:
  add --interactive: allow `update` to stage deleted files

1  2 
add-interactive.c
t/t3701-add-interactive.sh

diff --combined add-interactive.c
index 6047e8f6489fa3e6695e3925cd513963d05a5615,63bc1c1d671949f89d8b60af5c26f8c7aec41539..22fcd3412ca5d8b343190cd3ae07148d83fd55e3
@@@ -9,13 -9,22 +9,13 @@@
  #include "lockfile.h"
  #include "dir.h"
  #include "run-command.h"
 -
 -struct add_i_state {
 -      struct repository *r;
 -      int use_color;
 -      char header_color[COLOR_MAXLEN];
 -      char help_color[COLOR_MAXLEN];
 -      char prompt_color[COLOR_MAXLEN];
 -      char error_color[COLOR_MAXLEN];
 -      char reset_color[COLOR_MAXLEN];
 -};
 +#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)
@@@ -27,7 -36,7 +27,7 @@@
        free(key);
  }
  
 -static void init_add_i_state(struct add_i_state *s, struct repository *r)
 +void init_add_i_state(struct add_i_state *s, struct repository *r)
  {
        const char *value;
  
                        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, "reset", s->reset_color, GIT_COLOR_RESET);
 +      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, "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_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);
 +
 +      FREE_AND_NULL(s->interactive_diff_algorithm);
 +      git_config_get_string("diff.algorithm",
 +                            &s->interactive_diff_algorithm);
 +
 +      git_config_get_bool("interactive.singlekey", &s->use_single_key);
 +      if (s->use_single_key)
 +              setbuf(stdin, NULL);
 +}
 +
 +void clear_add_i_state(struct add_i_state *s)
 +{
 +      FREE_AND_NULL(s->interactive_diff_filter);
 +      FREE_AND_NULL(s->interactive_diff_algorithm);
 +      memset(s, 0, sizeof(*s));
 +      s->use_color = -1;
  }
  
  /*
@@@ -104,12 -78,8 +104,12 @@@ struct prefix_item_list 
        int *selected; /* for multi-selections */
        size_t min_length, max_length;
  };
 -#define PREFIX_ITEM_LIST_INIT \
 -      { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 }
 +#define PREFIX_ITEM_LIST_INIT { \
 +      .items = STRING_LIST_INIT_DUP, \
 +      .sorted = STRING_LIST_INIT_NODUP, \
 +      .min_length = 1, \
 +      .max_length = 4, \
 +}
  
  static void prefix_item_list_clear(struct prefix_item_list *list)
  {
@@@ -207,8 -177,7 +207,8 @@@ static ssize_t find_unique(const char *
        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;
@@@ -304,12 -273,13 +304,12 @@@ static ssize_t list_and_choose(struct a
                fputs(singleton ? "> " : ">> ", stdout);
                fflush(stdout);
  
 -              if (strbuf_getline(&input, stdin) == EOF) {
 +              if (git_read_line_interactively(&input) == EOF) {
                        putchar('\n');
                        if (immediate)
                                res = LIST_AND_CHOOSE_QUIT;
                        break;
                }
 -              strbuf_trim(&input);
  
                if (!input.len)
                        break;
                                if (endp == p + sep)
                                        to = from + 1;
                                else if (*endp == '-') {
 -                                      to = strtoul(++endp, &endp, 10);
 +                                      if (isdigit(*(++endp)))
 +                                              to = strtoul(endp, &endp, 10);
 +                                      else
 +                                              to = items->items.nr;
                                        /* extra characters after the range? */
                                        if (endp != p + sep)
                                                from = -1;
  
                        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) {
@@@ -419,7 -386,7 +419,7 @@@ struct file_item 
  
  static void add_file_item(struct string_list *files, const char *name)
  {
 -      struct file_item *item = xcalloc(sizeof(*item), 1);
 +      struct file_item *item = xcalloc(1, sizeof(*item));
  
        string_list_append(files, name)->util = item;
  }
@@@ -482,7 -449,7 +482,7 @@@ static void collect_changes_cb(struct d
  
                        add_file_item(s->files, name);
  
 -                      entry = xcalloc(sizeof(*entry), 1);
 +                      CALLOC_ARRAY(entry, 1);
                        hashmap_entry_init(&entry->ent, hash);
                        entry->name = s->files->items[s->files->nr - 1].string;
                        entry->item = s->files->items[s->files->nr - 1].util;
@@@ -568,9 -535,10 +568,9 @@@ static int get_modified_files(struct re
                        run_diff_files(&rev, 0);
                }
  
 -              if (ps)
 -                      clear_pathspec(&rev.prune_data);
 +              release_revisions(&rev);
        }
 -      hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
 +      hashmap_clear_and_free(&s.file_map, struct pathname_entry, ent);
        if (unmerged_count)
                *unmerged_count = s.unmerged_count;
        if (binary_count)
@@@ -697,8 -665,16 +697,16 @@@ static int run_update(struct add_i_stat
  
        for (i = 0; i < files->items.nr; i++) {
                const char *name = files->items.items[i].string;
-               if (files->selected[i] &&
-                   add_file_to_index(s->r->index, name, 0) < 0) {
+               struct stat st;
+               if (!files->selected[i])
+                       continue;
+               if (lstat(name, &st) && is_missing_file_error(errno)) {
+                       if (remove_file_from_index(s->r->index, name) < 0) {
+                               res = error(_("could not stage '%s'"), name);
+                               break;
+                       }
+               } else if (add_file_to_index(s->r->index, name, 0) < 0) {
                        res = error(_("could not stage '%s'"), name);
                        break;
                }
@@@ -798,14 -774,14 +806,14 @@@ static int run_revert(struct add_i_stat
        diffopt.flags.override_submodule_config = 1;
        diffopt.repo = s->r;
  
 -      if (do_diff_cache(&oid, &diffopt))
 +      if (do_diff_cache(&oid, &diffopt)) {
 +              diff_free(&diffopt);
                res = -1;
 -      else {
 +      else {
                diffcore_std(&diffopt);
                diff_flush(&diffopt);
        }
        free(paths);
 -      clear_pathspec(&diffopt.pathspec);
  
        if (!res && write_locked_index(s->r->index, &index_lock,
                                       COMMIT_LOCK) < 0)
@@@ -947,20 -923,17 +955,20 @@@ static int run_patch(struct add_i_stat
  
        opts->prompt = N_("Patch update");
        count = list_and_choose(s, files, opts);
 -      if (count >= 0) {
 -              struct argv_array args = ARGV_ARRAY_INIT;
 +      if (count > 0) {
 +              struct strvec args = STRVEC_INIT;
 +              struct pathspec ps_selected = { 0 };
  
 -              argv_array_pushl(&args, "git", "add--interactive", "--patch",
 -                               "--", NULL);
                for (i = 0; i < files->items.nr; i++)
                        if (files->selected[i])
 -                              argv_array_push(&args,
 -                                              files->items.items[i].string);
 -              res = run_command_v_opt(args.argv, 0);
 -              argv_array_clear(&args);
 +                              strvec_push(&args,
 +                                          files->items.items[i].string);
 +              parse_pathspec(&ps_selected,
 +                             PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 +                             PATHSPEC_LITERAL_PATH, "", args.v);
 +              res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected);
 +              strvec_clear(&args);
 +              clear_pathspec(&ps_selected);
        }
  
        return res;
@@@ -988,19 -961,19 +996,19 @@@ static int run_diff(struct add_i_state 
        opts->flags = IMMEDIATE;
        count = list_and_choose(s, files, opts);
        opts->flags = 0;
 -      if (count >= 0) {
 -              struct argv_array args = ARGV_ARRAY_INIT;
 +      if (count > 0) {
 +              struct strvec args = STRVEC_INIT;
  
 -              argv_array_pushl(&args, "git", "diff", "-p", "--cached",
 -                               oid_to_hex(!is_initial ? &oid :
 -                                          s->r->hash_algo->empty_tree),
 -                               "--", NULL);
 +              strvec_pushl(&args, "git", "diff", "-p", "--cached",
 +                           oid_to_hex(!is_initial ? &oid :
 +                                      s->r->hash_algo->empty_tree),
 +                           "--", NULL);
                for (i = 0; i < files->items.nr; i++)
                        if (files->selected[i])
 -                              argv_array_push(&args,
 -                                              files->items.items[i].string);
 -              res = run_command_v_opt(args.argv, 0);
 -              argv_array_clear(&args);
 +                              strvec_push(&args,
 +                                          files->items.items[i].string);
 +              res = run_command_v_opt(args.v, 0);
 +              strvec_clear(&args);
        }
  
        putchar('\n');
@@@ -1125,7 -1098,7 +1133,7 @@@ int run_add_i(struct repository *r, con
        int res = 0;
  
        for (i = 0; i < ARRAY_SIZE(command_list); i++) {
 -              struct command_item *util = xcalloc(sizeof(*util), 1);
 +              struct command_item *util = xcalloc(1, sizeof(*util));
                util->command = command_list[i].command;
                string_list_append(&commands.items, command_list[i].string)
                        ->util = util;
        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;
        strbuf_release(&print_file_item_data.worktree);
        strbuf_release(&header);
        prefix_item_list_clear(&commands);
 +      clear_add_i_state(&s);
  
        return res;
  }
index fc26cb8bae8d93d49219bd2553ba83e1820b6b43,02c919c5da4e0e9c842a2a27a6b7129566f5e9d6..b354fb39de839aba1506693ee4a0cd7d4967d656
@@@ -1,9 -1,6 +1,9 @@@
  #!/bin/sh
  
  test_description='add -i basic tests'
 +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 +
  . ./test-lib.sh
  . "$TEST_DIRECTORY"/lib-terminal.sh
  
@@@ -26,26 -23,6 +26,26 @@@ diff_cmp () 
        test_cmp "$1.filtered" "$2.filtered"
  }
  
 +# This function uses a trick to manipulate the interactive add to use color:
 +# the `want_color()` function special-cases the situation where a pager was
 +# spawned and Git now wants to output colored text: to detect that situation,
 +# the environment variable `GIT_PAGER_IN_USE` is set. However, color is
 +# suppressed despite that environment variable if the `TERM` variable
 +# indicates a dumb terminal, so we set that variable, too.
 +
 +force_color () {
 +      # The first element of $@ may be a shell function, as a result POSIX
 +      # does not guarantee that "one-shot assignment" will not persist after
 +      # the function call. Thus, we prevent these variables from escaping
 +      # this function's context with this subshell.
 +      (
 +              GIT_PAGER_IN_USE=true &&
 +              TERM=vt100 &&
 +              export GIT_PAGER_IN_USE TERM &&
 +              "$@"
 +      )
 +}
 +
  test_expect_success 'setup (initial)' '
        echo content >file &&
        git add file &&
@@@ -80,15 -57,6 +80,15 @@@ test_expect_success 'revert works (init
        ! grep . output
  '
  
 +test_expect_success 'add untracked (multiple)' '
 +      test_when_finished "git reset && rm [1-9]" &&
 +      touch $(test_seq 9) &&
 +      test_write_lines a "2-5 8-" | git add -i -- [1-9] &&
 +      test_write_lines 2 3 4 5 8 9 >expected &&
 +      git ls-files [1-9] >output &&
 +      test_cmp expected output
 +'
 +
  test_expect_success 'setup (commit)' '
        echo baseline >file &&
        git add file &&
@@@ -103,6 -71,15 +103,15 @@@ test_expect_success 'status works (comm
        grep "+1/-0 *+2/-0 file" output
  '
  
+ test_expect_success 'update can stage deletions' '
+       >to-delete &&
+       git add to-delete &&
+       rm to-delete &&
+       test_write_lines u t "" | git add -i &&
+       git ls-files to-delete >output &&
+       test_must_be_empty output
+ '
  test_expect_success 'setup expected' '
        cat >expected <<-\EOF
        index 180b47c..b6f2c08 100644
@@@ -126,6 -103,7 +135,6 @@@ test_expect_success 'revert works (comm
        grep "unchanged *+3/-0 file" output
  '
  
 -
  test_expect_success 'setup expected' '
        cat >expected <<-\EOF
        EOF
@@@ -294,41 -272,10 +303,41 @@@ test_expect_success FILEMODE 'stage mod
  
  # end of tests disabled when filemode is not usable
  
 +test_expect_success 'different prompts for mode change/deleted' '
 +      git reset --hard &&
 +      >file &&
 +      >deleted &&
 +      git add --chmod=+x file deleted &&
 +      echo changed >file &&
 +      rm deleted &&
 +      test_write_lines n n n |
 +      git -c core.filemode=true add -p >actual &&
 +      sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered &&
 +      cat >expect <<-\EOF &&
 +      (1/1) Stage deletion [y,n,q,a,d,?]?
 +      (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,?]?
 +      (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?
 +      EOF
 +      test_cmp expect actual.filtered
 +'
 +
 +test_expect_success 'correct message when there is nothing to do' '
 +      git reset --hard &&
 +      git add -p 2>err &&
 +      test_i18ngrep "No changes" err &&
 +      printf "\\0123" >binary &&
 +      git add binary &&
 +      printf "\\0abc" >binary &&
 +      git add -p 2>err &&
 +      test_i18ngrep "Only binary files changed" err
 +'
 +
  test_expect_success 'setup again' '
        git reset --hard &&
        test_chmod +x file &&
 -      echo content >>file
 +      echo content >>file &&
 +      test_write_lines A B C D>file2 &&
 +      git add file2
  '
  
  # Write the patch file with a new line at the top and bottom
@@@ -343,27 -290,13 +352,27 @@@ test_expect_success 'setup patch' 
         content
        +lastline
        \ No newline at end of file
 +      diff --git a/file2 b/file2
 +      index 8422d40..35b930a 100644
 +      --- a/file2
 +      +++ b/file2
 +      @@ -1,4 +1,5 @@
 +      -A
 +      +Z
 +       B
 +      +Y
 +       C
 +      -D
 +      +X
        EOF
  '
  
  # Expected output, diff is similar to the patch but w/ diff at the top
  test_expect_success 'setup expected' '
        echo diff --git a/file b/file >expected &&
 -      cat patch |sed "/^index/s/ 100644/ 100755/" >>expected &&
 +      sed -e "/^index 180b47c/s/ 100644/ 100755/" \
 +          -e /1,5/s//1,4/ \
 +          -e /Y/d patch >>expected &&
        cat >expected-output <<-\EOF
        --- a/file
        +++ b/file
         content
        +lastline
        \ No newline at end of file
 +      --- a/file2
 +      +++ b/file2
 +      @@ -1,4 +1,5 @@
 +      -A
 +      +Z
 +       B
 +      +Y
 +       C
 +      -D
 +      +X
 +      @@ -1,2 +1,2 @@
 +      -A
 +      +Z
 +       B
 +      @@ -2,2 +2,3 @@
 +       B
 +      +Y
 +       C
 +      @@ -3,2 +4,2 @@
 +       C
 +      -D
 +      +X
        EOF
  '
  
  # Test splitting the first patch, then adding both
 -test_expect_success C_LOCALE_OUTPUT 'add first line works' '
 +test_expect_success 'add first line works' '
        git commit -am "clear local changes" &&
        git apply patch &&
 -      printf "%s\n" s y y | git add -p file 2>error |
 -              sed -n -e "s/^([1-2]\/[1-2]) Stage this hunk[^@]*\(@@ .*\)/\1/" \
 -                     -e "/^[-+@ \\\\]"/p  >output &&
 +      test_write_lines s y y s y n y | git add -p 2>error >raw-output &&
 +      sed -n -e "s/^([1-9]\/[1-9]) Stage this hunk[^@]*\(@@ .*\)/\1/" \
 +             -e "/^[-+@ \\\\]"/p raw-output >output &&
        test_must_be_empty error &&
        git diff --cached >diff &&
        diff_cmp expected diff &&
@@@ -462,25 -373,6 +471,25 @@@ test_expect_success 'deleting an empty 
        diff_cmp expected diff
  '
  
 +test_expect_success 'adding an empty file' '
 +      git init added &&
 +      (
 +              cd added &&
 +              test_commit initial &&
 +              >empty &&
 +              git add empty &&
 +              test_tick &&
 +              git commit -m empty &&
 +              git tag added-file &&
 +              git reset --hard HEAD^ &&
 +              test_path_is_missing empty &&
 +
 +              echo y | git checkout -p added-file -- >actual &&
 +              test_path_is_file empty &&
 +              test_i18ngrep "Apply addition to index and worktree" actual
 +      )
 +'
 +
  test_expect_success 'split hunk setup' '
        git reset --hard &&
        test_write_lines 10 20 30 40 50 60 >test &&
        test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test
  '
  
 +test_expect_success 'goto hunk' '
 +      test_when_finished "git reset" &&
 +      tr _ " " >expect <<-EOF &&
 +      (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1:  -1,2 +1,3          +15
 +      _ 2:  -2,4 +3,8          +21
 +      go to which hunk? @@ -1,2 +1,3 @@
 +      _10
 +      +15
 +      _20
 +      (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
 +      EOF
 +      test_write_lines s y g 1 | git add -p >actual &&
 +      tail -n 7 <actual >actual.trimmed &&
 +      test_cmp expect actual.trimmed
 +'
 +
 +test_expect_success 'navigate to hunk via regex' '
 +      test_when_finished "git reset" &&
 +      tr _ " " >expect <<-EOF &&
 +      (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,2 +1,3 @@
 +      _10
 +      +15
 +      _20
 +      (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
 +      EOF
 +      test_write_lines s y /1,2 | git add -p >actual &&
 +      tail -n 5 <actual >actual.trimmed &&
 +      test_cmp expect actual.trimmed
 +'
 +
  test_expect_success 'split hunk "add -p (edit)"' '
        # Split, say Edit and do nothing.  Then:
        #
        ! grep "^+15" actual
  '
  
 -test_expect_failure 'split hunk "add -p (no, yes, edit)"' '
 +test_expect_success 'setup ADD_I_USE_BUILTIN check' '
 +      result=success &&
 +      if ! test_have_prereq ADD_I_USE_BUILTIN
 +      then
 +              result=failure
 +      fi
 +'
 +
 +test_expect_$result 'split hunk "add -p (no, yes, edit)"' '
        test_write_lines 5 10 20 21 30 31 40 50 60 >test &&
        git reset &&
        # test sequence is s(plit), n(o), y(es), e(dit)
        ! grep "^+31" actual
  '
  
 +test_expect_success 'split hunk with incomplete line at end' '
 +      git reset --hard &&
 +      printf "missing LF" >>test &&
 +      git add test &&
 +      test_write_lines before 10 20 30 40 50 60 70 >test &&
 +      git grep --cached missing &&
 +      test_write_lines s n y q | git add -p &&
 +      test_must_fail git grep --cached missing &&
 +      git grep before &&
 +      test_must_fail git grep --cached before
 +'
 +
 +test_expect_$result 'edit, adding lines to the first hunk' '
 +      test_write_lines 10 11 20 30 40 50 51 60 >test &&
 +      git reset &&
 +      tr _ " " >patch <<-EOF &&
 +      @@ -1,5 +1,6 @@
 +      _10
 +      +11
 +      +12
 +      _20
 +      +21
 +      +22
 +      _30
 +      EOF
 +      # test sequence is s(plit), e(dit), n(o)
 +      # q n q q is there to make sure we exit at the end.
 +      printf "%s\n" s e n   q n q q |
 +      EDITOR=./fake_editor.sh git add -p 2>error &&
 +      test_must_be_empty error &&
 +      git diff --cached >actual &&
 +      grep "^+22" actual
 +'
 +
  test_expect_success 'patch mode ignores unmerged entries' '
        git reset --hard &&
        test_commit conflict &&
        test_commit non-conflict &&
        git checkout -b side &&
        test_commit side conflict.t &&
 -      git checkout master &&
 -      test_commit master conflict.t &&
 +      git checkout main &&
 +      test_commit main conflict.t &&
        test_must_fail git merge side &&
        echo changed >non-conflict.t &&
        echo y | git add -p >output &&
        diff_cmp expected diff
  '
  
 -test_expect_success TTY 'diffs can be colorized' '
 +test_expect_success 'index is refreshed after applying patch' '
 +      git reset --hard &&
 +      echo content >test &&
 +      printf y | git add -p &&
 +      git diff-files --exit-code
 +'
 +
 +test_expect_success 'diffs can be colorized' '
        git reset --hard &&
  
        echo content >test &&
 -      printf y | test_terminal git add -p >output 2>&1 &&
 +      printf y >y &&
 +      force_color git add -p >output 2>&1 <y &&
 +      git diff-files --exit-code &&
  
        # We do not want to depend on the exact coloring scheme
        # git uses for diffs, so just check that we saw some kind of color.
        grep "$(printf "\\033")" output
  '
  
 -test_expect_success TTY 'diffFilter filters diff' '
 +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 <input &&
 +      test_decode_color <err.raw >err &&
 +      grep "<BLUE>Huh (trigger)?<RESET>" 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 <input &&
 +      test_decode_color <actual.raw >actual &&
 +      cat >expect <<-\EOF &&
 +      <RED>           staged     unstaged path<RESET>
 +        1:        +3/-0        +2/-1 color-test
 +
 +      <RED>*** Commands ***<RESET>
 +        1: <YELLOW>s<RESET>tatus        2: <YELLOW>u<RESET>pdate        3: <YELLOW>r<RESET>evert        4: <YELLOW>a<RESET>dd untracked
 +        5: <YELLOW>p<RESET>atch         6: <YELLOW>d<RESET>iff          7: <YELLOW>q<RESET>uit          8: <YELLOW>h<RESET>elp
 +      <YELLOW>What now<RESET>> <GREEN>status        - show paths with changes<RESET>
 +      <GREEN>update        - add working tree state to the staged set of changes<RESET>
 +      <GREEN>revert        - revert staged set of changes back to the HEAD version<RESET>
 +      <GREEN>patch         - pick hunks and update selectively<RESET>
 +      <GREEN>diff          - view diff between HEAD and index<RESET>
 +      <GREEN>add untracked - add contents of untracked files to the staged set of changes<RESET>
 +      <RED>*** Commands ***<RESET>
 +        1: <YELLOW>s<RESET>tatus        2: <YELLOW>u<RESET>pdate        3: <YELLOW>r<RESET>evert        4: <YELLOW>a<RESET>dd untracked
 +        5: <YELLOW>p<RESET>atch         6: <YELLOW>d<RESET>iff          7: <YELLOW>q<RESET>uit          8: <YELLOW>h<RESET>elp
 +      <YELLOW>What now<RESET>> 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 <input &&
 +      test_decode_color <actual.raw >actual.decoded &&
 +      sed "s/index [0-9a-f]*\\.\\.[0-9a-f]* 100644/<INDEX-LINE>/" <actual.decoded >actual &&
 +      cat >expect <<-\EOF &&
 +      <ITALIC>diff --git a/color-test b/color-test<RESET>
 +      <ITALIC><INDEX-LINE><RESET>
 +      <ITALIC>--- a/color-test<RESET>
 +      <ITALIC>+++ b/color-test<RESET>
 +      <MAGENTA>@@ -1,3 +1,4 @@<RESET>
 +      <CYAN> context<RESET>
 +      <BOLD>-old<RESET>
 +      <BLUE>+<RESET><BLUE>new<RESET>
 +      <CYAN> more-context<RESET>
 +      <BLUE>+<RESET><BLUE>another-one<RESET>
 +      <YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? <RESET><BOLD>Split into 2 hunks.<RESET>
 +      <MAGENTA>@@ -1,3 +1,3 @@<RESET>
 +      <CYAN> context<RESET>
 +      <BOLD>-old<RESET>
 +      <BLUE>+<RESET><BLUE>new<RESET>
 +      <CYAN> more-context<RESET>
 +      <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET>
 +      <CYAN> more-context<RESET>
 +      <BLUE>+<RESET><BLUE>another-one<RESET>
 +      <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET>
 +      <CYAN> context<RESET>
 +      <BOLD>-old<RESET>
 +      <BLUE>+new<RESET>
 +      <CYAN> more-context<RESET>
 +      <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? <RESET>
 +      EOF
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'colorized diffs respect diff.wsErrorHighlight' '
 +      git reset --hard &&
 +
 +      echo "old " >test &&
 +      git add test &&
 +      echo "new " >test &&
 +
 +      printf y >y &&
 +      force_color git -c diff.wsErrorHighlight=all add -p >output.raw 2>&1 <y &&
 +      test_decode_color <output.raw >output &&
 +      grep "old<" output
 +'
 +
 +test_expect_success 'diffFilter filters diff' '
        git reset --hard &&
  
        echo content >test &&
        test_config interactive.diffFilter "sed s/^/foo:/" &&
 -      printf y | test_terminal git add -p >output 2>&1 &&
 +      printf y >y &&
 +      force_color git add -p >output 2>&1 <y &&
  
        # avoid depending on the exact coloring or content of the prompts,
        # and just make sure we saw our diff prefixed
        grep foo:.*content output
  '
  
 -test_expect_success TTY 'detect bogus diffFilter output' '
 +test_expect_success 'detect bogus diffFilter output' '
        git reset --hard &&
  
        echo content >test &&
 -      test_config interactive.diffFilter "echo too-short" &&
 -      printf y | test_must_fail test_terminal git add -p
 +      test_config interactive.diffFilter "sed 1d" &&
 +      printf y >y &&
 +      force_color test_must_fail git add -p <y
 +'
 +
 +test_expect_success 'diff.algorithm is passed to `git diff-files`' '
 +      git reset --hard &&
 +
 +      >file &&
 +      git add file &&
 +      echo changed >file &&
 +      test_must_fail git -c diff.algorithm=bogus add -p 2>err &&
 +      test_i18ngrep "error: option diff-algorithm accepts " err
  '
  
  test_expect_success 'patch-mode via -i prompts for files' '
@@@ -898,12 -600,6 +907,12 @@@ test_expect_success 'setup different ki
        cat >expected <<-\EOF &&
        dirty-both-ways
        dirty-head
 +      EOF
 +      test_cmp expected actual &&
 +      git -C for-submodules diff-files --name-only --ignore-submodules=none >actual &&
 +      cat >expected <<-\EOF &&
 +      dirty-both-ways
 +      dirty-head
        dirty-otherwise
        EOF
        test_cmp expected actual &&
@@@ -955,50 -651,12 +964,50 @@@ test_expect_success 'add -p patch editi
  test_expect_success 'checkout -p works with pathological context lines' '
        test_write_lines a a a a a a >a &&
        git add a &&
 -      test_write_lines a b a b a b a b a b a > a&&
 +      test_write_lines a b a b a b a b a b a >&&
        test_write_lines s n n y q | git checkout -p &&
        test_write_lines a b a b a a b a b a >expect &&
        test_cmp expect a
  '
  
 +# This should be called from a subshell as it sets a temporary editor
 +setup_new_file() {
 +      write_script new-file-editor.sh <<-\EOF &&
 +      sed /^#/d "$1" >patch &&
 +      sed /^+c/d patch >"$1"
 +      EOF
 +      test_set_editor "$(pwd)/new-file-editor.sh" &&
 +      test_write_lines a b c d e f >new-file &&
 +      test_write_lines a b d e f >new-file-expect &&
 +      test_write_lines "@@ -0,0 +1,6 @@" +a +b +c +d +e +f >patch-expect
 +}
 +
 +test_expect_success 'add -N followed by add -p patch editing' '
 +      git reset --hard &&
 +      (
 +              setup_new_file &&
 +              git add -N new-file &&
 +              test_write_lines e n q | git add -p &&
 +              git cat-file blob :new-file >actual &&
 +              test_cmp new-file-expect actual &&
 +              test_cmp patch-expect patch
 +      )
 +'
 +
 +test_expect_success 'checkout -p patch editing of added file' '
 +      git reset --hard &&
 +      (
 +              setup_new_file &&
 +              git add new-file &&
 +              git commit -m "add new file" &&
 +              git rm new-file &&
 +              git commit -m "remove new file" &&
 +              test_write_lines e n q | git checkout -p HEAD^ &&
 +              test_cmp new-file-expect new-file &&
 +              test_cmp patch-expect patch
 +      )
 +'
 +
  test_expect_success 'show help from add--helper' '
        git reset --hard &&
        cat >expect <<-EOF &&
        <BOLD;BLUE>What now<RESET>>$SP
        Bye.
        EOF
 -      test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored &&
 +      test_write_lines h | force_color git add -i >actual.colored &&
        test_decode_color <actual.colored >actual &&
 -      test_i18ncmp expect actual
 +      test_cmp expect actual
  '
  
  test_done