Skip to content

Commit f7925d5

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 632801a commit f7925d5

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
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: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -939,9 +939,13 @@ static int sparse_checkout_clean(int argc, const char **argv,
939939
{
940940
struct strbuf full_path = STRBUF_INIT;
941941
size_t worktree_len;
942-
int force = -1;
942+
int force = 0;
943+
int dry_run = 0;
944+
int require_force = 1;
943945

944946
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),
945949
OPT_END(),
946950
};
947951

@@ -955,6 +959,10 @@ static int sparse_checkout_clean(int argc, const char **argv,
955959
builtin_sparse_checkout_clean_options,
956960
builtin_sparse_checkout_clean_usage, 0);
957961

962+
repo_config_get_bool(repo, "clean.requireforce", &require_force);
963+
if (require_force && !force && !dry_run)
964+
die(_("clean.requireForce is true and -f not given: refusing to clean"));
965+
958966
if (repo_read_index(repo) < 0)
959967
die(_("failed to read index"));
960968

@@ -976,7 +984,7 @@ static int sparse_checkout_clean(int argc, const char **argv,
976984
if (!is_directory(full_path.buf))
977985
continue;
978986

979-
if (force) {
987+
if (dry_run <= 0) {
980988
printf(msg_remove, ce->name);
981989
if (remove_dir_recursively(&full_path, 0))
982990
warning_errno(_("failed to remove '%s'"), ce->name);

t/t1091-sparse-checkout-builtin.sh

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,12 +1056,25 @@ 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+
cat >expect <<-\EOF &&
1063+
Would remove deep/deeper2/
1064+
Would remove folder1/
1065+
EOF
1066+
1067+
git -C repo sparse-checkout clean --dry-run >out &&
1068+
test_cmp expect out &&
1069+
test_path_exists repo/deep/deeper2 &&
1070+
test_path_exists repo/folder1 &&
1071+
10591072
cat >expect <<-\EOF &&
10601073
Removing deep/deeper2/
10611074
Removing folder1/
10621075
EOF
10631076
1064-
git -C repo sparse-checkout clean >out &&
1077+
git -C repo sparse-checkout clean -f >out &&
10651078
test_cmp expect out &&
10661079
10671080
test_path_is_missing repo/deep/deeper2 &&
@@ -1077,16 +1090,27 @@ test_expect_success 'clean with staged sparse change' '
10771090
10781091
git -C repo add --sparse folder1/file &&
10791092
1093+
cat >expect <<-\EOF &&
1094+
Would remove deep/deeper2/
1095+
EOF
1096+
1097+
git -C repo sparse-checkout clean --dry-run >out &&
1098+
test_cmp expect out &&
1099+
test_path_exists repo/deep/deeper2 &&
1100+
test_path_exists repo/folder1 &&
1101+
test_path_exists repo/folder2 &&
1102+
10801103
# deletes deep/deeper2/ but leaves folder1/ and folder2/
10811104
cat >expect <<-\EOF &&
10821105
Removing deep/deeper2/
10831106
EOF
10841107
1085-
git -C repo sparse-checkout clean >out &&
1108+
git -C repo sparse-checkout clean -f >out &&
10861109
test_cmp expect out &&
10871110
10881111
test_path_is_missing repo/deep/deeper2 &&
1089-
test_path_exists repo/folder1
1112+
test_path_exists repo/folder1 &&
1113+
test_path_exists repo/folder2
10901114
'
10911115

10921116
test_expect_success 'clean with merge conflict status' '
@@ -1103,7 +1127,7 @@ test_expect_success 'clean with merge conflict status' '
11031127
11041128
git -C clean-merge sparse-checkout set deep/deeper1 &&
11051129
1106-
test_must_fail git -C clean-merge sparse-checkout clean 2>err &&
1130+
test_must_fail git -C clean-merge sparse-checkout clean -f 2>err &&
11071131
grep "failed to convert index to a sparse index" err &&
11081132
11091133
mkdir -p clean-merge/folder1/ &&
@@ -1116,7 +1140,7 @@ test_expect_success 'clean with merge conflict status' '
11161140
Removing folder2/
11171141
EOF
11181142
1119-
git -C clean-merge sparse-checkout clean >out &&
1143+
git -C clean-merge sparse-checkout clean -f >out &&
11201144
test_cmp expect out
11211145
'
11221146

0 commit comments

Comments
 (0)