Skip to content

Commit f0d5222

Browse files
derrickstoleedscho
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]>
1 parent 1556045 commit f0d5222

File tree

3 files changed

+87
-3
lines changed

3 files changed

+87
-3
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
@@ -940,6 +940,7 @@ static char const * const builtin_sparse_checkout_clean_usage[] = {
940940
};
941941

942942
static const char *msg_remove = N_("Removing %s\n");
943+
static const char *msg_would_remove = N_("Would remove %s\n");
943944

944945
static int sparse_checkout_clean(int argc, const char **argv,
945946
const char *prefix,
@@ -948,8 +949,12 @@ static int sparse_checkout_clean(int argc, const char **argv,
948949
struct strbuf full_path = STRBUF_INIT;
949950
const char *msg = msg_remove;
950951
size_t worktree_len;
952+
int force = 0, dry_run = 0;
953+
int require_force = 1;
951954

952955
struct option builtin_sparse_checkout_clean_options[] = {
956+
OPT__DRY_RUN(&dry_run, N_("dry run")),
957+
OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE),
953958
OPT_END(),
954959
};
955960

@@ -963,6 +968,13 @@ static int sparse_checkout_clean(int argc, const char **argv,
963968
builtin_sparse_checkout_clean_options,
964969
builtin_sparse_checkout_clean_usage, 0);
965970

971+
repo_config_get_bool(repo, "clean.requireforce", &require_force);
972+
if (require_force && !force && !dry_run)
973+
die(_("for safety, refusing to clean without one of --force or --dry-run"));
974+
975+
if (dry_run)
976+
msg = msg_would_remove;
977+
966978
if (repo_read_index(repo) < 0)
967979
die(_("failed to read index"));
968980

@@ -986,7 +998,8 @@ static int sparse_checkout_clean(int argc, const char **argv,
986998

987999
printf(msg, ce->name);
9881000

989-
if (remove_dir_recursively(&full_path, 0))
1001+
if (dry_run <= 0 &&
1002+
remove_dir_recursively(&full_path, 0))
9901003
warning_errno(_("failed to remove '%s'"), ce->name);
9911004
}
9921005

t/t1091-sparse-checkout-builtin.sh

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,12 +1061,29 @@ test_expect_success 'clean' '
10611061
touch repo/deep/deeper2/file &&
10621062
touch repo/folder1/file &&
10631063
1064+
test_must_fail git -C repo sparse-checkout clean 2>err &&
1065+
grep "refusing to clean" err &&
1066+
1067+
git -C repo config clean.requireForce true &&
1068+
test_must_fail git -C repo sparse-checkout clean 2>err &&
1069+
grep "refusing to clean" err &&
1070+
1071+
cat >expect <<-\EOF &&
1072+
Would remove deep/deeper2/
1073+
Would remove folder1/
1074+
EOF
1075+
1076+
git -C repo sparse-checkout clean --dry-run >out &&
1077+
test_cmp expect out &&
1078+
test_path_exists repo/deep/deeper2 &&
1079+
test_path_exists repo/folder1 &&
1080+
10641081
cat >expect <<-\EOF &&
10651082
Removing deep/deeper2/
10661083
Removing folder1/
10671084
EOF
10681085
1069-
git -C repo sparse-checkout clean >out &&
1086+
git -C repo sparse-checkout clean -f >out &&
10701087
test_cmp expect out &&
10711088
10721089
test_path_is_missing repo/deep/deeper2 &&
@@ -1082,16 +1099,61 @@ test_expect_success 'clean with staged sparse change' '
10821099
10831100
git -C repo add --sparse folder1/file &&
10841101
1102+
cat >expect <<-\EOF &&
1103+
Would remove deep/deeper2/
1104+
EOF
1105+
1106+
git -C repo sparse-checkout clean --dry-run >out &&
1107+
test_cmp expect out &&
1108+
test_path_exists repo/deep/deeper2 &&
1109+
test_path_exists repo/folder1 &&
1110+
test_path_exists repo/folder2 &&
1111+
10851112
# deletes deep/deeper2/ but leaves folder1/ and folder2/
10861113
cat >expect <<-\EOF &&
10871114
Removing deep/deeper2/
10881115
EOF
10891116
1117+
# The previous test case checked the -f option, so
1118+
# test the config option in this one.
1119+
git -C repo config clean.requireForce false &&
10901120
git -C repo sparse-checkout clean >out &&
10911121
test_cmp expect out &&
10921122
10931123
test_path_is_missing repo/deep/deeper2 &&
1094-
test_path_exists repo/folder1
1124+
test_path_exists repo/folder1 &&
1125+
test_path_exists repo/folder2
1126+
'
1127+
1128+
test_expect_success 'clean with merge conflict status' '
1129+
git clone repo clean-merge &&
1130+
1131+
echo dirty >clean-merge/deep/deeper2/a &&
1132+
touch clean-merge/folder2/extra &&
1133+
1134+
cat >input <<-EOF &&
1135+
0 $ZERO_OID folder1/a
1136+
100644 $(git -C clean-merge rev-parse HEAD:folder1/a) 1 folder1/a
1137+
EOF
1138+
git -C clean-merge update-index --index-info <input &&
1139+
1140+
git -C clean-merge sparse-checkout set deep/deeper1 &&
1141+
1142+
test_must_fail git -C clean-merge sparse-checkout clean -f 2>err &&
1143+
grep "failed to convert index to a sparse index" err &&
1144+
1145+
mkdir -p clean-merge/folder1/ &&
1146+
echo merged >clean-merge/folder1/a &&
1147+
git -C clean-merge add --sparse folder1/a &&
1148+
1149+
# deletes folder2/ but leaves staged change in folder1
1150+
# and dirty change in deep/deeper2/
1151+
cat >expect <<-\EOF &&
1152+
Removing folder2/
1153+
EOF
1154+
1155+
git -C clean-merge sparse-checkout clean -f >out &&
1156+
test_cmp expect out
10951157
'
10961158

10971159
test_done

0 commit comments

Comments
 (0)