Skip to content

Commit 99f9681

Browse files
committed
sparse-checkout: match some 'clean' behavior
The 'git sparse-checkout clean' subcommand is somewhat similar to 'git clean' in that it will delete files that should not be in the worktree. The big difference is that it focuses on the directories that should not be in the worktree due to cone-mode sparse-checkout. It also does not discriminate in the kinds of files and focuses on deleting entire directories. However, there are some restrictions that would be good to bring over from 'git clean', specifically how it refuses to do anything without the '-f'/'--force' or '-n'/'--dry-run' arguments. The 'clean.requireForce' config can be set to 'false' to imply '--force'. Add this behavior to avoid accidental deletion of files that cannot be recovered from Git. Signed-off-by: Derrick Stolee <[email protected]>
1 parent ab99f92 commit 99f9681

File tree

3 files changed

+58
-5
lines changed

3 files changed

+58
-5
lines changed

Documentation/git-sparse-checkout.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ all sparsity paths.
119119
This command can be used to be sure the sparse index works
120120
efficiently, though it does not require enabling the sparse index
121121
feature via the `index.sparse=true` configuration.
122+
+
123+
To prevent accidental deletion of worktree files, the `clean` subcommand
124+
will not delete any files without the `-f` or `--force` option, unless
125+
the `clean.requireForce` config option is set to `false`.
126+
+
127+
The `--dry-run` option will list the directories that would be removed
128+
without deleting them. Running in this mode can be helpful to predict the
129+
behavior of the clean comand or to determine which kinds of files are left
130+
in the sparse directories.
122131

123132
'disable'::
124133
Disable the `core.sparseCheckout` config setting, and restore the

builtin/sparse-checkout.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,7 @@ static char const * const builtin_sparse_checkout_clean_usage[] = {
931931
};
932932

933933
static const char *msg_remove = N_("Removing %s\n");
934+
static const char *msg_would_remove = N_("Would remove %s\n");
934935

935936
static int sparse_checkout_clean(int argc, const char **argv,
936937
const char *prefix,
@@ -939,8 +940,12 @@ static int sparse_checkout_clean(int argc, const char **argv,
939940
struct strbuf full_path = STRBUF_INIT;
940941
const char *msg = msg_remove;
941942
size_t worktree_len;
943+
int force = 0, dry_run = 0;
944+
int require_force = 1;
942945

943946
struct option builtin_sparse_checkout_clean_options[] = {
947+
OPT__DRY_RUN(&dry_run, N_("dry run")),
948+
OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE),
944949
OPT_END(),
945950
};
946951

@@ -954,6 +959,13 @@ static int sparse_checkout_clean(int argc, const char **argv,
954959
builtin_sparse_checkout_clean_options,
955960
builtin_sparse_checkout_clean_usage, 0);
956961

962+
repo_config_get_bool(repo, "clean.requireforce", &require_force);
963+
if (require_force && !force && !dry_run)
964+
die(_("for safety, refusing to clean without one of --force or --dry-run"));
965+
966+
if (dry_run)
967+
msg = msg_would_remove;
968+
957969
if (repo_read_index(repo) < 0)
958970
die(_("failed to read index"));
959971

@@ -977,7 +989,8 @@ static int sparse_checkout_clean(int argc, const char **argv,
977989

978990
printf(msg, ce->name);
979991

980-
if (remove_dir_recursively(&full_path, 0))
992+
if (dry_run <= 0 &&
993+
remove_dir_recursively(&full_path, 0))
981994
warning_errno(_("failed to remove '%s'"), ce->name);
982995
}
983996

t/t1091-sparse-checkout-builtin.sh

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,12 +1056,29 @@ test_expect_success 'clean' '
10561056
touch repo/deep/deeper2/file &&
10571057
touch repo/folder1/file &&
10581058
1059+
test_must_fail git -C repo sparse-checkout clean 2>err &&
1060+
grep "refusing to clean" err &&
1061+
1062+
git -C repo config clean.requireForce true &&
1063+
test_must_fail git -C repo sparse-checkout clean 2>err &&
1064+
grep "refusing to clean" err &&
1065+
1066+
cat >expect <<-\EOF &&
1067+
Would remove deep/deeper2/
1068+
Would remove folder1/
1069+
EOF
1070+
1071+
git -C repo sparse-checkout clean --dry-run >out &&
1072+
test_cmp expect out &&
1073+
test_path_exists repo/deep/deeper2 &&
1074+
test_path_exists repo/folder1 &&
1075+
10591076
cat >expect <<-\EOF &&
10601077
Removing deep/deeper2/
10611078
Removing folder1/
10621079
EOF
10631080
1064-
git -C repo sparse-checkout clean >out &&
1081+
git -C repo sparse-checkout clean -f >out &&
10651082
test_cmp expect out &&
10661083
10671084
test_path_is_missing repo/deep/deeper2 &&
@@ -1077,16 +1094,30 @@ test_expect_success 'clean with staged sparse change' '
10771094
10781095
git -C repo add --sparse folder1/file &&
10791096
1097+
cat >expect <<-\EOF &&
1098+
Would remove deep/deeper2/
1099+
EOF
1100+
1101+
git -C repo sparse-checkout clean --dry-run >out &&
1102+
test_cmp expect out &&
1103+
test_path_exists repo/deep/deeper2 &&
1104+
test_path_exists repo/folder1 &&
1105+
test_path_exists repo/folder2 &&
1106+
10801107
# deletes deep/deeper2/ but leaves folder1/ and folder2/
10811108
cat >expect <<-\EOF &&
10821109
Removing deep/deeper2/
10831110
EOF
10841111
1112+
# The previous test case checked the -f option, so
1113+
# test the config option in this one.
1114+
git -C repo config clean.requireForce false &&
10851115
git -C repo sparse-checkout clean >out &&
10861116
test_cmp expect out &&
10871117
10881118
test_path_is_missing repo/deep/deeper2 &&
1089-
test_path_exists repo/folder1
1119+
test_path_exists repo/folder1 &&
1120+
test_path_exists repo/folder2
10901121
'
10911122

10921123
test_expect_success 'clean with merge conflict status' '
@@ -1103,7 +1134,7 @@ test_expect_success 'clean with merge conflict status' '
11031134
11041135
git -C clean-merge sparse-checkout set deep/deeper1 &&
11051136
1106-
test_must_fail git -C clean-merge sparse-checkout clean 2>err &&
1137+
test_must_fail git -C clean-merge sparse-checkout clean -f 2>err &&
11071138
grep "failed to convert index to a sparse index" err &&
11081139
11091140
mkdir -p clean-merge/folder1/ &&
@@ -1116,7 +1147,7 @@ test_expect_success 'clean with merge conflict status' '
11161147
Removing folder2/
11171148
EOF
11181149
1119-
git -C clean-merge sparse-checkout clean >out &&
1150+
git -C clean-merge sparse-checkout clean -f >out &&
11201151
test_cmp expect out
11211152
'
11221153

0 commit comments

Comments
 (0)