Merge branch 'js/update-index-ignore-removal-for-skip-worktree'
authorJunio C Hamano <gitster@pobox.com>
Sun, 10 Nov 2019 09:02:16 +0000 (18:02 +0900)
committerJunio C Hamano <gitster@pobox.com>
Sun, 10 Nov 2019 09:02:16 +0000 (18:02 +0900)
"git stash save" in a working tree that is sparsely checked out
mistakenly removed paths that are outside the area of interest.

* js/update-index-ignore-removal-for-skip-worktree:
  stash: handle staged changes in skip-worktree files correctly
  update-index: optionally leave skip-worktree entries alone

1  2 
builtin/stash.c
builtin/update-index.c
git-legacy-stash.sh
t/t3903-stash.sh

diff --combined builtin/stash.c
@@@ -396,7 -396,7 +396,7 @@@ static int do_apply_stash(const char *p
        const struct object_id *bases[1];
  
        read_cache_preload(NULL);
 -      if (refresh_cache(REFRESH_QUIET))
 +      if (refresh_and_write_cache(REFRESH_QUIET, 0, 0))
                return -1;
  
        if (write_cache_as_tree(&c_tree, 0, NULL))
                                return error(_("could not save index tree"));
  
                        reset_head();
 +                      discard_cache();
 +                      read_cache();
                }
        }
  
        }
  
        if (quiet) {
 -              if (refresh_cache(REFRESH_QUIET))
 +              if (refresh_and_write_cache(REFRESH_QUIET, 0, 0))
                        warning("could not refresh index");
        } else {
                struct child_process cp = CHILD_PROCESS_INIT;
                 */
                cp.git_cmd = 1;
                cp.dir = prefix;
 +              argv_array_pushf(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT"=%s",
 +                               absolute_path(get_git_work_tree()));
 +              argv_array_pushf(&cp.env_array, GIT_DIR_ENVIRONMENT"=%s",
 +                               absolute_path(get_git_dir()));
                argv_array_push(&cp.args, "status");
                run_command(&cp);
        }
@@@ -1088,8 -1082,9 +1088,9 @@@ static int stash_working_tree(struct st
        }
  
        cp_upd_index.git_cmd = 1;
-       argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
-                        "--remove", "--stdin", NULL);
+       argv_array_pushl(&cp_upd_index.args, "update-index",
+                        "--ignore-skip-worktree-entries",
+                        "-z", "--add", "--remove", "--stdin", NULL);
        argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
                         stash_index_path.buf);
  
@@@ -1135,10 -1130,7 +1136,10 @@@ static int do_create_stash(const struc
        prepare_fallback_ident("git stash", "git@stash");
  
        read_cache_preload(NULL);
 -      refresh_cache(REFRESH_QUIET);
 +      if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0) {
 +              ret = -1;
 +              goto done;
 +      }
  
        if (get_oid("HEAD", &info->b_commit)) {
                if (!quiet)
@@@ -1299,7 -1291,7 +1300,7 @@@ static int do_push_stash(const struct p
                free(ps_matched);
        }
  
 -      if (refresh_cache(REFRESH_QUIET)) {
 +      if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) {
                ret = -1;
                goto done;
        }
                        struct child_process cp = CHILD_PROCESS_INIT;
                        cp.git_cmd = 1;
                        argv_array_pushl(&cp.args, "reset", "--hard", "-q",
 -                                       NULL);
 +                                       "--no-recurse-submodules", NULL);
                        if (run_command(&cp)) {
                                ret = -1;
                                goto done;
diff --combined builtin/update-index.c
@@@ -35,6 -35,7 +35,7 @@@ static int verbose
  static int mark_valid_only;
  static int mark_skip_worktree_only;
  static int mark_fsmonitor_only;
+ static int ignore_skip_worktree_entries;
  #define MARK_FLAG 1
  #define UNMARK_FLAG 2
  static struct strbuf mtime_dir = STRBUF_INIT;
@@@ -381,7 -382,8 +382,8 @@@ static int process_path(const char *pat
                 * so updating it does not make sense.
                 * On the other hand, removing it from index should work
                 */
-               if (allow_remove && remove_file_from_cache(path))
+               if (!ignore_skip_worktree_entries && allow_remove &&
+                   remove_file_from_cache(path))
                        return error("%s: cannot remove from the index", path);
                return 0;
        }
@@@ -966,7 -968,6 +968,7 @@@ int cmd_update_index(int argc, const ch
        struct parse_opt_ctx_t ctx;
        strbuf_getline_fn getline_fn;
        int parseopt_state = PARSE_OPT_UNKNOWN;
 +      struct repository *r = the_repository;
        struct option options[] = {
                OPT_BIT('q', NULL, &refresh_args.flags,
                        N_("continue refresh even when index needs update"),
                {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL,
                        N_("clear skip-worktree bit"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
+               OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries,
+                        N_("do not touch index-only entries")),
                OPT_SET_INT(0, "info-only", &info_only,
                        N_("add to index only; do not add content to object database"), 1),
                OPT_SET_INT(0, "force-remove", &force_remove,
                remove_split_index(&the_index);
        }
  
 +      prepare_repo_settings(r);
        switch (untracked_cache) {
        case UC_UNSPECIFIED:
                break;
        case UC_DISABLE:
 -              if (git_config_get_untracked_cache() == 1)
 +              if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE)
                        warning(_("core.untrackedCache is set to true; "
                                  "remove or change it, if you really want to "
                                  "disable the untracked cache"));
                return !test_if_untracked_cache_is_supported();
        case UC_ENABLE:
        case UC_FORCE:
 -              if (git_config_get_untracked_cache() == 0)
 +              if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE)
                        warning(_("core.untrackedCache is set to false; "
                                  "remove or change it, if you really want to "
                                  "enable the untracked cache"));
diff --combined git-legacy-stash.sh
@@@ -193,7 -193,8 +193,8 @@@ create_stash () 
                        GIT_INDEX_FILE="$TMPindex" &&
                        export GIT_INDEX_FILE &&
                        git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" &&
-                       git update-index -z --add --remove --stdin <"$TMP-stagenames" &&
+                       git update-index --ignore-skip-worktree-entries \
+                               -z --add --remove --stdin <"$TMP-stagenames" &&
                        git write-tree &&
                        rm -f "$TMPindex"
                ) ) ||
@@@ -370,7 -371,7 +371,7 @@@ push_stash () 
                        git diff-index -p --cached --binary HEAD -- "$@" |
                        git apply --index -R
                else
 -                      git reset --hard -q
 +                      git reset --hard -q --no-recurse-submodules
                fi
  
                if test "$keep_index" = "t" && test -n "$i_tree"
diff --combined t/t3903-stash.sh
@@@ -7,18 -7,6 +7,18 @@@ test_description='Test git stash
  
  . ./test-lib.sh
  
 +diff_cmp () {
 +      for i in "$1" "$2"
 +      do
 +              sed -e 's/^index 0000000\.\.[0-9a-f]*/index 0000000..1234567/' \
 +              -e 's/^index [0-9a-f]*\.\.[0-9a-f]*/index 1234567..89abcde/' \
 +              -e 's/^index [0-9a-f]*,[0-9a-f]*\.\.[0-9a-f]*/index 1234567,7654321..89abcde/' \
 +              "$i" >"$i.compare" || return 1
 +      done &&
 +      test_cmp "$1.compare" "$2.compare" &&
 +      rm -f "$1.compare" "$2.compare"
 +}
 +
  test_expect_success 'stash some dirty working directory' '
        echo 1 >file &&
        git add file &&
@@@ -48,7 -36,7 +48,7 @@@ EO
  test_expect_success 'parents of stash' '
        test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
        git diff stash^2..stash >output &&
 -      test_cmp expect output
 +      diff_cmp expect output
  '
  
  test_expect_success 'applying bogus stash does nothing' '
@@@ -222,13 -210,13 +222,13 @@@ test_expect_success 'stash branch' 
        test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
        test $(git rev-parse HEAD) = $(git rev-parse master^) &&
        git diff --cached >output &&
 -      test_cmp expect output &&
 +      diff_cmp expect output &&
        git diff >output &&
 -      test_cmp expect1 output &&
 +      diff_cmp expect1 output &&
        git add file &&
        git commit -m alternate\ second &&
        git diff master..stashbranch >output &&
 -      test_cmp output expect2 &&
 +      diff_cmp output expect2 &&
        test 0 = $(git stash list | wc -l)
  '
  
@@@ -589,7 -577,7 +589,7 @@@ test_expect_success 'stash show -p - st
        +bar
        EOF
        git stash show -p ${STASH_ID} >actual &&
 -      test_cmp expected actual
 +      diff_cmp expected actual
  '
  
  test_expect_success 'stash show - no stashes on stack, stash-like argument' '
@@@ -621,7 -609,7 +621,7 @@@ test_expect_success 'stash show -p - n
        +foo
        EOF
        git stash show -p ${STASH_ID} >actual &&
 -      test_cmp expected actual
 +      diff_cmp expected actual
  '
  
  test_expect_success 'stash show --patience shows diff' '
        +foo
        EOF
        git stash show --patience ${STASH_ID} >actual &&
 -      test_cmp expected actual
 +      diff_cmp expected actual
  '
  
  test_expect_success 'drop: fail early if specified stash is not a stash ref' '
@@@ -803,7 -791,7 +803,7 @@@ test_expect_success 'stash where workin
        git diff-index --cached --quiet HEAD &&
        test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
        git diff stash^..stash >output &&
 -      test_cmp expect output
 +      diff_cmp expect output
  '
  
  test_expect_success 'store called with invalid commit' '
@@@ -859,7 -847,7 +859,7 @@@ test_expect_success 'stash list implie
        +working
        EOF
        git stash list --format=%gd -p >actual &&
 -      test_cmp expect actual
 +      diff_cmp expect actual
  '
  
  test_expect_success 'stash list --cc shows combined diff' '
        ++working
        EOF
        git stash list --format=%gd -p --cc >actual &&
 -      test_cmp expect actual
 +      diff_cmp expect actual
  '
  
  test_expect_success 'stash is not confused by partial renames' '
@@@ -1253,20 -1241,15 +1253,31 @@@ test_expect_success 'stash --keep-inde
        test_path_is_missing to-remove
  '
  
 +test_expect_success 'stash apply should succeed with unmodified file' '
 +      echo base >file &&
 +      git add file &&
 +      git commit -m base &&
 +
 +      # now stash a modification
 +      echo modified >file &&
 +      git stash &&
 +
 +      # make the file stat dirty
 +      cp file other &&
 +      mv other file &&
 +
 +      git stash apply
 +'
 +
+ test_expect_success 'stash handles skip-worktree entries nicely' '
+       test_commit A &&
+       echo changed >A.t &&
+       git add A.t &&
+       git update-index --skip-worktree A.t &&
+       rm A.t &&
+       git stash &&
+       git rev-parse --verify refs/stash:A.t
+ '
  test_done