From 7dc72f0237e592852efc2b6b780a65d65bd2e8c1 Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Tue, 9 Sep 2025 07:53:06 -0700 Subject: [PATCH 1/2] Fix `restore --staged` for certain vfs ops When using virtualfilesystem, Running `git restore --staged` after `git cherry-pick -n` or `git reset --soft` would result in an incorrect state - the modified files would still have their modified contents, but git would no longer recognize them as being modified; and the added files would be deleted from disk instead of just unstaged. See https://github.com/microsoft/VFSForGit/issues/1855 for more details. This commit fixes these issues with two changes: 1) Add a new flag to the index state structure, `clear_skip_worktree_for_added_entries`. This flag is set during cherry-pick -n before calculating the new index state, and causes newly added entries to have their SKIP_WORKTREE bit cleared. This ensures that newly added files are added to both the index and the worktree in the next step. Otherwise, sparse-checkout would prevent them from being added to the worktree. 3) Set the existing flag updated_skipworktree on the index when running a checkout index (aka `restore --staged`) operation. These operations already will clear SKIP_WORKTREE bits for modified files, but without this flag set the virtualfilesystem hook notification does not indicate that they changed, causing the VFS to not update its state correctly. --- builtin/checkout.c | 7 +++++++ read-cache-ll.h | 3 ++- sequencer.c | 11 ++++++++++- unpack-trees.c | 21 +++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index 5109573aed32e9..7ab212d08167ce 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -652,6 +652,13 @@ static int checkout_paths(const struct checkout_opts *opts, checkout_index = opts->checkout_index; if (checkout_index) { + /* Some scenarios that checkout the index may update skipworktree bits, + * such as `restore --staged` after `cherry-pick -n` or `reset --soft`, + * so this flag should be set to ensure the correct virtual filesystem + * event is sent. + */ + the_repository->index->updated_skipworktree = 1; + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); } else { diff --git a/read-cache-ll.h b/read-cache-ll.h index 84092540a7830b..b37e57d0b3487d 100644 --- a/read-cache-ll.h +++ b/read-cache-ll.h @@ -176,7 +176,8 @@ struct index_state { drop_cache_tree : 1, updated_workdir : 1, updated_skipworktree : 1, - fsmonitor_has_run_once : 1; + fsmonitor_has_run_once : 1, + clear_skip_worktree_for_added_entries : 1; enum sparse_index_mode sparse_index; struct hashmap name_hash; struct hashmap dir_hash; diff --git a/sequencer.c b/sequencer.c index f54ad1b6a8be17..d216a2a3f2f436 100644 --- a/sequencer.c +++ b/sequencer.c @@ -787,13 +787,22 @@ static int do_recursive_merge(struct repository *r, * to be replace with the tree the index matched before we * started doing any picks. */ + if (opts->no_commit && core_virtualfilesystem) { + /* When using the virtual file system, staged new files + * should clear SKIP_WORKTREE during this step to ensure the new files + * are properly added to the working tree as well as index - otherwise + * sparse-checkout functionality will prevent them from being added. + */ + o.repo->index->clear_skip_worktree_for_added_entries = 1; + } merge_switch_to_result(&o, head_tree, &result, 1, show_output); + o.repo->index->clear_skip_worktree_for_added_entries = 0; + clean = result.clean; if (clean < 0) { rollback_lock_file(&index_lock); return clean; } - if (write_locked_index(r->index, &index_lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) /* diff --git a/unpack-trees.c b/unpack-trees.c index 399d0c3e61789a..01614d28343454 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1852,6 +1852,25 @@ static void mark_new_skip_worktree(struct pattern_list *pl, enable_fscache(istate->cache_nr); clear_ce_flags(istate, select_flag, skip_wt_flag, pl, show_progress); disable_fscache(); + + /* + * 3. If clear_skip_worktree_for_added_entries is set and we are checking + * for added entries, clear skip_wt_flag from all added entries. This is + * used when running with virtualfilesystem to ensure that added entries are + * also checked out in the working tree - otherwise skip_wt_flag will + * prevent that. + */ + if ((select_flag & CE_ADDED) + && istate->clear_skip_worktree_for_added_entries) { + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if ((ce->ce_flags & (CE_ADDED | skip_wt_flag)) + == (CE_ADDED | skip_wt_flag)) { + ce->ce_flags &= ~skip_wt_flag; + istate->updated_skipworktree = 1; + } + } + } } static void populate_from_existing_patterns(struct unpack_trees_options *o, @@ -2004,6 +2023,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options is_sparse_index_allowed(&o->internal.result, 0)) o->internal.result.sparse_index = 1; + o->internal.result.clear_skip_worktree_for_added_entries = + o->src_index->clear_skip_worktree_for_added_entries; /* * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries */ From e9af392a09474382fdca9d619fdd43f370750a7a Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Thu, 9 Oct 2025 12:47:01 -0700 Subject: [PATCH 2/2] Fix failing test t7113 due to expected change The failing test `t7113` is due to the change in `builtin/checkout.c` that now sets the `updated_skipworktree` flag. The test checked that this flag was not set, which is no longer valid. --- t/t7113-post-index-change-hook.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/t/t7113-post-index-change-hook.sh b/t/t7113-post-index-change-hook.sh index 22b81a67cc32b6..497f4fe55ebb8c 100755 --- a/t/t7113-post-index-change-hook.sh +++ b/t/t7113-post-index-change-hook.sh @@ -79,9 +79,6 @@ test_expect_success 'test status, add, commit, others trigger hook without flags git commit -m "second" && test_path_is_file testsuccess && rm -f testsuccess && test_path_is_missing testfailure && - git checkout -- dir1/file1.txt && - test_path_is_file testsuccess && rm -f testsuccess && - test_path_is_missing testfailure && git update-index && test_path_is_missing testsuccess && test_path_is_missing testfailure &&