From 163b42dfa21c306dc1dc573c5edfc8bda5c99fd0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Sep 2019 22:56:02 +0200 Subject: [PATCH 1/2] update-index: optionally leave skip-worktree entries alone While `git update-index` mostly ignores paths referring to index entries whose skip-worktree bit is set, in b4d1690df11 (Teach Git to respect skip-worktree bit (reading part), 2009-08-20), for reasons that are not entirely obvious, the `--remove` option was made special: it _does_ remove index entries even if their skip-worktree bit is set. Seeing as this behavior has been in place for a decade now, it does not make sense to change it. However, in preparation for fixing a bug in `git stash` where it pretends that skip-worktree entries have actually been removed, we need a mode where `git update-index` leaves all skip-worktree entries alone, even if the `--remove` option was passed. Signed-off-by: Johannes Schindelin --- Documentation/git-update-index.txt | 6 ++++++ builtin/update-index.c | 6 +++++- t/t7012-skip-worktree-writing.sh | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 1c4d146a41ce09..08393445e754d9 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -16,6 +16,7 @@ SYNOPSIS [--chmod=(+|-)x] [--[no-]assume-unchanged] [--[no-]skip-worktree] + [--[no-]ignore-skip-worktree-entries] [--[no-]fsmonitor-valid] [--ignore-submodules] [--[no-]split-index] @@ -113,6 +114,11 @@ you will need to handle the situation manually. set and unset the "skip-worktree" bit for the paths. See section "Skip-worktree bit" below for more information. + +--[no-]ignore-skip-worktree-entries:: + Do not remove skip-worktree (AKA "index-only") entries even when + the `--remove` option was specified. + --[no-]fsmonitor-valid:: When one of these flags is specified, the object name recorded for the paths are not updated. Instead, these options diff --git a/builtin/update-index.c b/builtin/update-index.c index dff2f4b837208d..074d563df08570 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -35,6 +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 @@ static int process_path(const char *path, struct stat *st, int stat_errno) * 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; } @@ -1013,6 +1015,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) {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, diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index 9d1abe50eff677..7476781979c3ff 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -134,6 +134,21 @@ test_expect_success 'git-clean, dirty case' ' test_i18ncmp expected result ' +test_expect_success '--ignore-skip-worktree-entries leaves worktree alone' ' + test_commit keep-me && + git update-index --skip-worktree keep-me.t && + rm keep-me.t && + + : ignoring the worktree && + git update-index --remove --ignore-skip-worktree-entries keep-me.t && + git diff-index --cached --exit-code HEAD && + + : not ignoring the worktree, a deletion is staged && + git update-index --remove keep-me.t && + test_must_fail git diff-index --cached --exit-code HEAD \ + --diff-filter=D -- keep-me.t +' + #TODO test_expect_failure 'git-apply adds file' false #TODO test_expect_failure 'git-apply updates file' false #TODO test_expect_failure 'git-apply removes file' false From 8f49a393e0ef7fff955a02963ff5229fb9885e89 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Sep 2019 22:16:56 +0200 Subject: [PATCH 2/2] stash: handle staged changes in skip-worktree files correctly When calling `git stash` while changes were staged for files that are marked with the `skip-worktree` bit (e.g. files that are excluded in a sparse checkout), the files are recorded as _deleted_ instead. The reason is that `git stash` tries to construct the tree reflecting the worktree essentially by copying the index to a temporary one and then updating the files from the worktree. Crucially, it calls `git diff-index` to update also those files that are in the HEAD but have been unstaged in the index. However, when the temporary index is updated via `git update-index --add --remove`, skip-worktree entries mark the files as deleted by mistake. Let's use the newly-introduced `--ignore-skip-worktree-entries` option of `git update-index` to prevent exactly this from happening. Note that the regression test case deliberately avoids replicating the scenario described above and instead tries to recreate just the symptom. Reported by Dan Thompson. Signed-off-by: Johannes Schindelin --- builtin/stash.c | 5 +++-- git-legacy-stash.sh | 3 ++- t/t3903-stash.sh | 11 +++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index ab30d1e9203b42..e3962bf73e00d2 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1082,8 +1082,9 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps } 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); diff --git a/git-legacy-stash.sh b/git-legacy-stash.sh index f60e9b3e877b24..5398a5161d253e 100755 --- a/git-legacy-stash.sh +++ b/git-legacy-stash.sh @@ -193,7 +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" ) ) || diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 392954d6dd9243..57258d56687e15 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1257,4 +1257,15 @@ test_expect_success 'stash apply should succeed with unmodified 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