summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-sparse-checkout.adoc9
-rw-r--r--builtin/sparse-checkout.c15
-rwxr-xr-xt/t1091-sparse-checkout-builtin.sh54
3 files changed, 76 insertions, 2 deletions
diff --git a/Documentation/git-sparse-checkout.adoc b/Documentation/git-sparse-checkout.adoc
index baaebce746..42050ff5b5 100644
--- a/Documentation/git-sparse-checkout.adoc
+++ b/Documentation/git-sparse-checkout.adoc
@@ -127,6 +127,15 @@ clean` to resolve these cases.
This command can be used to be sure the sparse index works efficiently,
though it does not require enabling the sparse index feature via the
`index.sparse=true` configuration.
++
+To prevent accidental deletion of worktree files, the `clean` subcommand
+will not delete any files without the `-f` or `--force` option, unless
+the `clean.requireForce` config option is set to `false`.
++
+The `--dry-run` option will list the directories that would be removed
+without deleting them. Running in this mode can be helpful to predict the
+behavior of the clean comand or to determine which kinds of files are left
+in the sparse directories.
'disable'::
Disable the `core.sparseCheckout` config setting, and restore the
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index f7caa28f3f..d777b64960 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -931,6 +931,7 @@ static char const * const builtin_sparse_checkout_clean_usage[] = {
};
static const char *msg_remove = N_("Removing %s\n");
+static const char *msg_would_remove = N_("Would remove %s\n");
static int sparse_checkout_clean(int argc, const char **argv,
const char *prefix,
@@ -939,8 +940,12 @@ static int sparse_checkout_clean(int argc, const char **argv,
struct strbuf full_path = STRBUF_INIT;
const char *msg = msg_remove;
size_t worktree_len;
+ int force = 0, dry_run = 0;
+ int require_force = 1;
struct option builtin_sparse_checkout_clean_options[] = {
+ OPT__DRY_RUN(&dry_run, N_("dry run")),
+ OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE),
OPT_END(),
};
@@ -954,6 +959,13 @@ static int sparse_checkout_clean(int argc, const char **argv,
builtin_sparse_checkout_clean_options,
builtin_sparse_checkout_clean_usage, 0);
+ repo_config_get_bool(repo, "clean.requireforce", &require_force);
+ if (require_force && !force && !dry_run)
+ die(_("for safety, refusing to clean without one of --force or --dry-run"));
+
+ if (dry_run)
+ msg = msg_would_remove;
+
if (repo_read_index(repo) < 0)
die(_("failed to read index"));
@@ -977,7 +989,8 @@ static int sparse_checkout_clean(int argc, const char **argv,
printf(msg, ce->name);
- if (remove_dir_recursively(&full_path, 0))
+ if (dry_run <= 0 &&
+ remove_dir_recursively(&full_path, 0))
warning_errno(_("failed to remove '%s'"), ce->name);
}
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
index bdb7b21e32..e6b768a8da 100755
--- a/t/t1091-sparse-checkout-builtin.sh
+++ b/t/t1091-sparse-checkout-builtin.sh
@@ -1059,12 +1059,29 @@ test_expect_success 'clean' '
touch repo/deep/deeper2/file &&
touch repo/folder1/file &&
+ test_must_fail git -C repo sparse-checkout clean 2>err &&
+ grep "refusing to clean" err &&
+
+ git -C repo config clean.requireForce true &&
+ test_must_fail git -C repo sparse-checkout clean 2>err &&
+ grep "refusing to clean" err &&
+
+ cat >expect <<-\EOF &&
+ Would remove deep/deeper2/
+ Would remove folder1/
+ EOF
+
+ git -C repo sparse-checkout clean --dry-run >out &&
+ test_cmp expect out &&
+ test_path_exists repo/deep/deeper2 &&
+ test_path_exists repo/folder1 &&
+
cat >expect <<-\EOF &&
Removing deep/deeper2/
Removing folder1/
EOF
- git -C repo sparse-checkout clean >out &&
+ git -C repo sparse-checkout clean -f >out &&
test_cmp expect out &&
test_path_is_missing repo/deep/deeper2 &&
@@ -1076,6 +1093,10 @@ test_expect_success 'clean with sparse file states' '
git -C repo sparse-checkout set --cone deep/deeper1 &&
mkdir repo/folder2 &&
+ # The previous test case checked the -f option, so
+ # test the config option in this one.
+ git -C repo config clean.requireForce false &&
+
# create an untracked file and a modified file
touch repo/folder2/file &&
echo dirty >repo/folder2/a &&
@@ -1154,4 +1175,35 @@ test_expect_success 'clean with sparse file states' '
test_must_be_empty out
'
+test_expect_success 'clean with merge conflict status' '
+ git clone repo clean-merge &&
+
+ echo dirty >clean-merge/deep/deeper2/a &&
+ touch clean-merge/folder2/extra &&
+
+ cat >input <<-EOF &&
+ 0 $ZERO_OID folder1/a
+ 100644 $(git -C clean-merge rev-parse HEAD:folder1/a) 1 folder1/a
+ EOF
+ git -C clean-merge update-index --index-info <input &&
+
+ git -C clean-merge sparse-checkout set deep/deeper1 &&
+
+ test_must_fail git -C clean-merge sparse-checkout clean -f 2>err &&
+ grep "failed to convert index to a sparse index" err &&
+
+ mkdir -p clean-merge/folder1/ &&
+ echo merged >clean-merge/folder1/a &&
+ git -C clean-merge add --sparse folder1/a &&
+
+ # deletes folder2/ but leaves staged change in folder1
+ # and dirty change in deep/deeper2/
+ cat >expect <<-\EOF &&
+ Removing folder2/
+ EOF
+
+ git -C clean-merge sparse-checkout clean -f >out &&
+ test_cmp expect out
+'
+
test_done