mirror of
https://github.com/git/git.git
synced 2025-12-12 20:36:24 +01:00
Merge branch 'ds/sparse-checkout-clean'
"git sparse-checkout" subcommand learned a new "clean" action to prune otherwise unused working-tree files that are outside the areas of interest. * ds/sparse-checkout-clean: sparse-index: improve advice message instructions t: expand tests around sparse merges and clean sparse-index: point users to new 'clean' action sparse-checkout: add --verbose option to 'clean' dir: add generic "walk all files" helper sparse-checkout: match some 'clean' behavior sparse-checkout: add basics of 'clean' command sparse-checkout: remove use of the_repository
This commit is contained in:
@@ -9,7 +9,7 @@ git-sparse-checkout - Reduce your working tree to a subset of tracked files
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
'git sparse-checkout' (init | list | set | add | reapply | disable | check-rules) [<options>]
|
'git sparse-checkout' (init | list | set | add | reapply | disable | check-rules | clean) [<options>]
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
@@ -111,6 +111,37 @@ flags, with the same meaning as the flags from the `set` command, in order
|
|||||||
to change which sparsity mode you are using without needing to also respecify
|
to change which sparsity mode you are using without needing to also respecify
|
||||||
all sparsity paths.
|
all sparsity paths.
|
||||||
|
|
||||||
|
'clean'::
|
||||||
|
Opportunistically remove files outside of the sparse-checkout
|
||||||
|
definition. This command requires cone mode to use recursive
|
||||||
|
directory matches to determine which files should be removed. A
|
||||||
|
file is considered for removal if it is contained within a tracked
|
||||||
|
directory that is outside of the sparse-checkout definition.
|
||||||
|
+
|
||||||
|
Some special cases, such as merge conflicts or modified files outside of
|
||||||
|
the sparse-checkout definition could lead to keeping files that would
|
||||||
|
otherwise be removed. Resolve conflicts, stage modifications, and use
|
||||||
|
`git sparse-checkout reapply` in conjunction with `git sparse-checkout
|
||||||
|
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.
|
||||||
|
+
|
||||||
|
The `--verbose` option will list every file within the directories that
|
||||||
|
are considered for removal. This option is helpful to determine if those
|
||||||
|
files are actually important or perhaps to explain why the directory is
|
||||||
|
still present despite the current sparse-checkout.
|
||||||
|
|
||||||
'disable'::
|
'disable'::
|
||||||
Disable the `core.sparseCheckout` config setting, and restore the
|
Disable the `core.sparseCheckout` config setting, and restore the
|
||||||
working directory to include all files.
|
working directory to include all files.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define DISABLE_SIGN_COMPARE_WARNINGS
|
#define DISABLE_SIGN_COMPARE_WARNINGS
|
||||||
|
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
|
#include "abspath.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "environment.h"
|
#include "environment.h"
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
static const char *empty_base = "";
|
static const char *empty_base = "";
|
||||||
|
|
||||||
static char const * const builtin_sparse_checkout_usage[] = {
|
static char const * const builtin_sparse_checkout_usage[] = {
|
||||||
N_("git sparse-checkout (init | list | set | add | reapply | disable | check-rules) [<options>]"),
|
N_("git sparse-checkout (init | list | set | add | reapply | disable | check-rules | clean) [<options>]"),
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -204,12 +205,12 @@ static void clean_tracked_sparse_directories(struct repository *r)
|
|||||||
ensure_full_index(r->index);
|
ensure_full_index(r->index);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int update_working_directory(struct pattern_list *pl)
|
static int update_working_directory(struct repository *r,
|
||||||
|
struct pattern_list *pl)
|
||||||
{
|
{
|
||||||
enum update_sparsity_result result;
|
enum update_sparsity_result result;
|
||||||
struct unpack_trees_options o;
|
struct unpack_trees_options o;
|
||||||
struct lock_file lock_file = LOCK_INIT;
|
struct lock_file lock_file = LOCK_INIT;
|
||||||
struct repository *r = the_repository;
|
|
||||||
struct pattern_list *old_pl;
|
struct pattern_list *old_pl;
|
||||||
|
|
||||||
/* If no branch has been checked out, there are no updates to make. */
|
/* If no branch has been checked out, there are no updates to make. */
|
||||||
@@ -327,7 +328,8 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl)
|
|||||||
string_list_clear(&sl, 0);
|
string_list_clear(&sl, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int write_patterns_and_update(struct pattern_list *pl)
|
static int write_patterns_and_update(struct repository *repo,
|
||||||
|
struct pattern_list *pl)
|
||||||
{
|
{
|
||||||
char *sparse_filename;
|
char *sparse_filename;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
@@ -336,15 +338,15 @@ static int write_patterns_and_update(struct pattern_list *pl)
|
|||||||
|
|
||||||
sparse_filename = get_sparse_checkout_filename();
|
sparse_filename = get_sparse_checkout_filename();
|
||||||
|
|
||||||
if (safe_create_leading_directories(the_repository, sparse_filename))
|
if (safe_create_leading_directories(repo, sparse_filename))
|
||||||
die(_("failed to create directory for sparse-checkout file"));
|
die(_("failed to create directory for sparse-checkout file"));
|
||||||
|
|
||||||
hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR);
|
hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR);
|
||||||
|
|
||||||
result = update_working_directory(pl);
|
result = update_working_directory(repo, pl);
|
||||||
if (result) {
|
if (result) {
|
||||||
rollback_lock_file(&lk);
|
rollback_lock_file(&lk);
|
||||||
update_working_directory(NULL);
|
update_working_directory(repo, NULL);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,25 +374,26 @@ enum sparse_checkout_mode {
|
|||||||
MODE_CONE_PATTERNS = 2,
|
MODE_CONE_PATTERNS = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int set_config(enum sparse_checkout_mode mode)
|
static int set_config(struct repository *repo,
|
||||||
|
enum sparse_checkout_mode mode)
|
||||||
{
|
{
|
||||||
/* Update to use worktree config, if not already. */
|
/* Update to use worktree config, if not already. */
|
||||||
if (init_worktree_config(the_repository)) {
|
if (init_worktree_config(repo)) {
|
||||||
error(_("failed to initialize worktree config"));
|
error(_("failed to initialize worktree config"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repo_config_set_worktree_gently(the_repository,
|
if (repo_config_set_worktree_gently(repo,
|
||||||
"core.sparseCheckout",
|
"core.sparseCheckout",
|
||||||
mode ? "true" : "false") ||
|
mode ? "true" : "false") ||
|
||||||
repo_config_set_worktree_gently(the_repository,
|
repo_config_set_worktree_gently(repo,
|
||||||
"core.sparseCheckoutCone",
|
"core.sparseCheckoutCone",
|
||||||
mode == MODE_CONE_PATTERNS ?
|
mode == MODE_CONE_PATTERNS ?
|
||||||
"true" : "false"))
|
"true" : "false"))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (mode == MODE_NO_PATTERNS)
|
if (mode == MODE_NO_PATTERNS)
|
||||||
return set_sparse_index_config(the_repository, 0);
|
return set_sparse_index_config(repo, 0);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -410,7 +413,7 @@ static enum sparse_checkout_mode update_cone_mode(int *cone_mode) {
|
|||||||
return MODE_ALL_PATTERNS;
|
return MODE_ALL_PATTERNS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int update_modes(int *cone_mode, int *sparse_index)
|
static int update_modes(struct repository *repo, int *cone_mode, int *sparse_index)
|
||||||
{
|
{
|
||||||
int mode, record_mode;
|
int mode, record_mode;
|
||||||
|
|
||||||
@@ -418,20 +421,20 @@ static int update_modes(int *cone_mode, int *sparse_index)
|
|||||||
record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout;
|
record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout;
|
||||||
|
|
||||||
mode = update_cone_mode(cone_mode);
|
mode = update_cone_mode(cone_mode);
|
||||||
if (record_mode && set_config(mode))
|
if (record_mode && set_config(repo, mode))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
/* Set sparse-index/non-sparse-index mode if specified */
|
/* Set sparse-index/non-sparse-index mode if specified */
|
||||||
if (*sparse_index >= 0) {
|
if (*sparse_index >= 0) {
|
||||||
if (set_sparse_index_config(the_repository, *sparse_index) < 0)
|
if (set_sparse_index_config(repo, *sparse_index) < 0)
|
||||||
die(_("failed to modify sparse-index config"));
|
die(_("failed to modify sparse-index config"));
|
||||||
|
|
||||||
/* force an index rewrite */
|
/* force an index rewrite */
|
||||||
repo_read_index(the_repository);
|
repo_read_index(repo);
|
||||||
the_repository->index->updated_workdir = 1;
|
repo->index->updated_workdir = 1;
|
||||||
|
|
||||||
if (!*sparse_index)
|
if (!*sparse_index)
|
||||||
ensure_full_index(the_repository->index);
|
ensure_full_index(repo->index);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -448,7 +451,7 @@ static struct sparse_checkout_init_opts {
|
|||||||
} init_opts;
|
} init_opts;
|
||||||
|
|
||||||
static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
|
static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
struct pattern_list pl;
|
struct pattern_list pl;
|
||||||
char *sparse_filename;
|
char *sparse_filename;
|
||||||
@@ -464,7 +467,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
|
|||||||
};
|
};
|
||||||
|
|
||||||
setup_work_tree();
|
setup_work_tree();
|
||||||
repo_read_index(the_repository);
|
repo_read_index(repo);
|
||||||
|
|
||||||
init_opts.cone_mode = -1;
|
init_opts.cone_mode = -1;
|
||||||
init_opts.sparse_index = -1;
|
init_opts.sparse_index = -1;
|
||||||
@@ -473,7 +476,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
|
|||||||
builtin_sparse_checkout_init_options,
|
builtin_sparse_checkout_init_options,
|
||||||
builtin_sparse_checkout_init_usage, 0);
|
builtin_sparse_checkout_init_usage, 0);
|
||||||
|
|
||||||
if (update_modes(&init_opts.cone_mode, &init_opts.sparse_index))
|
if (update_modes(repo, &init_opts.cone_mode, &init_opts.sparse_index))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
memset(&pl, 0, sizeof(pl));
|
memset(&pl, 0, sizeof(pl));
|
||||||
@@ -485,14 +488,14 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
|
|||||||
if (res >= 0) {
|
if (res >= 0) {
|
||||||
free(sparse_filename);
|
free(sparse_filename);
|
||||||
clear_pattern_list(&pl);
|
clear_pattern_list(&pl);
|
||||||
return update_working_directory(NULL);
|
return update_working_directory(repo, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repo_get_oid(the_repository, "HEAD", &oid)) {
|
if (repo_get_oid(repo, "HEAD", &oid)) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
|
||||||
/* assume we are in a fresh repo, but update the sparse-checkout file */
|
/* assume we are in a fresh repo, but update the sparse-checkout file */
|
||||||
if (safe_create_leading_directories(the_repository, sparse_filename))
|
if (safe_create_leading_directories(repo, sparse_filename))
|
||||||
die(_("unable to create leading directories of %s"),
|
die(_("unable to create leading directories of %s"),
|
||||||
sparse_filename);
|
sparse_filename);
|
||||||
fp = xfopen(sparse_filename, "w");
|
fp = xfopen(sparse_filename, "w");
|
||||||
@@ -511,7 +514,7 @@ static int sparse_checkout_init(int argc, const char **argv, const char *prefix,
|
|||||||
add_pattern("!/*/", empty_base, 0, &pl, 0);
|
add_pattern("!/*/", empty_base, 0, &pl, 0);
|
||||||
pl.use_cone_patterns = init_opts.cone_mode;
|
pl.use_cone_patterns = init_opts.cone_mode;
|
||||||
|
|
||||||
return write_patterns_and_update(&pl);
|
return write_patterns_and_update(repo, &pl);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
|
static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path)
|
||||||
@@ -674,7 +677,8 @@ static void add_patterns_literal(int argc, const char **argv,
|
|||||||
add_patterns_from_input(pl, argc, argv, use_stdin ? stdin : NULL);
|
add_patterns_from_input(pl, argc, argv, use_stdin ? stdin : NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int modify_pattern_list(struct strvec *args, int use_stdin,
|
static int modify_pattern_list(struct repository *repo,
|
||||||
|
struct strvec *args, int use_stdin,
|
||||||
enum modify_type m)
|
enum modify_type m)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
@@ -696,22 +700,23 @@ static int modify_pattern_list(struct strvec *args, int use_stdin,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!core_apply_sparse_checkout) {
|
if (!core_apply_sparse_checkout) {
|
||||||
set_config(MODE_ALL_PATTERNS);
|
set_config(repo, MODE_ALL_PATTERNS);
|
||||||
core_apply_sparse_checkout = 1;
|
core_apply_sparse_checkout = 1;
|
||||||
changed_config = 1;
|
changed_config = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = write_patterns_and_update(pl);
|
result = write_patterns_and_update(repo, pl);
|
||||||
|
|
||||||
if (result && changed_config)
|
if (result && changed_config)
|
||||||
set_config(MODE_NO_PATTERNS);
|
set_config(repo, MODE_NO_PATTERNS);
|
||||||
|
|
||||||
clear_pattern_list(pl);
|
clear_pattern_list(pl);
|
||||||
free(pl);
|
free(pl);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sanitize_paths(struct strvec *args,
|
static void sanitize_paths(struct repository *repo,
|
||||||
|
struct strvec *args,
|
||||||
const char *prefix, int skip_checks)
|
const char *prefix, int skip_checks)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@@ -752,7 +757,7 @@ static void sanitize_paths(struct strvec *args,
|
|||||||
|
|
||||||
for (i = 0; i < args->nr; i++) {
|
for (i = 0; i < args->nr; i++) {
|
||||||
struct cache_entry *ce;
|
struct cache_entry *ce;
|
||||||
struct index_state *index = the_repository->index;
|
struct index_state *index = repo->index;
|
||||||
int pos = index_name_pos(index, args->v[i], strlen(args->v[i]));
|
int pos = index_name_pos(index, args->v[i], strlen(args->v[i]));
|
||||||
|
|
||||||
if (pos < 0)
|
if (pos < 0)
|
||||||
@@ -779,7 +784,7 @@ static struct sparse_checkout_add_opts {
|
|||||||
} add_opts;
|
} add_opts;
|
||||||
|
|
||||||
static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
|
static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
static struct option builtin_sparse_checkout_add_options[] = {
|
static struct option builtin_sparse_checkout_add_options[] = {
|
||||||
OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks,
|
OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks,
|
||||||
@@ -796,7 +801,7 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
|
|||||||
if (!core_apply_sparse_checkout)
|
if (!core_apply_sparse_checkout)
|
||||||
die(_("no sparse-checkout to add to"));
|
die(_("no sparse-checkout to add to"));
|
||||||
|
|
||||||
repo_read_index(the_repository);
|
repo_read_index(repo);
|
||||||
|
|
||||||
argc = parse_options(argc, argv, prefix,
|
argc = parse_options(argc, argv, prefix,
|
||||||
builtin_sparse_checkout_add_options,
|
builtin_sparse_checkout_add_options,
|
||||||
@@ -804,9 +809,9 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix,
|
|||||||
|
|
||||||
for (int i = 0; i < argc; i++)
|
for (int i = 0; i < argc; i++)
|
||||||
strvec_push(&patterns, argv[i]);
|
strvec_push(&patterns, argv[i]);
|
||||||
sanitize_paths(&patterns, prefix, add_opts.skip_checks);
|
sanitize_paths(repo, &patterns, prefix, add_opts.skip_checks);
|
||||||
|
|
||||||
ret = modify_pattern_list(&patterns, add_opts.use_stdin, ADD);
|
ret = modify_pattern_list(repo, &patterns, add_opts.use_stdin, ADD);
|
||||||
|
|
||||||
strvec_clear(&patterns);
|
strvec_clear(&patterns);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -825,7 +830,7 @@ static struct sparse_checkout_set_opts {
|
|||||||
} set_opts;
|
} set_opts;
|
||||||
|
|
||||||
static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
|
static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
int default_patterns_nr = 2;
|
int default_patterns_nr = 2;
|
||||||
const char *default_patterns[] = {"/*", "!/*/", NULL};
|
const char *default_patterns[] = {"/*", "!/*/", NULL};
|
||||||
@@ -847,7 +852,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
setup_work_tree();
|
setup_work_tree();
|
||||||
repo_read_index(the_repository);
|
repo_read_index(repo);
|
||||||
|
|
||||||
set_opts.cone_mode = -1;
|
set_opts.cone_mode = -1;
|
||||||
set_opts.sparse_index = -1;
|
set_opts.sparse_index = -1;
|
||||||
@@ -856,7 +861,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
|
|||||||
builtin_sparse_checkout_set_options,
|
builtin_sparse_checkout_set_options,
|
||||||
builtin_sparse_checkout_set_usage, 0);
|
builtin_sparse_checkout_set_usage, 0);
|
||||||
|
|
||||||
if (update_modes(&set_opts.cone_mode, &set_opts.sparse_index))
|
if (update_modes(repo, &set_opts.cone_mode, &set_opts.sparse_index))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -870,10 +875,10 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix,
|
|||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < argc; i++)
|
for (int i = 0; i < argc; i++)
|
||||||
strvec_push(&patterns, argv[i]);
|
strvec_push(&patterns, argv[i]);
|
||||||
sanitize_paths(&patterns, prefix, set_opts.skip_checks);
|
sanitize_paths(repo, &patterns, prefix, set_opts.skip_checks);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = modify_pattern_list(&patterns, set_opts.use_stdin, REPLACE);
|
ret = modify_pattern_list(repo, &patterns, set_opts.use_stdin, REPLACE);
|
||||||
|
|
||||||
strvec_clear(&patterns);
|
strvec_clear(&patterns);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -891,7 +896,7 @@ static struct sparse_checkout_reapply_opts {
|
|||||||
|
|
||||||
static int sparse_checkout_reapply(int argc, const char **argv,
|
static int sparse_checkout_reapply(int argc, const char **argv,
|
||||||
const char *prefix,
|
const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
static struct option builtin_sparse_checkout_reapply_options[] = {
|
static struct option builtin_sparse_checkout_reapply_options[] = {
|
||||||
OPT_BOOL(0, "cone", &reapply_opts.cone_mode,
|
OPT_BOOL(0, "cone", &reapply_opts.cone_mode,
|
||||||
@@ -912,12 +917,107 @@ static int sparse_checkout_reapply(int argc, const char **argv,
|
|||||||
builtin_sparse_checkout_reapply_options,
|
builtin_sparse_checkout_reapply_options,
|
||||||
builtin_sparse_checkout_reapply_usage, 0);
|
builtin_sparse_checkout_reapply_usage, 0);
|
||||||
|
|
||||||
repo_read_index(the_repository);
|
repo_read_index(repo);
|
||||||
|
|
||||||
if (update_modes(&reapply_opts.cone_mode, &reapply_opts.sparse_index))
|
if (update_modes(repo, &reapply_opts.cone_mode, &reapply_opts.sparse_index))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
return update_working_directory(NULL);
|
return update_working_directory(repo, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char const * const builtin_sparse_checkout_clean_usage[] = {
|
||||||
|
"git sparse-checkout clean [-n|--dry-run]",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static int list_file_iterator(const char *path, const void *data)
|
||||||
|
{
|
||||||
|
const char *msg = data;
|
||||||
|
|
||||||
|
printf(msg, path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void list_every_file_in_dir(const char *msg,
|
||||||
|
const char *directory)
|
||||||
|
{
|
||||||
|
struct strbuf path = STRBUF_INIT;
|
||||||
|
|
||||||
|
strbuf_addstr(&path, directory);
|
||||||
|
for_each_file_in_dir(&path, list_file_iterator, msg);
|
||||||
|
strbuf_release(&path);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
struct repository *repo)
|
||||||
|
{
|
||||||
|
struct strbuf full_path = STRBUF_INIT;
|
||||||
|
const char *msg = msg_remove;
|
||||||
|
size_t worktree_len;
|
||||||
|
int force = 0, dry_run = 0, verbose = 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__VERBOSE(&verbose, N_("report each affected file, not just directories")),
|
||||||
|
OPT_END(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setup_work_tree();
|
||||||
|
if (!core_apply_sparse_checkout)
|
||||||
|
die(_("must be in a sparse-checkout to clean directories"));
|
||||||
|
if (!core_sparse_checkout_cone)
|
||||||
|
die(_("must be in a cone-mode sparse-checkout to clean directories"));
|
||||||
|
|
||||||
|
argc = parse_options(argc, argv, prefix,
|
||||||
|
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"));
|
||||||
|
|
||||||
|
if (convert_to_sparse(repo->index, SPARSE_INDEX_MEMORY_ONLY) ||
|
||||||
|
repo->index->sparse_index == INDEX_EXPANDED)
|
||||||
|
die(_("failed to convert index to a sparse index; resolve merge conflicts and try again"));
|
||||||
|
|
||||||
|
strbuf_addstr(&full_path, repo->worktree);
|
||||||
|
strbuf_addch(&full_path, '/');
|
||||||
|
worktree_len = full_path.len;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < repo->index->cache_nr; i++) {
|
||||||
|
struct cache_entry *ce = repo->index->cache[i];
|
||||||
|
if (!S_ISSPARSEDIR(ce->ce_mode))
|
||||||
|
continue;
|
||||||
|
strbuf_setlen(&full_path, worktree_len);
|
||||||
|
strbuf_add(&full_path, ce->name, ce->ce_namelen);
|
||||||
|
|
||||||
|
if (!is_directory(full_path.buf))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
list_every_file_in_dir(msg, ce->name);
|
||||||
|
else
|
||||||
|
printf(msg, ce->name);
|
||||||
|
|
||||||
|
if (dry_run <= 0 &&
|
||||||
|
remove_dir_recursively(&full_path, 0))
|
||||||
|
warning_errno(_("failed to remove '%s'"), ce->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_release(&full_path);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char const * const builtin_sparse_checkout_disable_usage[] = {
|
static char const * const builtin_sparse_checkout_disable_usage[] = {
|
||||||
@@ -927,7 +1027,7 @@ static char const * const builtin_sparse_checkout_disable_usage[] = {
|
|||||||
|
|
||||||
static int sparse_checkout_disable(int argc, const char **argv,
|
static int sparse_checkout_disable(int argc, const char **argv,
|
||||||
const char *prefix,
|
const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
static struct option builtin_sparse_checkout_disable_options[] = {
|
static struct option builtin_sparse_checkout_disable_options[] = {
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
@@ -955,7 +1055,7 @@ static int sparse_checkout_disable(int argc, const char **argv,
|
|||||||
* are expecting to do that when disabling sparse-checkout.
|
* are expecting to do that when disabling sparse-checkout.
|
||||||
*/
|
*/
|
||||||
give_advice_on_expansion = 0;
|
give_advice_on_expansion = 0;
|
||||||
repo_read_index(the_repository);
|
repo_read_index(repo);
|
||||||
|
|
||||||
memset(&pl, 0, sizeof(pl));
|
memset(&pl, 0, sizeof(pl));
|
||||||
hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
|
hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0);
|
||||||
@@ -966,13 +1066,13 @@ static int sparse_checkout_disable(int argc, const char **argv,
|
|||||||
add_pattern("/*", empty_base, 0, &pl, 0);
|
add_pattern("/*", empty_base, 0, &pl, 0);
|
||||||
|
|
||||||
prepare_repo_settings(the_repository);
|
prepare_repo_settings(the_repository);
|
||||||
the_repository->settings.sparse_index = 0;
|
repo->settings.sparse_index = 0;
|
||||||
|
|
||||||
if (update_working_directory(&pl))
|
if (update_working_directory(repo, &pl))
|
||||||
die(_("error while refreshing working directory"));
|
die(_("error while refreshing working directory"));
|
||||||
|
|
||||||
clear_pattern_list(&pl);
|
clear_pattern_list(&pl);
|
||||||
return set_config(MODE_NO_PATTERNS);
|
return set_config(repo, MODE_NO_PATTERNS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char const * const builtin_sparse_checkout_check_rules_usage[] = {
|
static char const * const builtin_sparse_checkout_check_rules_usage[] = {
|
||||||
@@ -987,14 +1087,17 @@ static struct sparse_checkout_check_rules_opts {
|
|||||||
char *rules_file;
|
char *rules_file;
|
||||||
} check_rules_opts;
|
} check_rules_opts;
|
||||||
|
|
||||||
static int check_rules(struct pattern_list *pl, int null_terminated) {
|
static int check_rules(struct repository *repo,
|
||||||
|
struct pattern_list *pl,
|
||||||
|
int null_terminated)
|
||||||
|
{
|
||||||
struct strbuf line = STRBUF_INIT;
|
struct strbuf line = STRBUF_INIT;
|
||||||
struct strbuf unquoted = STRBUF_INIT;
|
struct strbuf unquoted = STRBUF_INIT;
|
||||||
char *path;
|
char *path;
|
||||||
int line_terminator = null_terminated ? 0 : '\n';
|
int line_terminator = null_terminated ? 0 : '\n';
|
||||||
strbuf_getline_fn getline_fn = null_terminated ? strbuf_getline_nul
|
strbuf_getline_fn getline_fn = null_terminated ? strbuf_getline_nul
|
||||||
: strbuf_getline;
|
: strbuf_getline;
|
||||||
the_repository->index->sparse_checkout_patterns = pl;
|
repo->index->sparse_checkout_patterns = pl;
|
||||||
while (!getline_fn(&line, stdin)) {
|
while (!getline_fn(&line, stdin)) {
|
||||||
path = line.buf;
|
path = line.buf;
|
||||||
if (!null_terminated && line.buf[0] == '"') {
|
if (!null_terminated && line.buf[0] == '"') {
|
||||||
@@ -1006,7 +1109,7 @@ static int check_rules(struct pattern_list *pl, int null_terminated) {
|
|||||||
path = unquoted.buf;
|
path = unquoted.buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path_in_sparse_checkout(path, the_repository->index))
|
if (path_in_sparse_checkout(path, repo->index))
|
||||||
write_name_quoted(path, stdout, line_terminator);
|
write_name_quoted(path, stdout, line_terminator);
|
||||||
}
|
}
|
||||||
strbuf_release(&line);
|
strbuf_release(&line);
|
||||||
@@ -1016,7 +1119,7 @@ static int check_rules(struct pattern_list *pl, int null_terminated) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int sparse_checkout_check_rules(int argc, const char **argv, const char *prefix,
|
static int sparse_checkout_check_rules(int argc, const char **argv, const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
static struct option builtin_sparse_checkout_check_rules_options[] = {
|
static struct option builtin_sparse_checkout_check_rules_options[] = {
|
||||||
OPT_BOOL('z', NULL, &check_rules_opts.null_termination,
|
OPT_BOOL('z', NULL, &check_rules_opts.null_termination,
|
||||||
@@ -1055,7 +1158,7 @@ static int sparse_checkout_check_rules(int argc, const char **argv, const char *
|
|||||||
free(sparse_filename);
|
free(sparse_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = check_rules(&pl, check_rules_opts.null_termination);
|
ret = check_rules(repo, &pl, check_rules_opts.null_termination);
|
||||||
clear_pattern_list(&pl);
|
clear_pattern_list(&pl);
|
||||||
free(check_rules_opts.rules_file);
|
free(check_rules_opts.rules_file);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -1073,6 +1176,7 @@ int cmd_sparse_checkout(int argc,
|
|||||||
OPT_SUBCOMMAND("set", &fn, sparse_checkout_set),
|
OPT_SUBCOMMAND("set", &fn, sparse_checkout_set),
|
||||||
OPT_SUBCOMMAND("add", &fn, sparse_checkout_add),
|
OPT_SUBCOMMAND("add", &fn, sparse_checkout_add),
|
||||||
OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply),
|
OPT_SUBCOMMAND("reapply", &fn, sparse_checkout_reapply),
|
||||||
|
OPT_SUBCOMMAND("clean", &fn, sparse_checkout_clean),
|
||||||
OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable),
|
OPT_SUBCOMMAND("disable", &fn, sparse_checkout_disable),
|
||||||
OPT_SUBCOMMAND("check-rules", &fn, sparse_checkout_check_rules),
|
OPT_SUBCOMMAND("check-rules", &fn, sparse_checkout_check_rules),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
@@ -1084,8 +1188,8 @@ int cmd_sparse_checkout(int argc,
|
|||||||
|
|
||||||
repo_config(the_repository, git_default_config, NULL);
|
repo_config(the_repository, git_default_config, NULL);
|
||||||
|
|
||||||
prepare_repo_settings(the_repository);
|
prepare_repo_settings(repo);
|
||||||
the_repository->settings.command_requires_full_index = 0;
|
repo->settings.command_requires_full_index = 0;
|
||||||
|
|
||||||
return fn(argc, argv, prefix, repo);
|
return fn(argc, argv, prefix, repo);
|
||||||
}
|
}
|
||||||
|
|||||||
28
dir.c
28
dir.c
@@ -30,6 +30,7 @@
|
|||||||
#include "read-cache-ll.h"
|
#include "read-cache-ll.h"
|
||||||
#include "setup.h"
|
#include "setup.h"
|
||||||
#include "sparse-index.h"
|
#include "sparse-index.h"
|
||||||
|
#include "strbuf.h"
|
||||||
#include "submodule-config.h"
|
#include "submodule-config.h"
|
||||||
#include "symlinks.h"
|
#include "symlinks.h"
|
||||||
#include "trace2.h"
|
#include "trace2.h"
|
||||||
@@ -87,6 +88,33 @@ struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp)
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int for_each_file_in_dir(struct strbuf *path, file_iterator fn, const void *data)
|
||||||
|
{
|
||||||
|
struct dirent *e;
|
||||||
|
int res = 0;
|
||||||
|
size_t baselen = path->len;
|
||||||
|
DIR *dir = opendir(path->buf);
|
||||||
|
|
||||||
|
if (!dir)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while (!res && (e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||||
|
unsigned char dtype = get_dtype(e, path, 0);
|
||||||
|
strbuf_setlen(path, baselen);
|
||||||
|
strbuf_addstr(path, e->d_name);
|
||||||
|
|
||||||
|
if (dtype == DT_REG) {
|
||||||
|
res = fn(path->buf, data);
|
||||||
|
} else if (dtype == DT_DIR) {
|
||||||
|
strbuf_addch(path, '/');
|
||||||
|
res = for_each_file_in_dir(path, fn, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
int count_slashes(const char *s)
|
int count_slashes(const char *s)
|
||||||
{
|
{
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
|
|||||||
14
dir.h
14
dir.h
@@ -536,6 +536,20 @@ int get_sparse_checkout_patterns(struct pattern_list *pl);
|
|||||||
*/
|
*/
|
||||||
int remove_dir_recursively(struct strbuf *path, int flag);
|
int remove_dir_recursively(struct strbuf *path, int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function pointer type is called on each file discovered in
|
||||||
|
* for_each_file_in_dir. The iteration stops if this method returns
|
||||||
|
* non-zero.
|
||||||
|
*/
|
||||||
|
typedef int (*file_iterator)(const char *path, const void *data);
|
||||||
|
|
||||||
|
struct strbuf;
|
||||||
|
/*
|
||||||
|
* Given a directory path, recursively visit each file within, including
|
||||||
|
* within subdirectories.
|
||||||
|
*/
|
||||||
|
int for_each_file_in_dir(struct strbuf *path, file_iterator fn, const void *data);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tries to remove the path, along with leading empty directories so long as
|
* Tries to remove the path, along with leading empty directories so long as
|
||||||
* those empty directories are not startup_info->original_cwd. Ignores
|
* those empty directories are not startup_info->original_cwd. Ignores
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ int give_advice_on_expansion = 1;
|
|||||||
"Your working directory likely has contents that are outside of\n" \
|
"Your working directory likely has contents that are outside of\n" \
|
||||||
"your sparse-checkout patterns. Use 'git sparse-checkout list' to\n" \
|
"your sparse-checkout patterns. Use 'git sparse-checkout list' to\n" \
|
||||||
"see your sparse-checkout definition and compare it to your working\n" \
|
"see your sparse-checkout definition and compare it to your working\n" \
|
||||||
"directory contents. Running 'git clean' may assist in this cleanup."
|
"directory contents. Cleaning up any merge conflicts or staged\n" \
|
||||||
|
"changes before running 'git sparse-checkout clean' or 'git\n" \
|
||||||
|
"sparse-checkout reapply' may assist in this cleanup."
|
||||||
|
|
||||||
struct modify_index_context {
|
struct modify_index_context {
|
||||||
struct index_state *write;
|
struct index_state *write;
|
||||||
|
|||||||
@@ -1050,5 +1050,180 @@ test_expect_success 'check-rules null termination' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clean' '
|
||||||
|
git -C repo sparse-checkout set --cone deep/deeper1 &&
|
||||||
|
git -C repo sparse-checkout reapply &&
|
||||||
|
mkdir -p repo/deep/deeper2 repo/folder1/extra/inside &&
|
||||||
|
|
||||||
|
# Add untracked files
|
||||||
|
touch repo/deep/deeper2/file &&
|
||||||
|
touch repo/folder1/extra/inside/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/extra/inside/file &&
|
||||||
|
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
Would remove deep/deeper2/file
|
||||||
|
Would remove folder1/extra/inside/file
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C repo sparse-checkout clean --dry-run --verbose >out &&
|
||||||
|
test_cmp expect out &&
|
||||||
|
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
Removing deep/deeper2/
|
||||||
|
Removing folder1/
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C repo sparse-checkout clean -f >out &&
|
||||||
|
test_cmp expect out &&
|
||||||
|
|
||||||
|
test_path_is_missing repo/deep/deeper2 &&
|
||||||
|
test_path_is_missing repo/folder1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'clean with sparse file states' '
|
||||||
|
test_when_finished git reset --hard &&
|
||||||
|
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 &&
|
||||||
|
|
||||||
|
# First clean/reapply pass will do nothing.
|
||||||
|
git -C repo sparse-checkout clean >out &&
|
||||||
|
test_must_be_empty out &&
|
||||||
|
test_path_exists repo/folder2/a &&
|
||||||
|
test_path_exists repo/folder2/file &&
|
||||||
|
|
||||||
|
git -C repo sparse-checkout reapply 2>err &&
|
||||||
|
test_grep folder2 err &&
|
||||||
|
test_path_exists repo/folder2/a &&
|
||||||
|
test_path_exists repo/folder2/file &&
|
||||||
|
|
||||||
|
# Now, stage the change to the tracked file.
|
||||||
|
git -C repo add --sparse folder2/a &&
|
||||||
|
|
||||||
|
# Clean will continue not doing anything.
|
||||||
|
git -C repo sparse-checkout clean >out &&
|
||||||
|
test_line_count = 0 out &&
|
||||||
|
test_path_exists repo/folder2/a &&
|
||||||
|
test_path_exists repo/folder2/file &&
|
||||||
|
|
||||||
|
# But we can reapply to remove the staged change.
|
||||||
|
git -C repo sparse-checkout reapply 2>err &&
|
||||||
|
test_grep folder2 err &&
|
||||||
|
test_path_is_missing repo/folder2/a &&
|
||||||
|
test_path_exists repo/folder2/file &&
|
||||||
|
|
||||||
|
# We can clean now.
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
Removing folder2/
|
||||||
|
EOF
|
||||||
|
git -C repo sparse-checkout clean >out &&
|
||||||
|
test_cmp expect out &&
|
||||||
|
test_path_is_missing repo/folder2 &&
|
||||||
|
|
||||||
|
# At the moment, the file is staged.
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
M folder2/a
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C repo status -s >out &&
|
||||||
|
test_cmp expect out &&
|
||||||
|
|
||||||
|
# Reapply persists the modified state.
|
||||||
|
git -C repo sparse-checkout reapply &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
M folder2/a
|
||||||
|
EOF
|
||||||
|
git -C repo status -s >out &&
|
||||||
|
test_cmp expect out &&
|
||||||
|
|
||||||
|
# Committing the change leads to resolved status.
|
||||||
|
git -C repo commit -m "modified" &&
|
||||||
|
git -C repo status -s >out &&
|
||||||
|
test_must_be_empty out &&
|
||||||
|
|
||||||
|
# Repeat, but this time commit before reapplying.
|
||||||
|
mkdir repo/folder2/ &&
|
||||||
|
echo dirtier >repo/folder2/a &&
|
||||||
|
git -C repo add --sparse folder2/a &&
|
||||||
|
git -C repo sparse-checkout clean >out &&
|
||||||
|
test_must_be_empty out &&
|
||||||
|
test_path_exists repo/folder2/a &&
|
||||||
|
|
||||||
|
# Committing without reapplying makes it look like a deletion
|
||||||
|
# due to no skip-worktree bit.
|
||||||
|
git -C repo commit -m "dirtier" &&
|
||||||
|
git -C repo status -s >out &&
|
||||||
|
test_must_be_empty out &&
|
||||||
|
|
||||||
|
git -C repo sparse-checkout reapply &&
|
||||||
|
git -C repo status -s >out &&
|
||||||
|
test_must_be_empty out
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'sparse-checkout operations with merge conflicts' '
|
||||||
|
git clone repo merge &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cd merge &&
|
||||||
|
mkdir -p folder1/even/more/dirs &&
|
||||||
|
echo base >folder1/even/more/dirs/file &&
|
||||||
|
git add folder1 &&
|
||||||
|
git commit -m "base" &&
|
||||||
|
|
||||||
|
git checkout -b right&&
|
||||||
|
echo right >folder1/even/more/dirs/file &&
|
||||||
|
git commit -a -m "right" &&
|
||||||
|
|
||||||
|
git checkout -b left HEAD~1 &&
|
||||||
|
echo left >folder1/even/more/dirs/file &&
|
||||||
|
git commit -a -m "left" &&
|
||||||
|
|
||||||
|
git checkout -b merge &&
|
||||||
|
git sparse-checkout set deep/deeper1 &&
|
||||||
|
|
||||||
|
test_must_fail git merge -m "will-conflict" right &&
|
||||||
|
|
||||||
|
test_must_fail git sparse-checkout clean -f 2>err &&
|
||||||
|
grep "failed to convert index to a sparse index" err &&
|
||||||
|
|
||||||
|
echo merged >folder1/even/more/dirs/file &&
|
||||||
|
git add --sparse folder1 &&
|
||||||
|
git merge --continue &&
|
||||||
|
|
||||||
|
test_path_exists folder1/even/more/dirs/file &&
|
||||||
|
|
||||||
|
# clean does not remove the file, because the
|
||||||
|
# SKIP_WORKTREE bit was not cleared by the merge command.
|
||||||
|
git sparse-checkout clean -f >out &&
|
||||||
|
test_line_count = 0 out &&
|
||||||
|
test_path_exists folder1/even/more/dirs/file &&
|
||||||
|
|
||||||
|
git sparse-checkout reapply &&
|
||||||
|
test_path_is_missing folder1
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
Reference in New Issue
Block a user