diff options
| -rw-r--r-- | Documentation/git-sparse-checkout.adoc | 9 | ||||
| -rw-r--r-- | builtin/sparse-checkout.c | 15 | ||||
| -rwxr-xr-x | t/t1091-sparse-checkout-builtin.sh | 54 |
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 |
