Merge branch 'ds/add-rm-with-sparse-index'
"git add", "git mv", and "git rm" have been adjusted to avoid updating paths outside of the sparse-checkout definition unless the user specifies a "--sparse" option. * ds/add-rm-with-sparse-index: advice: update message to suggest '--sparse' mv: refuse to move sparse paths rm: skip sparse paths with missing SKIP_WORKTREE rm: add --sparse option add: update --renormalize to skip sparse paths add: update --chmod to skip sparse paths add: implement the --sparse option add: skip tracked paths outside sparse-checkout cone add: fail when adding an untracked sparse file dir: fix pattern matching on dirs dir: select directories correctly t1092: behavior for adding sparse files t3705: test that 'sparse_entry' is unstaged
This commit is contained in:
commit
2d498a7c89
@ -9,7 +9,7 @@ SYNOPSIS
|
|||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
|
'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
|
||||||
[--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
|
[--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--sparse]
|
||||||
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
|
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
|
||||||
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
|
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
|
||||||
[--] [<pathspec>...]
|
[--] [<pathspec>...]
|
||||||
@ -79,6 +79,13 @@ in linkgit:gitglossary[7].
|
|||||||
--force::
|
--force::
|
||||||
Allow adding otherwise ignored files.
|
Allow adding otherwise ignored files.
|
||||||
|
|
||||||
|
--sparse::
|
||||||
|
Allow updating index entries outside of the sparse-checkout cone.
|
||||||
|
Normally, `git add` refuses to update index entries whose paths do
|
||||||
|
not fit within the sparse-checkout cone, since those files might
|
||||||
|
be removed from the working tree without warning. See
|
||||||
|
linkgit:git-sparse-checkout[1] for more details.
|
||||||
|
|
||||||
-i::
|
-i::
|
||||||
--interactive::
|
--interactive::
|
||||||
Add modified contents in the working tree interactively to
|
Add modified contents in the working tree interactively to
|
||||||
|
@ -72,6 +72,12 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
|
|||||||
--ignore-unmatch::
|
--ignore-unmatch::
|
||||||
Exit with a zero status even if no files matched.
|
Exit with a zero status even if no files matched.
|
||||||
|
|
||||||
|
--sparse::
|
||||||
|
Allow updating index entries outside of the sparse-checkout cone.
|
||||||
|
Normally, `git rm` refuses to update index entries whose paths do
|
||||||
|
not fit within the sparse-checkout cone. See
|
||||||
|
linkgit:git-sparse-checkout[1] for more.
|
||||||
|
|
||||||
-q::
|
-q::
|
||||||
--quiet::
|
--quiet::
|
||||||
`git rm` normally outputs one line (in the form of an `rm` command)
|
`git rm` normally outputs one line (in the form of an `rm` command)
|
||||||
|
11
advice.c
11
advice.c
@ -224,15 +224,16 @@ void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
|
|||||||
if (!pathspec_list->nr)
|
if (!pathspec_list->nr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fprintf(stderr, _("The following pathspecs didn't match any"
|
fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n"
|
||||||
" eligible path, but they do match index\n"
|
"outside of your sparse-checkout definition, so will not be\n"
|
||||||
"entries outside the current sparse checkout:\n"));
|
"updated in the index:\n"));
|
||||||
for_each_string_list_item(item, pathspec_list)
|
for_each_string_list_item(item, pathspec_list)
|
||||||
fprintf(stderr, "%s\n", item->string);
|
fprintf(stderr, "%s\n", item->string);
|
||||||
|
|
||||||
advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
|
advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
|
||||||
_("Disable or modify the sparsity rules if you intend"
|
_("If you intend to update such entries, try one of the following:\n"
|
||||||
" to update such entries."));
|
"* Use the --sparse option.\n"
|
||||||
|
"* Disable or modify the sparsity rules."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void detach_advice(const char *new_name)
|
void detach_advice(const char *new_name)
|
||||||
|
@ -30,6 +30,7 @@ static int patch_interactive, add_interactive, edit_interactive;
|
|||||||
static int take_worktree_changes;
|
static int take_worktree_changes;
|
||||||
static int add_renormalize;
|
static int add_renormalize;
|
||||||
static int pathspec_file_nul;
|
static int pathspec_file_nul;
|
||||||
|
static int include_sparse;
|
||||||
static const char *pathspec_from_file;
|
static const char *pathspec_from_file;
|
||||||
static int legacy_stash_p; /* support for the scripted `git stash` */
|
static int legacy_stash_p; /* support for the scripted `git stash` */
|
||||||
|
|
||||||
@ -46,7 +47,9 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
|
|||||||
struct cache_entry *ce = active_cache[i];
|
struct cache_entry *ce = active_cache[i];
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (ce_skip_worktree(ce))
|
if (!include_sparse &&
|
||||||
|
(ce_skip_worktree(ce) ||
|
||||||
|
!path_in_sparse_checkout(ce->name, &the_index)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
|
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
|
||||||
@ -94,6 +97,10 @@ static void update_callback(struct diff_queue_struct *q,
|
|||||||
for (i = 0; i < q->nr; i++) {
|
for (i = 0; i < q->nr; i++) {
|
||||||
struct diff_filepair *p = q->queue[i];
|
struct diff_filepair *p = q->queue[i];
|
||||||
const char *path = p->one->path;
|
const char *path = p->one->path;
|
||||||
|
|
||||||
|
if (!include_sparse && !path_in_sparse_checkout(path, &the_index))
|
||||||
|
continue;
|
||||||
|
|
||||||
switch (fix_unmerged_status(p, data)) {
|
switch (fix_unmerged_status(p, data)) {
|
||||||
default:
|
default:
|
||||||
die(_("unexpected diff status %c"), p->status);
|
die(_("unexpected diff status %c"), p->status);
|
||||||
@ -147,7 +154,9 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
|
|||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
struct cache_entry *ce = active_cache[i];
|
struct cache_entry *ce = active_cache[i];
|
||||||
|
|
||||||
if (ce_skip_worktree(ce))
|
if (!include_sparse &&
|
||||||
|
(ce_skip_worktree(ce) ||
|
||||||
|
!path_in_sparse_checkout(ce->name, &the_index)))
|
||||||
continue;
|
continue;
|
||||||
if (ce_stage(ce))
|
if (ce_stage(ce))
|
||||||
continue; /* do not touch unmerged paths */
|
continue; /* do not touch unmerged paths */
|
||||||
@ -377,6 +386,7 @@ static struct option builtin_add_options[] = {
|
|||||||
OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
|
OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
|
||||||
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
|
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
|
||||||
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
|
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
|
||||||
|
OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
|
||||||
OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
|
OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
|
||||||
N_("override the executable bit of the listed files")),
|
N_("override the executable bit of the listed files")),
|
||||||
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
|
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
|
||||||
@ -442,6 +452,7 @@ static void check_embedded_repo(const char *path)
|
|||||||
static int add_files(struct dir_struct *dir, int flags)
|
static int add_files(struct dir_struct *dir, int flags)
|
||||||
{
|
{
|
||||||
int i, exit_status = 0;
|
int i, exit_status = 0;
|
||||||
|
struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP;
|
||||||
|
|
||||||
if (dir->ignored_nr) {
|
if (dir->ignored_nr) {
|
||||||
fprintf(stderr, _(ignore_error));
|
fprintf(stderr, _(ignore_error));
|
||||||
@ -455,6 +466,12 @@ static int add_files(struct dir_struct *dir, int flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < dir->nr; i++) {
|
for (i = 0; i < dir->nr; i++) {
|
||||||
|
if (!include_sparse &&
|
||||||
|
!path_in_sparse_checkout(dir->entries[i]->name, &the_index)) {
|
||||||
|
string_list_append(&matched_sparse_paths,
|
||||||
|
dir->entries[i]->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
|
if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
|
||||||
if (!ignore_add_errors)
|
if (!ignore_add_errors)
|
||||||
die(_("adding files failed"));
|
die(_("adding files failed"));
|
||||||
@ -463,6 +480,14 @@ static int add_files(struct dir_struct *dir, int flags)
|
|||||||
check_embedded_repo(dir->entries[i]->name);
|
check_embedded_repo(dir->entries[i]->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matched_sparse_paths.nr) {
|
||||||
|
advise_on_updating_sparse_paths(&matched_sparse_paths);
|
||||||
|
exit_status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string_list_clear(&matched_sparse_paths, 0);
|
||||||
|
|
||||||
return exit_status;
|
return exit_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,7 +652,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
|||||||
if (seen[i])
|
if (seen[i])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
|
if (!include_sparse &&
|
||||||
|
matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
|
||||||
string_list_append(&only_match_skip_worktree,
|
string_list_append(&only_match_skip_worktree,
|
||||||
pathspec.items[i].original);
|
pathspec.items[i].original);
|
||||||
continue;
|
continue;
|
||||||
|
52
builtin/mv.c
52
builtin/mv.c
@ -118,21 +118,23 @@ static int index_range_of_same_dir(const char *src, int length,
|
|||||||
int cmd_mv(int argc, const char **argv, const char *prefix)
|
int cmd_mv(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int i, flags, gitmodules_modified = 0;
|
int i, flags, gitmodules_modified = 0;
|
||||||
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
|
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0;
|
||||||
struct option builtin_mv_options[] = {
|
struct option builtin_mv_options[] = {
|
||||||
OPT__VERBOSE(&verbose, N_("be verbose")),
|
OPT__VERBOSE(&verbose, N_("be verbose")),
|
||||||
OPT__DRY_RUN(&show_only, N_("dry run")),
|
OPT__DRY_RUN(&show_only, N_("dry run")),
|
||||||
OPT__FORCE(&force, N_("force move/rename even if target exists"),
|
OPT__FORCE(&force, N_("force move/rename even if target exists"),
|
||||||
PARSE_OPT_NOCOMPLETE),
|
PARSE_OPT_NOCOMPLETE),
|
||||||
OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
|
OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
|
||||||
|
OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
const char **source, **destination, **dest_path, **submodule_gitfile;
|
const char **source, **destination, **dest_path, **submodule_gitfile;
|
||||||
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
|
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
|
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
|
||||||
struct lock_file lock_file = LOCK_INIT;
|
struct lock_file lock_file = LOCK_INIT;
|
||||||
struct cache_entry *ce;
|
struct cache_entry *ce;
|
||||||
|
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
|
||||||
|
|
||||||
git_config(git_default_config, NULL);
|
git_config(git_default_config, NULL);
|
||||||
|
|
||||||
@ -176,14 +178,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
|
|||||||
const char *src = source[i], *dst = destination[i];
|
const char *src = source[i], *dst = destination[i];
|
||||||
int length, src_is_dir;
|
int length, src_is_dir;
|
||||||
const char *bad = NULL;
|
const char *bad = NULL;
|
||||||
|
int skip_sparse = 0;
|
||||||
|
|
||||||
if (show_only)
|
if (show_only)
|
||||||
printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
|
printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
|
||||||
|
|
||||||
length = strlen(src);
|
length = strlen(src);
|
||||||
if (lstat(src, &st) < 0)
|
if (lstat(src, &st) < 0) {
|
||||||
bad = _("bad source");
|
/* only error if existence is expected. */
|
||||||
else if (!strncmp(src, dst, length) &&
|
if (modes[i] != SPARSE)
|
||||||
|
bad = _("bad source");
|
||||||
|
} else if (!strncmp(src, dst, length) &&
|
||||||
(dst[length] == 0 || dst[length] == '/')) {
|
(dst[length] == 0 || dst[length] == '/')) {
|
||||||
bad = _("can not move directory into itself");
|
bad = _("can not move directory into itself");
|
||||||
} else if ((src_is_dir = S_ISDIR(st.st_mode))
|
} else if ((src_is_dir = S_ISDIR(st.st_mode))
|
||||||
@ -212,11 +217,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
|
|||||||
dst_len = strlen(dst);
|
dst_len = strlen(dst);
|
||||||
|
|
||||||
for (j = 0; j < last - first; j++) {
|
for (j = 0; j < last - first; j++) {
|
||||||
const char *path = active_cache[first + j]->name;
|
const struct cache_entry *ce = active_cache[first + j];
|
||||||
|
const char *path = ce->name;
|
||||||
source[argc + j] = path;
|
source[argc + j] = path;
|
||||||
destination[argc + j] =
|
destination[argc + j] =
|
||||||
prefix_path(dst, dst_len, path + length + 1);
|
prefix_path(dst, dst_len, path + length + 1);
|
||||||
modes[argc + j] = INDEX;
|
modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX;
|
||||||
submodule_gitfile[argc + j] = NULL;
|
submodule_gitfile[argc + j] = NULL;
|
||||||
}
|
}
|
||||||
argc += last - first;
|
argc += last - first;
|
||||||
@ -244,14 +250,36 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
|
|||||||
bad = _("multiple sources for the same target");
|
bad = _("multiple sources for the same target");
|
||||||
else if (is_dir_sep(dst[strlen(dst) - 1]))
|
else if (is_dir_sep(dst[strlen(dst) - 1]))
|
||||||
bad = _("destination directory does not exist");
|
bad = _("destination directory does not exist");
|
||||||
else
|
else {
|
||||||
|
/*
|
||||||
|
* We check if the paths are in the sparse-checkout
|
||||||
|
* definition as a very final check, since that
|
||||||
|
* allows us to point the user to the --sparse
|
||||||
|
* option as a way to have a successful run.
|
||||||
|
*/
|
||||||
|
if (!ignore_sparse &&
|
||||||
|
!path_in_sparse_checkout(src, &the_index)) {
|
||||||
|
string_list_append(&only_match_skip_worktree, src);
|
||||||
|
skip_sparse = 1;
|
||||||
|
}
|
||||||
|
if (!ignore_sparse &&
|
||||||
|
!path_in_sparse_checkout(dst, &the_index)) {
|
||||||
|
string_list_append(&only_match_skip_worktree, dst);
|
||||||
|
skip_sparse = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip_sparse)
|
||||||
|
goto remove_entry;
|
||||||
|
|
||||||
string_list_insert(&src_for_dst, dst);
|
string_list_insert(&src_for_dst, dst);
|
||||||
|
}
|
||||||
|
|
||||||
if (!bad)
|
if (!bad)
|
||||||
continue;
|
continue;
|
||||||
if (!ignore_errors)
|
if (!ignore_errors)
|
||||||
die(_("%s, source=%s, destination=%s"),
|
die(_("%s, source=%s, destination=%s"),
|
||||||
bad, src, dst);
|
bad, src, dst);
|
||||||
|
remove_entry:
|
||||||
if (--argc > 0) {
|
if (--argc > 0) {
|
||||||
int n = argc - i;
|
int n = argc - i;
|
||||||
memmove(source + i, source + i + 1,
|
memmove(source + i, source + i + 1,
|
||||||
@ -266,6 +294,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (only_match_skip_worktree.nr) {
|
||||||
|
advise_on_updating_sparse_paths(&only_match_skip_worktree);
|
||||||
|
if (!ignore_errors)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < argc; i++) {
|
for (i = 0; i < argc; i++) {
|
||||||
const char *src = source[i], *dst = destination[i];
|
const char *src = source[i], *dst = destination[i];
|
||||||
enum update_mode mode = modes[i];
|
enum update_mode mode = modes[i];
|
||||||
@ -274,7 +308,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
|
|||||||
printf(_("Renaming %s to %s\n"), src, dst);
|
printf(_("Renaming %s to %s\n"), src, dst);
|
||||||
if (show_only)
|
if (show_only)
|
||||||
continue;
|
continue;
|
||||||
if (mode != INDEX && rename(src, dst) < 0) {
|
if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) {
|
||||||
if (ignore_errors)
|
if (ignore_errors)
|
||||||
continue;
|
continue;
|
||||||
die_errno(_("renaming '%s' failed"), src);
|
die_errno(_("renaming '%s' failed"), src);
|
||||||
|
10
builtin/rm.c
10
builtin/rm.c
@ -237,6 +237,7 @@ static int check_local_mod(struct object_id *head, int index_only)
|
|||||||
|
|
||||||
static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
|
static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
|
||||||
static int ignore_unmatch = 0, pathspec_file_nul;
|
static int ignore_unmatch = 0, pathspec_file_nul;
|
||||||
|
static int include_sparse;
|
||||||
static char *pathspec_from_file;
|
static char *pathspec_from_file;
|
||||||
|
|
||||||
static struct option builtin_rm_options[] = {
|
static struct option builtin_rm_options[] = {
|
||||||
@ -247,6 +248,7 @@ static struct option builtin_rm_options[] = {
|
|||||||
OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")),
|
OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")),
|
||||||
OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
|
OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
|
||||||
N_("exit with a zero status even if nothing matched")),
|
N_("exit with a zero status even if nothing matched")),
|
||||||
|
OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
|
||||||
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
|
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
|
||||||
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
|
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
@ -298,7 +300,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
|||||||
ensure_full_index(&the_index);
|
ensure_full_index(&the_index);
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
const struct cache_entry *ce = active_cache[i];
|
const struct cache_entry *ce = active_cache[i];
|
||||||
if (ce_skip_worktree(ce))
|
|
||||||
|
if (!include_sparse &&
|
||||||
|
(ce_skip_worktree(ce) ||
|
||||||
|
!path_in_sparse_checkout(ce->name, &the_index)))
|
||||||
continue;
|
continue;
|
||||||
if (!ce_path_match(&the_index, ce, &pathspec, seen))
|
if (!ce_path_match(&the_index, ce, &pathspec, seen))
|
||||||
continue;
|
continue;
|
||||||
@ -322,7 +327,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
|||||||
seen_any = 1;
|
seen_any = 1;
|
||||||
else if (ignore_unmatch)
|
else if (ignore_unmatch)
|
||||||
continue;
|
continue;
|
||||||
else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
|
else if (!include_sparse &&
|
||||||
|
matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
|
||||||
string_list_append(&only_match_skip_worktree, original);
|
string_list_append(&only_match_skip_worktree, original);
|
||||||
else
|
else
|
||||||
die(_("pathspec '%s' did not match any files"), original);
|
die(_("pathspec '%s' did not match any files"), original);
|
||||||
|
56
dir.c
56
dir.c
@ -1294,7 +1294,7 @@ int match_pathname(const char *pathname, int pathlen,
|
|||||||
* then our prefix match is all we need; we
|
* then our prefix match is all we need; we
|
||||||
* do not need to call fnmatch at all.
|
* do not need to call fnmatch at all.
|
||||||
*/
|
*/
|
||||||
if (!patternlen && !namelen)
|
if (!patternlen && (!namelen || (flags & PATTERN_FLAG_MUSTBEDIR)))
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1303,6 +1303,44 @@ int match_pathname(const char *pathname, int pathlen,
|
|||||||
WM_PATHNAME) == 0;
|
WM_PATHNAME) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int path_matches_dir_pattern(const char *pathname,
|
||||||
|
int pathlen,
|
||||||
|
struct strbuf **path_parent,
|
||||||
|
int *dtype,
|
||||||
|
struct path_pattern *pattern,
|
||||||
|
struct index_state *istate)
|
||||||
|
{
|
||||||
|
if (!*path_parent) {
|
||||||
|
char *slash;
|
||||||
|
CALLOC_ARRAY(*path_parent, 1);
|
||||||
|
strbuf_add(*path_parent, pathname, pathlen);
|
||||||
|
slash = find_last_dir_sep((*path_parent)->buf);
|
||||||
|
|
||||||
|
if (slash)
|
||||||
|
strbuf_setlen(*path_parent, slash - (*path_parent)->buf);
|
||||||
|
else
|
||||||
|
strbuf_setlen(*path_parent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the parent directory matches the pattern, then we do not
|
||||||
|
* need to check for dtype.
|
||||||
|
*/
|
||||||
|
if ((*path_parent)->len &&
|
||||||
|
match_pathname((*path_parent)->buf, (*path_parent)->len,
|
||||||
|
pattern->base,
|
||||||
|
pattern->baselen ? pattern->baselen - 1 : 0,
|
||||||
|
pattern->pattern, pattern->nowildcardlen,
|
||||||
|
pattern->patternlen, pattern->flags))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
*dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
|
||||||
|
if (*dtype != DT_DIR)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scan the given exclude list in reverse to see whether pathname
|
* Scan the given exclude list in reverse to see whether pathname
|
||||||
* should be ignored. The first match (i.e. the last on the list), if
|
* should be ignored. The first match (i.e. the last on the list), if
|
||||||
@ -1318,6 +1356,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
|
|||||||
{
|
{
|
||||||
struct path_pattern *res = NULL; /* undecided */
|
struct path_pattern *res = NULL; /* undecided */
|
||||||
int i;
|
int i;
|
||||||
|
struct strbuf *path_parent = NULL;
|
||||||
|
|
||||||
if (!pl->nr)
|
if (!pl->nr)
|
||||||
return NULL; /* undefined */
|
return NULL; /* undefined */
|
||||||
@ -1327,11 +1366,10 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
|
|||||||
const char *exclude = pattern->pattern;
|
const char *exclude = pattern->pattern;
|
||||||
int prefix = pattern->nowildcardlen;
|
int prefix = pattern->nowildcardlen;
|
||||||
|
|
||||||
if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) {
|
if (pattern->flags & PATTERN_FLAG_MUSTBEDIR &&
|
||||||
*dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
|
!path_matches_dir_pattern(pathname, pathlen, &path_parent,
|
||||||
if (*dtype != DT_DIR)
|
dtype, pattern, istate))
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern->flags & PATTERN_FLAG_NODIR) {
|
if (pattern->flags & PATTERN_FLAG_NODIR) {
|
||||||
if (match_basename(basename,
|
if (match_basename(basename,
|
||||||
@ -1355,6 +1393,12 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path_parent) {
|
||||||
|
strbuf_release(path_parent);
|
||||||
|
free(path_parent);
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
|||||||
return;
|
return;
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
const struct cache_entry *ce = istate->cache[i];
|
const struct cache_entry *ce = istate->cache[i];
|
||||||
if (sw_action == PS_IGNORE_SKIP_WORKTREE && ce_skip_worktree(ce))
|
if (sw_action == PS_IGNORE_SKIP_WORKTREE &&
|
||||||
|
(ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate)))
|
||||||
continue;
|
continue;
|
||||||
ce_path_match(istate, ce, pathspec, seen);
|
ce_path_match(istate, ce, pathspec, seen);
|
||||||
}
|
}
|
||||||
@ -70,7 +71,7 @@ char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec)
|
|||||||
|
|
||||||
for (i = 0; i < istate->cache_nr; i++) {
|
for (i = 0; i < istate->cache_nr; i++) {
|
||||||
struct cache_entry *ce = istate->cache[i];
|
struct cache_entry *ce = istate->cache[i];
|
||||||
if (ce_skip_worktree(ce))
|
if (ce_skip_worktree(ce) || !path_in_sparse_checkout(ce->name, istate))
|
||||||
ce_path_match(istate, ce, pathspec, seen);
|
ce_path_match(istate, ce, pathspec, seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,7 +411,7 @@ test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged stat
|
|||||||
git -C unmerged sparse-checkout disable
|
git -C unmerged sparse-checkout disable
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'sparse-checkout reapply' '
|
test_expect_failure 'sparse-checkout reapply' '
|
||||||
git clone repo tweak &&
|
git clone repo tweak &&
|
||||||
|
|
||||||
echo dirty >tweak/deep/deeper2/a &&
|
echo dirty >tweak/deep/deeper2/a &&
|
||||||
@ -443,6 +443,8 @@ test_expect_success 'sparse-checkout reapply' '
|
|||||||
test_i18ngrep "warning.*The following paths are unmerged" err &&
|
test_i18ngrep "warning.*The following paths are unmerged" err &&
|
||||||
test_path_is_file tweak/folder1/a &&
|
test_path_is_file tweak/folder1/a &&
|
||||||
|
|
||||||
|
# NEEDSWORK: We are asking to update a file outside of the
|
||||||
|
# sparse-checkout cone, but this is no longer allowed.
|
||||||
git -C tweak add folder1/a &&
|
git -C tweak add folder1/a &&
|
||||||
git -C tweak sparse-checkout reapply 2>err &&
|
git -C tweak sparse-checkout reapply 2>err &&
|
||||||
test_must_be_empty err &&
|
test_must_be_empty err &&
|
||||||
|
@ -187,6 +187,16 @@ test_sparse_match () {
|
|||||||
test_cmp sparse-checkout-err sparse-index-err
|
test_cmp sparse-checkout-err sparse-index-err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_sparse_unstaged () {
|
||||||
|
file=$1 &&
|
||||||
|
for repo in sparse-checkout sparse-index
|
||||||
|
do
|
||||||
|
# Skip "unmerged" paths
|
||||||
|
git -C $repo diff --staged --diff-filter=u -- "$file" >diff &&
|
||||||
|
test_must_be_empty diff || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
test_expect_success 'sparse-index contents' '
|
test_expect_success 'sparse-index contents' '
|
||||||
init_repos &&
|
init_repos &&
|
||||||
|
|
||||||
@ -291,6 +301,20 @@ test_expect_success 'add, commit, checkout' '
|
|||||||
test_all_match git checkout -
|
test_all_match git checkout -
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'add outside sparse cone' '
|
||||||
|
init_repos &&
|
||||||
|
|
||||||
|
run_on_sparse mkdir folder1 &&
|
||||||
|
run_on_sparse ../edit-contents folder1/a &&
|
||||||
|
run_on_sparse ../edit-contents folder1/newfile &&
|
||||||
|
test_sparse_match test_must_fail git add folder1/a &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder1/a &&
|
||||||
|
test_sparse_match test_must_fail git add folder1/newfile &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder1/newfile
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'commit including unstaged changes' '
|
test_expect_success 'commit including unstaged changes' '
|
||||||
init_repos &&
|
init_repos &&
|
||||||
|
|
||||||
@ -339,18 +363,24 @@ test_expect_success 'status/add: outside sparse cone' '
|
|||||||
|
|
||||||
# Adding the path outside of the sparse-checkout cone should fail.
|
# Adding the path outside of the sparse-checkout cone should fail.
|
||||||
test_sparse_match test_must_fail git add folder1/a &&
|
test_sparse_match test_must_fail git add folder1/a &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder1/a &&
|
||||||
test_sparse_match test_must_fail git add --refresh folder1/a &&
|
test_sparse_match test_must_fail git add --refresh folder1/a &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder1/a &&
|
||||||
|
test_sparse_match test_must_fail git add folder1/new &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder1/new &&
|
||||||
|
test_sparse_match git add --sparse folder1/a &&
|
||||||
|
test_sparse_match git add --sparse folder1/new &&
|
||||||
|
|
||||||
# NEEDSWORK: Adding a newly-tracked file outside the cone succeeds
|
test_all_match git add --sparse . &&
|
||||||
test_sparse_match git add folder1/new &&
|
|
||||||
|
|
||||||
test_all_match git add . &&
|
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
test_all_match git commit -m folder1/new &&
|
test_all_match git commit -m folder1/new &&
|
||||||
test_all_match git rev-parse HEAD^{tree} &&
|
test_all_match git rev-parse HEAD^{tree} &&
|
||||||
|
|
||||||
run_on_all ../edit-contents folder1/newer &&
|
run_on_all ../edit-contents folder1/newer &&
|
||||||
test_all_match git add folder1/ &&
|
test_all_match git add --sparse folder1/ &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
test_all_match git commit -m folder1/newer &&
|
test_all_match git commit -m folder1/newer &&
|
||||||
test_all_match git rev-parse HEAD^{tree}
|
test_all_match git rev-parse HEAD^{tree}
|
||||||
@ -494,11 +524,6 @@ test_expect_success 'merge, cherry-pick, and rebase' '
|
|||||||
done
|
done
|
||||||
'
|
'
|
||||||
|
|
||||||
# NEEDSWORK: This test is documenting current behavior, but that
|
|
||||||
# behavior can be confusing to users so there is desire to change it.
|
|
||||||
# Right now, users might be using this flow to work through conflicts,
|
|
||||||
# so any solution should present advice to users who try this sequence
|
|
||||||
# of commands to follow whatever new method we create.
|
|
||||||
test_expect_success 'merge with conflict outside cone' '
|
test_expect_success 'merge with conflict outside cone' '
|
||||||
init_repos &&
|
init_repos &&
|
||||||
|
|
||||||
@ -513,13 +538,19 @@ test_expect_success 'merge with conflict outside cone' '
|
|||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
|
||||||
# 2. Add the file with conflict markers
|
# 2. Add the file with conflict markers
|
||||||
test_all_match git add folder1/a &&
|
test_sparse_match test_must_fail git add folder1/a &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder1/a &&
|
||||||
|
test_all_match git add --sparse folder1/a &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
|
||||||
# 3. Rename the file to another sparse filename and
|
# 3. Rename the file to another sparse filename and
|
||||||
# accept conflict markers as resolved content.
|
# accept conflict markers as resolved content.
|
||||||
run_on_all mv folder2/a folder2/z &&
|
run_on_all mv folder2/a folder2/z &&
|
||||||
test_all_match git add folder2 &&
|
test_sparse_match test_must_fail git add folder2 &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder2/z &&
|
||||||
|
test_all_match git add --sparse folder2 &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
|
||||||
test_all_match git merge --continue &&
|
test_all_match git merge --continue &&
|
||||||
@ -544,13 +575,25 @@ test_expect_success 'cherry-pick/rebase with conflict outside cone' '
|
|||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
|
||||||
# 2. Add the file with conflict markers
|
# 2. Add the file with conflict markers
|
||||||
test_all_match git add folder1/a &&
|
# NEEDSWORK: Even though the merge conflict removed the
|
||||||
|
# SKIP_WORKTREE bit from the index entry for folder1/a, we should
|
||||||
|
# warn that this is a problematic add.
|
||||||
|
test_sparse_match test_must_fail git add folder1/a &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder1/a &&
|
||||||
|
test_all_match git add --sparse folder1/a &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
|
||||||
# 3. Rename the file to another sparse filename and
|
# 3. Rename the file to another sparse filename and
|
||||||
# accept conflict markers as resolved content.
|
# accept conflict markers as resolved content.
|
||||||
|
# NEEDSWORK: This mode now fails, because folder2/z is
|
||||||
|
# outside of the sparse-checkout cone and does not match an
|
||||||
|
# existing index entry with the SKIP_WORKTREE bit cleared.
|
||||||
run_on_all mv folder2/a folder2/z &&
|
run_on_all mv folder2/a folder2/z &&
|
||||||
test_all_match git add folder2 &&
|
test_sparse_match test_must_fail git add folder2 &&
|
||||||
|
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
|
||||||
|
test_sparse_unstaged folder2/z &&
|
||||||
|
test_all_match git add --sparse folder2 &&
|
||||||
test_all_match git status --porcelain=v2 &&
|
test_all_match git status --porcelain=v2 &&
|
||||||
|
|
||||||
test_all_match git $OPERATION --continue &&
|
test_all_match git $OPERATION --continue &&
|
||||||
@ -626,6 +669,7 @@ test_expect_success 'clean' '
|
|||||||
test_expect_success 'submodule handling' '
|
test_expect_success 'submodule handling' '
|
||||||
init_repos &&
|
init_repos &&
|
||||||
|
|
||||||
|
test_sparse_match git sparse-checkout add modules &&
|
||||||
test_all_match mkdir modules &&
|
test_all_match mkdir modules &&
|
||||||
test_all_match touch modules/a &&
|
test_all_match touch modules/a &&
|
||||||
test_all_match git add modules &&
|
test_all_match git add modules &&
|
||||||
@ -635,6 +679,7 @@ test_expect_success 'submodule handling' '
|
|||||||
test_all_match git commit -m "add submodule" &&
|
test_all_match git commit -m "add submodule" &&
|
||||||
|
|
||||||
# having a submodule prevents "modules" from collapse
|
# having a submodule prevents "modules" from collapse
|
||||||
|
test_sparse_match git sparse-checkout set deep/deeper1 &&
|
||||||
test-tool -C sparse-index read-cache --table >cache &&
|
test-tool -C sparse-index read-cache --table >cache &&
|
||||||
grep "100644 blob .* modules/a" cache &&
|
grep "100644 blob .* modules/a" cache &&
|
||||||
grep "160000 commit $(git -C initial-repo rev-parse HEAD) modules/sub" cache
|
grep "160000 commit $(git -C initial-repo rev-parse HEAD) modules/sub" cache
|
||||||
|
@ -11,12 +11,15 @@ test_expect_success 'setup' "
|
|||||||
git commit -m files &&
|
git commit -m files &&
|
||||||
|
|
||||||
cat >sparse_error_header <<-EOF &&
|
cat >sparse_error_header <<-EOF &&
|
||||||
The following pathspecs didn't match any eligible path, but they do match index
|
The following paths and/or pathspecs matched paths that exist
|
||||||
entries outside the current sparse checkout:
|
outside of your sparse-checkout definition, so will not be
|
||||||
|
updated in the index:
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat >sparse_hint <<-EOF &&
|
cat >sparse_hint <<-EOF &&
|
||||||
hint: Disable or modify the sparsity rules if you intend to update such entries.
|
hint: If you intend to update such entries, try one of the following:
|
||||||
|
hint: * Use the --sparse option.
|
||||||
|
hint: * Disable or modify the sparsity rules.
|
||||||
hint: Disable this message with \"git config advice.updateSparsePath false\"
|
hint: Disable this message with \"git config advice.updateSparsePath false\"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@ -37,9 +40,25 @@ done
|
|||||||
test_expect_success 'recursive rm does not remove sparse entries' '
|
test_expect_success 'recursive rm does not remove sparse entries' '
|
||||||
git reset --hard &&
|
git reset --hard &&
|
||||||
git sparse-checkout set sub/dir &&
|
git sparse-checkout set sub/dir &&
|
||||||
git rm -r sub &&
|
test_must_fail git rm -r sub &&
|
||||||
|
git rm --sparse -r sub &&
|
||||||
git status --porcelain -uno >actual &&
|
git status --porcelain -uno >actual &&
|
||||||
echo "D sub/dir/e" >expected &&
|
cat >expected <<-\EOF &&
|
||||||
|
D sub/d
|
||||||
|
D sub/dir/e
|
||||||
|
EOF
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'recursive rm --sparse removes sparse entries' '
|
||||||
|
git reset --hard &&
|
||||||
|
git sparse-checkout set "sub/dir" &&
|
||||||
|
git rm --sparse -r sub &&
|
||||||
|
git status --porcelain -uno >actual &&
|
||||||
|
cat >expected <<-\EOF &&
|
||||||
|
D sub/d
|
||||||
|
D sub/dir/e
|
||||||
|
EOF
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
@ -75,4 +94,15 @@ test_expect_success 'do not warn about sparse entries with --ignore-unmatch' '
|
|||||||
git ls-files --error-unmatch b
|
git ls-files --error-unmatch b
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'refuse to rm a non-skip-worktree path outside sparse cone' '
|
||||||
|
git reset --hard &&
|
||||||
|
git sparse-checkout set a &&
|
||||||
|
git update-index --no-skip-worktree b &&
|
||||||
|
test_must_fail git rm b 2>stderr &&
|
||||||
|
test_cmp b_error_and_hint stderr &&
|
||||||
|
git rm --sparse b 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
test_path_is_missing b
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -19,6 +19,7 @@ setup_sparse_entry () {
|
|||||||
fi &&
|
fi &&
|
||||||
git add sparse_entry &&
|
git add sparse_entry &&
|
||||||
git update-index --skip-worktree sparse_entry &&
|
git update-index --skip-worktree sparse_entry &&
|
||||||
|
git commit --allow-empty -m "ensure sparse_entry exists at HEAD" &&
|
||||||
SPARSE_ENTRY_BLOB=$(git rev-parse :sparse_entry)
|
SPARSE_ENTRY_BLOB=$(git rev-parse :sparse_entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,14 +37,22 @@ setup_gitignore () {
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_sparse_entry_unstaged () {
|
||||||
|
git diff --staged -- sparse_entry >diff &&
|
||||||
|
test_must_be_empty diff
|
||||||
|
}
|
||||||
|
|
||||||
test_expect_success 'setup' "
|
test_expect_success 'setup' "
|
||||||
cat >sparse_error_header <<-EOF &&
|
cat >sparse_error_header <<-EOF &&
|
||||||
The following pathspecs didn't match any eligible path, but they do match index
|
The following paths and/or pathspecs matched paths that exist
|
||||||
entries outside the current sparse checkout:
|
outside of your sparse-checkout definition, so will not be
|
||||||
|
updated in the index:
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat >sparse_hint <<-EOF &&
|
cat >sparse_hint <<-EOF &&
|
||||||
hint: Disable or modify the sparsity rules if you intend to update such entries.
|
hint: If you intend to update such entries, try one of the following:
|
||||||
|
hint: * Use the --sparse option.
|
||||||
|
hint: * Disable or modify the sparsity rules.
|
||||||
hint: Disable this message with \"git config advice.updateSparsePath false\"
|
hint: Disable this message with \"git config advice.updateSparsePath false\"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@ -55,6 +64,7 @@ test_expect_success 'git add does not remove sparse entries' '
|
|||||||
setup_sparse_entry &&
|
setup_sparse_entry &&
|
||||||
rm sparse_entry &&
|
rm sparse_entry &&
|
||||||
test_must_fail git add sparse_entry 2>stderr &&
|
test_must_fail git add sparse_entry 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
test_cmp error_and_hint stderr &&
|
test_cmp error_and_hint stderr &&
|
||||||
test_sparse_entry_unchanged
|
test_sparse_entry_unchanged
|
||||||
'
|
'
|
||||||
@ -73,6 +83,7 @@ test_expect_success 'git add . does not remove sparse entries' '
|
|||||||
rm sparse_entry &&
|
rm sparse_entry &&
|
||||||
setup_gitignore &&
|
setup_gitignore &&
|
||||||
test_must_fail git add . 2>stderr &&
|
test_must_fail git add . 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
|
|
||||||
cat sparse_error_header >expect &&
|
cat sparse_error_header >expect &&
|
||||||
echo . >>expect &&
|
echo . >>expect &&
|
||||||
@ -88,6 +99,7 @@ do
|
|||||||
setup_sparse_entry &&
|
setup_sparse_entry &&
|
||||||
echo modified >sparse_entry &&
|
echo modified >sparse_entry &&
|
||||||
test_must_fail git add $opt sparse_entry 2>stderr &&
|
test_must_fail git add $opt sparse_entry 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
test_cmp error_and_hint stderr &&
|
test_cmp error_and_hint stderr &&
|
||||||
test_sparse_entry_unchanged
|
test_sparse_entry_unchanged
|
||||||
'
|
'
|
||||||
@ -98,6 +110,7 @@ test_expect_success 'git add --refresh does not update sparse entries' '
|
|||||||
git ls-files --debug sparse_entry | grep mtime >before &&
|
git ls-files --debug sparse_entry | grep mtime >before &&
|
||||||
test-tool chmtime -60 sparse_entry &&
|
test-tool chmtime -60 sparse_entry &&
|
||||||
test_must_fail git add --refresh sparse_entry 2>stderr &&
|
test_must_fail git add --refresh sparse_entry 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
test_cmp error_and_hint stderr &&
|
test_cmp error_and_hint stderr &&
|
||||||
git ls-files --debug sparse_entry | grep mtime >after &&
|
git ls-files --debug sparse_entry | grep mtime >after &&
|
||||||
test_cmp before after
|
test_cmp before after
|
||||||
@ -106,6 +119,7 @@ test_expect_success 'git add --refresh does not update sparse entries' '
|
|||||||
test_expect_success 'git add --chmod does not update sparse entries' '
|
test_expect_success 'git add --chmod does not update sparse entries' '
|
||||||
setup_sparse_entry &&
|
setup_sparse_entry &&
|
||||||
test_must_fail git add --chmod=+x sparse_entry 2>stderr &&
|
test_must_fail git add --chmod=+x sparse_entry 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
test_cmp error_and_hint stderr &&
|
test_cmp error_and_hint stderr &&
|
||||||
test_sparse_entry_unchanged &&
|
test_sparse_entry_unchanged &&
|
||||||
! test -x sparse_entry
|
! test -x sparse_entry
|
||||||
@ -116,6 +130,7 @@ test_expect_success 'git add --renormalize does not update sparse entries' '
|
|||||||
setup_sparse_entry "LINEONE\r\nLINETWO\r\n" &&
|
setup_sparse_entry "LINEONE\r\nLINETWO\r\n" &&
|
||||||
echo "sparse_entry text=auto" >.gitattributes &&
|
echo "sparse_entry text=auto" >.gitattributes &&
|
||||||
test_must_fail git add --renormalize sparse_entry 2>stderr &&
|
test_must_fail git add --renormalize sparse_entry 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
test_cmp error_and_hint stderr &&
|
test_cmp error_and_hint stderr &&
|
||||||
test_sparse_entry_unchanged
|
test_sparse_entry_unchanged
|
||||||
'
|
'
|
||||||
@ -124,6 +139,7 @@ test_expect_success 'git add --dry-run --ignore-missing warn on sparse path' '
|
|||||||
setup_sparse_entry &&
|
setup_sparse_entry &&
|
||||||
rm sparse_entry &&
|
rm sparse_entry &&
|
||||||
test_must_fail git add --dry-run --ignore-missing sparse_entry 2>stderr &&
|
test_must_fail git add --dry-run --ignore-missing sparse_entry 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
test_cmp error_and_hint stderr &&
|
test_cmp error_and_hint stderr &&
|
||||||
test_sparse_entry_unchanged
|
test_sparse_entry_unchanged
|
||||||
'
|
'
|
||||||
@ -145,11 +161,57 @@ test_expect_success 'do not warn when pathspec matches dense entries' '
|
|||||||
git ls-files --error-unmatch dense_entry
|
git ls-files --error-unmatch dense_entry
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git add fails outside of sparse-checkout definition' '
|
||||||
|
test_when_finished git sparse-checkout disable &&
|
||||||
|
test_commit a &&
|
||||||
|
git sparse-checkout init &&
|
||||||
|
git sparse-checkout set a &&
|
||||||
|
echo >>sparse_entry &&
|
||||||
|
|
||||||
|
git update-index --no-skip-worktree sparse_entry &&
|
||||||
|
test_must_fail git add sparse_entry &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
|
|
||||||
|
test_must_fail git add --chmod=+x sparse_entry &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
|
|
||||||
|
test_must_fail git add --renormalize sparse_entry &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
|
|
||||||
|
# Avoid munging CRLFs to avoid an error message
|
||||||
|
git -c core.autocrlf=input add --sparse sparse_entry 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
test-tool read-cache --table >actual &&
|
||||||
|
grep "^100644 blob.*sparse_entry\$" actual &&
|
||||||
|
|
||||||
|
git add --sparse --chmod=+x sparse_entry 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
test-tool read-cache --table >actual &&
|
||||||
|
grep "^100755 blob.*sparse_entry\$" actual &&
|
||||||
|
|
||||||
|
git reset &&
|
||||||
|
|
||||||
|
# This will print a message over stderr on Windows.
|
||||||
|
git add --sparse --renormalize sparse_entry &&
|
||||||
|
git status --porcelain >actual &&
|
||||||
|
grep "^M sparse_entry\$" actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'add obeys advice.updateSparsePath' '
|
test_expect_success 'add obeys advice.updateSparsePath' '
|
||||||
setup_sparse_entry &&
|
setup_sparse_entry &&
|
||||||
test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr &&
|
test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr &&
|
||||||
|
test_sparse_entry_unstaged &&
|
||||||
test_cmp sparse_entry_error stderr
|
test_cmp sparse_entry_error stderr
|
||||||
|
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'add allows sparse entries with --sparse' '
|
||||||
|
git sparse-checkout set a &&
|
||||||
|
echo modified >sparse_entry &&
|
||||||
|
test_must_fail git add sparse_entry &&
|
||||||
|
test_sparse_entry_unchanged &&
|
||||||
|
git add --sparse sparse_entry 2>stderr &&
|
||||||
|
test_must_be_empty stderr
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
189
t/t7002-mv-sparse-checkout.sh
Executable file
189
t/t7002-mv-sparse-checkout.sh
Executable file
@ -0,0 +1,189 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='git mv in sparse working trees'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup' "
|
||||||
|
mkdir -p sub/dir sub/dir2 &&
|
||||||
|
touch a b c sub/d sub/dir/e sub/dir2/e &&
|
||||||
|
git add -A &&
|
||||||
|
git commit -m files &&
|
||||||
|
|
||||||
|
cat >sparse_error_header <<-EOF &&
|
||||||
|
The following paths and/or pathspecs matched paths that exist
|
||||||
|
outside of your sparse-checkout definition, so will not be
|
||||||
|
updated in the index:
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >sparse_hint <<-EOF
|
||||||
|
hint: If you intend to update such entries, try one of the following:
|
||||||
|
hint: * Use the --sparse option.
|
||||||
|
hint: * Disable or modify the sparsity rules.
|
||||||
|
hint: Disable this message with \"git config advice.updateSparsePath false\"
|
||||||
|
EOF
|
||||||
|
"
|
||||||
|
|
||||||
|
test_expect_success 'mv refuses to move sparse-to-sparse' '
|
||||||
|
test_when_finished rm -f e &&
|
||||||
|
git reset --hard &&
|
||||||
|
git sparse-checkout set a &&
|
||||||
|
touch b &&
|
||||||
|
test_must_fail git mv b e 2>stderr &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
echo b >>expect &&
|
||||||
|
echo e >>expect &&
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
git mv --sparse b e 2>stderr &&
|
||||||
|
test_must_be_empty stderr
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'mv refuses to move sparse-to-sparse, ignores failure' '
|
||||||
|
test_when_finished rm -f b c e &&
|
||||||
|
git reset --hard &&
|
||||||
|
git sparse-checkout set a &&
|
||||||
|
|
||||||
|
# tracked-to-untracked
|
||||||
|
touch b &&
|
||||||
|
git mv -k b e 2>stderr &&
|
||||||
|
test_path_exists b &&
|
||||||
|
test_path_is_missing e &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
echo b >>expect &&
|
||||||
|
echo e >>expect &&
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
|
||||||
|
git mv --sparse b e 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
test_path_is_missing b &&
|
||||||
|
test_path_exists e &&
|
||||||
|
|
||||||
|
# tracked-to-tracked
|
||||||
|
git reset --hard &&
|
||||||
|
touch b &&
|
||||||
|
git mv -k b c 2>stderr &&
|
||||||
|
test_path_exists b &&
|
||||||
|
test_path_is_missing c &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
echo b >>expect &&
|
||||||
|
echo c >>expect &&
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
|
||||||
|
git mv --sparse b c 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
test_path_is_missing b &&
|
||||||
|
test_path_exists c
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'mv refuses to move non-sparse-to-sparse' '
|
||||||
|
test_when_finished rm -f b c e &&
|
||||||
|
git reset --hard &&
|
||||||
|
git sparse-checkout set a &&
|
||||||
|
|
||||||
|
# tracked-to-untracked
|
||||||
|
test_must_fail git mv a e 2>stderr &&
|
||||||
|
test_path_exists a &&
|
||||||
|
test_path_is_missing e &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
echo e >>expect &&
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
git mv --sparse a e 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
test_path_is_missing a &&
|
||||||
|
test_path_exists e &&
|
||||||
|
|
||||||
|
# tracked-to-tracked
|
||||||
|
rm e &&
|
||||||
|
git reset --hard &&
|
||||||
|
test_must_fail git mv a c 2>stderr &&
|
||||||
|
test_path_exists a &&
|
||||||
|
test_path_is_missing c &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
echo c >>expect &&
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
git mv --sparse a c 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
test_path_is_missing a &&
|
||||||
|
test_path_exists c
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'mv refuses to move sparse-to-non-sparse' '
|
||||||
|
test_when_finished rm -f b c e &&
|
||||||
|
git reset --hard &&
|
||||||
|
git sparse-checkout set a e &&
|
||||||
|
|
||||||
|
# tracked-to-untracked
|
||||||
|
touch b &&
|
||||||
|
test_must_fail git mv b e 2>stderr &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
echo b >>expect &&
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
git mv --sparse b e 2>stderr &&
|
||||||
|
test_must_be_empty stderr
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'recursive mv refuses to move (possible) sparse' '
|
||||||
|
test_when_finished rm -rf b c e sub2 &&
|
||||||
|
git reset --hard &&
|
||||||
|
# Without cone mode, "sub" and "sub2" do not match
|
||||||
|
git sparse-checkout set sub/dir sub2/dir &&
|
||||||
|
|
||||||
|
# Add contained contents to ensure we avoid non-existence errors
|
||||||
|
mkdir sub/dir2 &&
|
||||||
|
touch sub/d sub/dir2/e &&
|
||||||
|
|
||||||
|
test_must_fail git mv sub sub2 2>stderr &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
cat >>expect <<-\EOF &&
|
||||||
|
sub/d
|
||||||
|
sub2/d
|
||||||
|
sub/dir/e
|
||||||
|
sub2/dir/e
|
||||||
|
sub/dir2/e
|
||||||
|
sub2/dir2/e
|
||||||
|
EOF
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
git mv --sparse sub sub2 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
git commit -m "moved sub to sub2" &&
|
||||||
|
git rev-parse HEAD~1:sub >expect &&
|
||||||
|
git rev-parse HEAD:sub2 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'recursive mv refuses to move sparse' '
|
||||||
|
git reset --hard &&
|
||||||
|
# Use cone mode so "sub/" matches the sparse-checkout patterns
|
||||||
|
git sparse-checkout init --cone &&
|
||||||
|
git sparse-checkout set sub/dir sub2/dir &&
|
||||||
|
|
||||||
|
# Add contained contents to ensure we avoid non-existence errors
|
||||||
|
mkdir sub/dir2 &&
|
||||||
|
touch sub/dir2/e &&
|
||||||
|
|
||||||
|
test_must_fail git mv sub sub2 2>stderr &&
|
||||||
|
cat sparse_error_header >expect &&
|
||||||
|
cat >>expect <<-\EOF &&
|
||||||
|
sub/dir2/e
|
||||||
|
sub2/dir2/e
|
||||||
|
EOF
|
||||||
|
cat sparse_hint >>expect &&
|
||||||
|
test_cmp expect stderr &&
|
||||||
|
git mv --sparse sub sub2 2>stderr &&
|
||||||
|
test_must_be_empty stderr &&
|
||||||
|
git commit -m "moved sub to sub2" &&
|
||||||
|
git rev-parse HEAD~1:sub >expect &&
|
||||||
|
git rev-parse HEAD:sub2 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git reset --hard HEAD~1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user