From: Elijah Newren <newren@gmail.com>
To: Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
Cc: git@vger.kernel.org, gitster@pobox.com,
Patrick Steinhardt <ps@pks.im>,
Derrick Stolee <stolee@gmail.com>
Subject: Re: [PATCH v2 8/8] sparse-checkout: make 'clean' clear more files
Date: Tue, 5 Aug 2025 17:21:50 -0700 [thread overview]
Message-ID: <CABPp-BHLcy-A4yLR8gP1Sjt_EKQ4K08kPyb7G6yifdZj+0MJNg@mail.gmail.com> (raw)
In-Reply-To: <82c24ce51980d85e1a53e746b462397e6e6c908a.1752716054.git.gitgitgadget@gmail.com>
On Wed, Jul 16, 2025 at 6:34 PM Derrick Stolee via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: Derrick Stolee <stolee@gmail.com>
>
> The 'git sparse-checkout clean' command is designed to be a one-command
> way to get the worktree in a state such that a sparse index would
> operate efficiently. The previous change demonstrated that files outside
> the sparse-checkout that were committed due to a merge conflict would
> persist despite attempts to run 'git sparse-checkout clean' and instead
> a 'git sparse-checkout reapply' would be required.
>
> Instead of requiring users to run both commands, update 'clean' to be
> more ruthless about tracked sparse directories. The key here is to make
> sure that the SKIP_WORKTREE bit is removed from more paths in the index
> using update_sparsity() before compressing the index to a sparse one
> in-memory.
>
> The tricky part here is that update_sparsity() was previously assuming
> that it would be in 'update' mode and would change the worktree as it
> made changes. However, we do not want to make these worktree changes at
> this point, instead relying on our later logic (that integrates with
> --dry-run and --verbose options) to perform those steps.
>
> One side-effect here is that we also clear out staged files that exist
> in the worktree, but they would also appear in the verbose output as
> part of the dry run.
>
> The final test in t1091 demonstrates that we no longer need the
> 'reapply' subcommand for merge resolutions. It also fixes an earlier
> case where 'git add --sparse' clears the SKIP_WORKTREE bit and avoids a
> directory deletion.
>
> Signed-off-by: Derrick Stolee <stolee@gmail.com>
> ---
> builtin/sparse-checkout.c | 8 ++++++++
> t/t1091-sparse-checkout-builtin.sh | 24 +++++++++++++++++-------
> unpack-trees.c | 2 +-
> 3 files changed, 26 insertions(+), 8 deletions(-)
>
> diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
> index f38a0809c098..1d1d5208a3ba 100644
> --- a/builtin/sparse-checkout.c
> +++ b/builtin/sparse-checkout.c
> @@ -962,6 +962,7 @@ static int sparse_checkout_clean(int argc, const char **argv,
> size_t worktree_len;
> int force = 0, dry_run = 0, verbose = 0;
> int require_force = 1;
> + struct unpack_trees_options o = { 0 };
>
> struct option builtin_sparse_checkout_clean_options[] = {
> OPT__DRY_RUN(&dry_run, N_("dry run")),
> @@ -990,6 +991,13 @@ static int sparse_checkout_clean(int argc, const char **argv,
> if (repo_read_index(repo) < 0)
> die(_("failed to read index"));
>
> + o.verbose_update = verbose;
> + o.update = 0; /* skip modifying the worktree here. */
> + o.head_idx = -1;
> + o.src_index = o.dst_index = repo->index;
> + if (update_sparsity(&o, NULL))
> + warning(_("failed to reapply sparse-checkout patterns"));
> +
> if (convert_to_sparse(repo->index, SPARSE_INDEX_MEMORY_ONLY) ||
> repo->index->sparse_index == INDEX_EXPANDED)
> die(_("failed to convert index to a sparse index; resolve merge conflicts and try again"));
> diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
> index 116ad7c9a20e..4b9078d90a61 100755
> --- a/t/t1091-sparse-checkout-builtin.sh
> +++ b/t/t1091-sparse-checkout-builtin.sh
> @@ -1104,6 +1104,7 @@ test_expect_success 'clean with staged sparse change' '
>
> cat >expect <<-\EOF &&
> Would remove deep/deeper2/
> + Would remove folder1/
> EOF
>
> git -C repo sparse-checkout clean --dry-run >out &&
> @@ -1115,6 +1116,7 @@ test_expect_success 'clean with staged sparse change' '
> # deletes deep/deeper2/ but leaves folder1/ and folder2/
> cat >expect <<-\EOF &&
> Removing deep/deeper2/
> + Removing folder1/
> EOF
>
> # The previous test case checked the -f option, so
> @@ -1124,7 +1126,7 @@ test_expect_success 'clean with staged sparse change' '
> test_cmp expect out &&
>
> test_path_is_missing repo/deep/deeper2 &&
> - test_path_exists repo/folder1 &&
> + test_path_is_missing repo/folder1 &&
> test_path_exists repo/folder2
What this doesn't show is that afterwards:
$ git -C repo status
On branch main
You are in a sparse checkout with 78% of tracked files present.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: folder1/file
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: folder1/file
modified: folder2/a
==> In other words, folder1/file and folder1/ were successfully
removed by this clean command, but when the user runs status, they see
that folder1/file has been deleted (and has a staged change). This is
probably correct behavior and may be expected, but might be surprising
to users at first; it probably deserves to be documented, or at least
covered in the commit message. Especially since it's somewhat
difficult for users to work with:
$ cd repo
$ git checkout HEAD -- folder1/file
error: pathspec 'folder1/file' did not match any file(s) known to git
$ git add -- folder1/file
The following paths and/or pathspecs matched paths that exist
outside of your sparse-checkout definition, so will not be
updated in the index:
folder1/file
hint: If you intend to update such entries, try one of the following:
hint: * Use the --sparse option.
hint: * Disable or modify the sparsity rules.
hint: Disable this message with "git config set advice.updateSparsePath false"
$ git add --sparse -- folder1/file
fatal: pathspec 'folder1/file' did not match any files
$ git reset -- folder1/file
Unstaged changes after reset:
M folder2/a
$
==> So, both checkout and add fail to work with this path while reset
works. `git commit` also would have worked; let's back up to the
point of status above, then get rid of the folder2/a modification, and
then use git commit to commit our folder1/file change:
$ git checkout folder2/a
Updated 1 path from the index
$ git commit -m "A change"
[main aad2d89] A change
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 folder1/file
$ git status
On branch main
You are in a sparse checkout with 78% of tracked files present.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: folder1/file
no changes added to commit (use "git add" and/or "git commit -a")
==> Great, so we got rid of the folder2/a modification, and committed
the staged folder1/file modification. The folder1/file showing as
deleted is annoying but a `sparse-checkout clean` should get rid of it
as well as the pesky folder2/ directory, right?
$ git sparse-checkout clean -f
Removing folder2/
$ git status
On branch main
You are in a sparse checkout with 78% of tracked files present.
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: folder1/file
deleted: folder2/a
no changes added to commit (use "git add" and/or "git commit -a")
==> Huh, `git sparse-checkout clean -f` got rid of folder2, but now
folder2/a shows up as deleted locally...and folder1/file is still
showing as deleted locally. And it doesn't matter how many times we
run `git sparse-checkout clean -f`; this is the end state for it. But
if we run `git sparse-checkout reapply`:
$ git sparse-checkout reapply
$ git status
On branch main
You are in a sparse checkout with 56% of tracked files present.
nothing to commit, working tree clean
==> So, you still need to use `git sparse-checkout reapply` together
with `git sparse-checkout clean -f`; you haven't fixed that yet.
> @@ -1147,7 +1149,11 @@ test_expect_success 'sparse-checkout operations with merge conflicts' '
> git commit -a -m "left" &&
>
> git checkout -b merge &&
> - git sparse-checkout set deep/deeper1 &&
> +
> + touch deep/deeper2/extra &&
> + git sparse-checkout set deep/deeper1 2>err &&
> + grep "contains untracked files" err &&
> + test_path_exists deep/deeper2/extra &&
>
> test_must_fail git merge -m "will-conflict" right &&
>
> @@ -1159,15 +1165,19 @@ test_expect_success 'sparse-checkout operations with merge conflicts' '
> git merge --continue &&
>
> test_path_exists folder1/even/more/dirs/file &&
> + test_path_exists deep/deeper2/extra &&
> +
> + cat >expect <<-\EOF &&
> + Removing deep/deeper2/
> + Removing folder1/
> + EOF
>
> # clean does not remove the file, because the
> # SKIP_WORKTREE bit was not cleared by the merge command.
Shouldn't the comment be updated, given the testcase updates?
> git sparse-checkout clean -f >out &&
> - test_line_count = 0 out &&
> - test_path_exists folder1/even/more/dirs/file &&
> -
> - git sparse-checkout reapply &&
> - test_path_is_missing folder1
> + test_cmp expect out &&
> + test_path_is_missing folder1 &&
> + test_path_is_missing deep/deeper2
Yes, but why does `git status` show folder1/even/more/dirs/file as
being locally deleted? Does the code forget to update the
SKIP_WORKTREE status after clearing out the files?
next prev parent reply other threads:[~2025-08-06 0:22 UTC|newest]
Thread overview: 69+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-08 11:19 [PATCH 0/3] sparse-checkout: add 'clean' command Derrick Stolee via GitGitGadget
2025-07-08 11:19 ` [PATCH 1/3] sparse-checkout: remove use of the_repository Derrick Stolee via GitGitGadget
2025-07-08 20:49 ` Elijah Newren
2025-07-08 20:59 ` Junio C Hamano
2025-07-08 11:19 ` [PATCH 2/3] sparse-checkout: add 'clean' command Derrick Stolee via GitGitGadget
2025-07-08 12:15 ` Patrick Steinhardt
2025-07-08 20:30 ` Junio C Hamano
2025-07-08 21:20 ` Junio C Hamano
2025-07-09 14:39 ` Derrick Stolee
2025-07-09 16:46 ` Junio C Hamano
2025-07-08 21:43 ` Elijah Newren
2025-07-09 16:13 ` Derrick Stolee
2025-07-09 17:35 ` Elijah Newren
2025-07-15 13:38 ` Derrick Stolee
2025-07-15 17:17 ` Elijah Newren
2025-07-08 11:19 ` [PATCH 3/3] sparse-index: point users to new 'clean' action Derrick Stolee via GitGitGadget
2025-07-08 21:45 ` Elijah Newren
2025-07-08 12:15 ` [PATCH 0/3] sparse-checkout: add 'clean' command Patrick Steinhardt
2025-07-08 20:36 ` Elijah Newren
2025-07-08 22:01 ` Elijah Newren
2025-07-08 23:41 ` Junio C Hamano
2025-07-09 15:41 ` Derrick Stolee
2025-07-17 1:34 ` [PATCH v2 0/8] " Derrick Stolee via GitGitGadget
2025-07-17 1:34 ` [PATCH v2 1/8] sparse-checkout: remove use of the_repository Derrick Stolee via GitGitGadget
2025-07-17 1:34 ` [PATCH v2 2/8] sparse-checkout: add basics of 'clean' command Derrick Stolee via GitGitGadget
2025-08-05 21:32 ` Elijah Newren
2025-09-11 13:37 ` Derrick Stolee
2025-07-17 1:34 ` [PATCH v2 3/8] sparse-checkout: match some 'clean' behavior Derrick Stolee via GitGitGadget
2025-08-05 22:06 ` Elijah Newren
2025-09-11 13:52 ` Derrick Stolee
2025-07-17 1:34 ` [PATCH v2 4/8] dir: add generic "walk all files" helper Derrick Stolee via GitGitGadget
2025-08-05 22:22 ` Elijah Newren
2025-07-17 1:34 ` [PATCH v2 5/8] sparse-checkout: add --verbose option to 'clean' Derrick Stolee via GitGitGadget
2025-08-05 22:22 ` Elijah Newren
2025-09-11 14:06 ` Derrick Stolee
2025-07-17 1:34 ` [PATCH v2 6/8] sparse-index: point users to new 'clean' action Derrick Stolee via GitGitGadget
2025-07-17 1:34 ` [PATCH v2 7/8] t: expand tests around sparse merges and clean Derrick Stolee via GitGitGadget
2025-07-17 1:34 ` [PATCH v2 8/8] sparse-checkout: make 'clean' clear more files Derrick Stolee via GitGitGadget
2025-08-06 0:21 ` Elijah Newren [this message]
2025-09-11 15:26 ` Derrick Stolee
2025-09-11 16:21 ` Derrick Stolee
2025-08-28 23:22 ` [PATCH v2 0/8] sparse-checkout: add 'clean' command Junio C Hamano
2025-08-29 0:15 ` Elijah Newren
2025-08-29 0:27 ` Junio C Hamano
2025-08-29 21:03 ` Junio C Hamano
2025-08-30 13:41 ` Derrick Stolee
2025-09-12 10:30 ` [PATCH v3 0/7] " Derrick Stolee via GitGitGadget
2025-09-12 10:30 ` [PATCH v3 1/7] sparse-checkout: remove use of the_repository Derrick Stolee via GitGitGadget
2025-09-12 10:30 ` [PATCH v3 2/7] sparse-checkout: add basics of 'clean' command Derrick Stolee via GitGitGadget
2025-10-07 22:49 ` Elijah Newren
2025-10-20 14:16 ` Derrick Stolee
2025-09-12 10:30 ` [PATCH v3 3/7] sparse-checkout: match some 'clean' behavior Derrick Stolee via GitGitGadget
2025-09-12 10:30 ` [PATCH v3 4/7] dir: add generic "walk all files" helper Derrick Stolee via GitGitGadget
2025-09-12 10:30 ` [PATCH v3 5/7] sparse-checkout: add --verbose option to 'clean' Derrick Stolee via GitGitGadget
2025-09-15 18:09 ` Derrick Stolee
2025-09-15 19:12 ` Junio C Hamano
2025-09-16 2:00 ` Derrick Stolee
2025-09-12 10:30 ` [PATCH v3 6/7] sparse-index: point users to new 'clean' action Derrick Stolee via GitGitGadget
2025-10-07 22:53 ` Elijah Newren
2025-10-20 14:17 ` Derrick Stolee
2025-09-12 10:30 ` [PATCH v3 7/7] t: expand tests around sparse merges and clean Derrick Stolee via GitGitGadget
2025-09-12 16:12 ` [PATCH v3 0/7] sparse-checkout: add 'clean' command Junio C Hamano
2025-09-26 13:40 ` Derrick Stolee
2025-09-26 18:58 ` Elijah Newren
2025-10-07 23:07 ` Elijah Newren
2025-10-20 14:25 ` Derrick Stolee
2025-10-20 14:24 ` [PATCH 8/8] sparse-index: improve advice message instructions Derrick Stolee
2025-10-20 16:29 ` Junio C Hamano
2025-10-24 2:22 ` Elijah Newren
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CABPp-BHLcy-A4yLR8gP1Sjt_EKQ4K08kPyb7G6yifdZj+0MJNg@mail.gmail.com \
--to=newren@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitgitgadget@gmail.com \
--cc=gitster@pobox.com \
--cc=ps@pks.im \
--cc=stolee@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).