Skip to content

Commit a8077c1

Browse files
derrickstoleegitster
authored andcommitted
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]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2520efd commit a8077c1

File tree

3 files changed

+76
-2
lines changed

3 files changed

+76
-2
lines changed

Documentation/git-sparse-checkout.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ clean` to resolve these cases.
127127
This command can be used to be sure the sparse index works efficiently,
128128
though it does not require enabling the sparse index feature via the
129129
`index.sparse=true` configuration.
130+
+
131+
To prevent accidental deletion of worktree files, the `clean` subcommand
132+
will not delete any files without the `-f` or `--force` option, unless
133+
the `clean.requireForce` config option is set to `false`.
134+
+
135+
The `--dry-run` option will list the directories that would be removed
136+
without deleting them. Running in this mode can be helpful to predict the
137+
behavior of the clean comand or to determine which kinds of files are left
138+
in the sparse directories.
130139

131140
'disable'::
132141
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: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1059,12 +1059,29 @@ test_expect_success 'clean' '
10591059
touch repo/deep/deeper2/file &&
10601060
touch repo/folder1/file &&
10611061
1062+
test_must_fail git -C repo sparse-checkout clean 2>err &&
1063+
grep "refusing to clean" err &&
1064+
1065+
git -C repo config clean.requireForce true &&
1066+
test_must_fail git -C repo sparse-checkout clean 2>err &&
1067+
grep "refusing to clean" err &&
1068+
1069+
cat >expect <<-\EOF &&
1070+
Would remove deep/deeper2/
1071+
Would remove folder1/
1072+
EOF
1073+
1074+
git -C repo sparse-checkout clean --dry-run >out &&
1075+
test_cmp expect out &&
1076+
test_path_exists repo/deep/deeper2 &&
1077+
test_path_exists repo/folder1 &&
1078+
10621079
cat >expect <<-\EOF &&
10631080
Removing deep/deeper2/
10641081
Removing folder1/
10651082
EOF
10661083
1067-
git -C repo sparse-checkout clean >out &&
1084+
git -C repo sparse-checkout clean -f >out &&
10681085
test_cmp expect out &&
10691086
10701087
test_path_is_missing repo/deep/deeper2 &&
@@ -1076,6 +1093,10 @@ test_expect_success 'clean with sparse file states' '
10761093
git -C repo sparse-checkout set --cone deep/deeper1 &&
10771094
mkdir repo/folder2 &&
10781095
1096+
# The previous test case checked the -f option, so
1097+
# test the config option in this one.
1098+
git -C repo config clean.requireForce false &&
1099+
10791100
# create an untracked file and a modified file
10801101
touch repo/folder2/file &&
10811102
echo dirty >repo/folder2/a &&
@@ -1154,4 +1175,35 @@ test_expect_success 'clean with sparse file states' '
11541175
test_must_be_empty out
11551176
'
11561177

1178+
test_expect_success 'clean with merge conflict status' '
1179+
git clone repo clean-merge &&
1180+
1181+
echo dirty >clean-merge/deep/deeper2/a &&
1182+
touch clean-merge/folder2/extra &&
1183+
1184+
cat >input <<-EOF &&
1185+
0 $ZERO_OID folder1/a
1186+
100644 $(git -C clean-merge rev-parse HEAD:folder1/a) 1 folder1/a
1187+
EOF
1188+
git -C clean-merge update-index --index-info <input &&
1189+
1190+
git -C clean-merge sparse-checkout set deep/deeper1 &&
1191+
1192+
test_must_fail git -C clean-merge sparse-checkout clean -f 2>err &&
1193+
grep "failed to convert index to a sparse index" err &&
1194+
1195+
mkdir -p clean-merge/folder1/ &&
1196+
echo merged >clean-merge/folder1/a &&
1197+
git -C clean-merge add --sparse folder1/a &&
1198+
1199+
# deletes folder2/ but leaves staged change in folder1
1200+
# and dirty change in deep/deeper2/
1201+
cat >expect <<-\EOF &&
1202+
Removing folder2/
1203+
EOF
1204+
1205+
git -C clean-merge sparse-checkout clean -f >out &&
1206+
test_cmp expect out
1207+
'
1208+
11571209
test_done

0 commit comments

Comments
 (0)