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]
|
||||
'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]
|
||||
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
|
||||
[--] [<pathspec>...]
|
||||
@ -79,6 +79,13 @@ in linkgit:gitglossary[7].
|
||||
--force::
|
||||
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::
|
||||
--interactive::
|
||||
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::
|
||||
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::
|
||||
--quiet::
|
||||
`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)
|
||||
return;
|
||||
|
||||
fprintf(stderr, _("The following pathspecs didn't match any"
|
||||
" eligible path, but they do match index\n"
|
||||
"entries outside the current sparse checkout:\n"));
|
||||
fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n"
|
||||
"outside of your sparse-checkout definition, so will not be\n"
|
||||
"updated in the index:\n"));
|
||||
for_each_string_list_item(item, pathspec_list)
|
||||
fprintf(stderr, "%s\n", item->string);
|
||||
|
||||
advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
|
||||
_("Disable or modify the sparsity rules if you intend"
|
||||
" to update such entries."));
|
||||
_("If you intend to update such entries, try one of the following:\n"
|
||||
"* Use the --sparse option.\n"
|
||||
"* Disable or modify the sparsity rules."));
|
||||
}
|
||||
|
||||
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 add_renormalize;
|
||||
static int pathspec_file_nul;
|
||||
static int include_sparse;
|
||||
static const char *pathspec_from_file;
|
||||
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];
|
||||
int err;
|
||||
|
||||
if (ce_skip_worktree(ce))
|
||||
if (!include_sparse &&
|
||||
(ce_skip_worktree(ce) ||
|
||||
!path_in_sparse_checkout(ce->name, &the_index)))
|
||||
continue;
|
||||
|
||||
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++) {
|
||||
struct diff_filepair *p = q->queue[i];
|
||||
const char *path = p->one->path;
|
||||
|
||||
if (!include_sparse && !path_in_sparse_checkout(path, &the_index))
|
||||
continue;
|
||||
|
||||
switch (fix_unmerged_status(p, data)) {
|
||||
default:
|
||||
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++) {
|
||||
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;
|
||||
if (ce_stage(ce))
|
||||
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 , "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, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
|
||||
OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
|
||||
N_("override the executable bit of the listed files")),
|
||||
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)
|
||||
{
|
||||
int i, exit_status = 0;
|
||||
struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP;
|
||||
|
||||
if (dir->ignored_nr) {
|
||||
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++) {
|
||||
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 (!ignore_add_errors)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -627,7 +652,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||
if (seen[i])
|
||||
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,
|
||||
pathspec.items[i].original);
|
||||
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 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[] = {
|
||||
OPT__VERBOSE(&verbose, N_("be verbose")),
|
||||
OPT__DRY_RUN(&show_only, N_("dry run")),
|
||||
OPT__FORCE(&force, N_("force move/rename even if target exists"),
|
||||
PARSE_OPT_NOCOMPLETE),
|
||||
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(),
|
||||
};
|
||||
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 string_list src_for_dst = STRING_LIST_INIT_NODUP;
|
||||
struct lock_file lock_file = LOCK_INIT;
|
||||
struct cache_entry *ce;
|
||||
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
|
||||
|
||||
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];
|
||||
int length, src_is_dir;
|
||||
const char *bad = NULL;
|
||||
int skip_sparse = 0;
|
||||
|
||||
if (show_only)
|
||||
printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
|
||||
|
||||
length = strlen(src);
|
||||
if (lstat(src, &st) < 0)
|
||||
bad = _("bad source");
|
||||
else if (!strncmp(src, dst, length) &&
|
||||
if (lstat(src, &st) < 0) {
|
||||
/* only error if existence is expected. */
|
||||
if (modes[i] != SPARSE)
|
||||
bad = _("bad source");
|
||||
} else if (!strncmp(src, dst, length) &&
|
||||
(dst[length] == 0 || dst[length] == '/')) {
|
||||
bad = _("can not move directory into itself");
|
||||
} 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);
|
||||
|
||||
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;
|
||||
destination[argc + j] =
|
||||
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;
|
||||
}
|
||||
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");
|
||||
else if (is_dir_sep(dst[strlen(dst) - 1]))
|
||||
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);
|
||||
}
|
||||
|
||||
if (!bad)
|
||||
continue;
|
||||
if (!ignore_errors)
|
||||
die(_("%s, source=%s, destination=%s"),
|
||||
bad, src, dst);
|
||||
remove_entry:
|
||||
if (--argc > 0) {
|
||||
int n = argc - i;
|
||||
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++) {
|
||||
const char *src = source[i], *dst = destination[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);
|
||||
if (show_only)
|
||||
continue;
|
||||
if (mode != INDEX && rename(src, dst) < 0) {
|
||||
if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) {
|
||||
if (ignore_errors)
|
||||
continue;
|
||||
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 ignore_unmatch = 0, pathspec_file_nul;
|
||||
static int include_sparse;
|
||||
static char *pathspec_from_file;
|
||||
|
||||
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( 0 , "ignore-unmatch", &ignore_unmatch,
|
||||
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_FILE_NUL(&pathspec_file_nul),
|
||||
OPT_END(),
|
||||
@ -298,7 +300,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
||||
ensure_full_index(&the_index);
|
||||
for (i = 0; i < active_nr; 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;
|
||||
if (!ce_path_match(&the_index, ce, &pathspec, seen))
|
||||
continue;
|
||||
@ -322,7 +327,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
||||
seen_any = 1;
|
||||
else if (ignore_unmatch)
|
||||
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);
|
||||
else
|
||||
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
|
||||
* do not need to call fnmatch at all.
|
||||
*/
|
||||
if (!patternlen && !namelen)
|
||||
if (!patternlen && (!namelen || (flags & PATTERN_FLAG_MUSTBEDIR)))
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -1303,6 +1303,44 @@ int match_pathname(const char *pathname, int pathlen,
|
||||
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
|
||||
* 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 */
|
||||
int i;
|
||||
struct strbuf *path_parent = NULL;
|
||||
|
||||
if (!pl->nr)
|
||||
return NULL; /* undefined */
|
||||
@ -1327,11 +1366,10 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
|
||||
const char *exclude = pattern->pattern;
|
||||
int prefix = pattern->nowildcardlen;
|
||||
|
||||
if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) {
|
||||
*dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
|
||||
if (*dtype != DT_DIR)
|
||||
continue;
|
||||
}
|
||||
if (pattern->flags & PATTERN_FLAG_MUSTBEDIR &&
|
||||
!path_matches_dir_pattern(pathname, pathlen, &path_parent,
|
||||
dtype, pattern, istate))
|
||||
continue;
|
||||
|
||||
if (pattern->flags & PATTERN_FLAG_NODIR) {
|
||||
if (match_basename(basename,
|
||||
@ -1355,6 +1393,12 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path_parent) {
|
||||
strbuf_release(path_parent);
|
||||
free(path_parent);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
|
||||
return;
|
||||
for (i = 0; i < istate->cache_nr; 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;
|
||||
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++) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -411,7 +411,7 @@ test_expect_success 'sparse-checkout (init|set|disable) warns with unmerged stat
|
||||
git -C unmerged sparse-checkout disable
|
||||
'
|
||||
|
||||
test_expect_success 'sparse-checkout reapply' '
|
||||
test_expect_failure 'sparse-checkout reapply' '
|
||||
git clone repo tweak &&
|
||||
|
||||
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_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 sparse-checkout reapply 2>err &&
|
||||
test_must_be_empty err &&
|
||||
|
@ -187,6 +187,16 @@ test_sparse_match () {
|
||||
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' '
|
||||
init_repos &&
|
||||
|
||||
@ -291,6 +301,20 @@ test_expect_success 'add, commit, 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' '
|
||||
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.
|
||||
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 &&
|
||||
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_sparse_match git add folder1/new &&
|
||||
|
||||
test_all_match git add . &&
|
||||
test_all_match git add --sparse . &&
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
test_all_match git commit -m folder1/new &&
|
||||
test_all_match git rev-parse HEAD^{tree} &&
|
||||
|
||||
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 commit -m folder1/newer &&
|
||||
test_all_match git rev-parse HEAD^{tree}
|
||||
@ -494,11 +524,6 @@ test_expect_success 'merge, cherry-pick, and rebase' '
|
||||
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' '
|
||||
init_repos &&
|
||||
|
||||
@ -513,13 +538,19 @@ test_expect_success 'merge with conflict outside cone' '
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
# 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 &&
|
||||
|
||||
# 3. Rename the file to another sparse filename and
|
||||
# accept conflict markers as resolved content.
|
||||
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 merge --continue &&
|
||||
@ -544,13 +575,25 @@ test_expect_success 'cherry-pick/rebase with conflict outside cone' '
|
||||
test_all_match git status --porcelain=v2 &&
|
||||
|
||||
# 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 &&
|
||||
|
||||
# 3. Rename the file to another sparse filename and
|
||||
# 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 &&
|
||||
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 $OPERATION --continue &&
|
||||
@ -626,6 +669,7 @@ test_expect_success 'clean' '
|
||||
test_expect_success 'submodule handling' '
|
||||
init_repos &&
|
||||
|
||||
test_sparse_match git sparse-checkout add modules &&
|
||||
test_all_match mkdir modules &&
|
||||
test_all_match touch modules/a &&
|
||||
test_all_match git add modules &&
|
||||
@ -635,6 +679,7 @@ test_expect_success 'submodule handling' '
|
||||
test_all_match git commit -m "add submodule" &&
|
||||
|
||||
# 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 &&
|
||||
grep "100644 blob .* modules/a" 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 &&
|
||||
|
||||
cat >sparse_error_header <<-EOF &&
|
||||
The following pathspecs didn't match any eligible path, but they do match index
|
||||
entries outside the current sparse checkout:
|
||||
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: 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\"
|
||||
EOF
|
||||
|
||||
@ -37,9 +40,25 @@ done
|
||||
test_expect_success 'recursive rm does not remove sparse entries' '
|
||||
git reset --hard &&
|
||||
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 &&
|
||||
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
|
||||
'
|
||||
|
||||
@ -75,4 +94,15 @@ test_expect_success 'do not warn about sparse entries with --ignore-unmatch' '
|
||||
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
|
||||
|
@ -19,6 +19,7 @@ setup_sparse_entry () {
|
||||
fi &&
|
||||
git add 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)
|
||||
}
|
||||
|
||||
@ -36,14 +37,22 @@ setup_gitignore () {
|
||||
EOF
|
||||
}
|
||||
|
||||
test_sparse_entry_unstaged () {
|
||||
git diff --staged -- sparse_entry >diff &&
|
||||
test_must_be_empty diff
|
||||
}
|
||||
|
||||
test_expect_success 'setup' "
|
||||
cat >sparse_error_header <<-EOF &&
|
||||
The following pathspecs didn't match any eligible path, but they do match index
|
||||
entries outside the current sparse checkout:
|
||||
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: 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\"
|
||||
EOF
|
||||
|
||||
@ -55,6 +64,7 @@ test_expect_success 'git add does not remove sparse entries' '
|
||||
setup_sparse_entry &&
|
||||
rm sparse_entry &&
|
||||
test_must_fail git add sparse_entry 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
test_cmp error_and_hint stderr &&
|
||||
test_sparse_entry_unchanged
|
||||
'
|
||||
@ -73,6 +83,7 @@ test_expect_success 'git add . does not remove sparse entries' '
|
||||
rm sparse_entry &&
|
||||
setup_gitignore &&
|
||||
test_must_fail git add . 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
|
||||
cat sparse_error_header >expect &&
|
||||
echo . >>expect &&
|
||||
@ -88,6 +99,7 @@ do
|
||||
setup_sparse_entry &&
|
||||
echo modified >sparse_entry &&
|
||||
test_must_fail git add $opt sparse_entry 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
test_cmp error_and_hint stderr &&
|
||||
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 &&
|
||||
test-tool chmtime -60 sparse_entry &&
|
||||
test_must_fail git add --refresh sparse_entry 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
test_cmp error_and_hint stderr &&
|
||||
git ls-files --debug sparse_entry | grep mtime >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' '
|
||||
setup_sparse_entry &&
|
||||
test_must_fail git add --chmod=+x sparse_entry 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
test_cmp error_and_hint stderr &&
|
||||
test_sparse_entry_unchanged &&
|
||||
! 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" &&
|
||||
echo "sparse_entry text=auto" >.gitattributes &&
|
||||
test_must_fail git add --renormalize sparse_entry 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
test_cmp error_and_hint stderr &&
|
||||
test_sparse_entry_unchanged
|
||||
'
|
||||
@ -124,6 +139,7 @@ test_expect_success 'git add --dry-run --ignore-missing warn on sparse path' '
|
||||
setup_sparse_entry &&
|
||||
rm sparse_entry &&
|
||||
test_must_fail git add --dry-run --ignore-missing sparse_entry 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
test_cmp error_and_hint stderr &&
|
||||
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
|
||||
'
|
||||
|
||||
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' '
|
||||
setup_sparse_entry &&
|
||||
test_must_fail git -c advice.updateSparsePath=false add sparse_entry 2>stderr &&
|
||||
test_sparse_entry_unstaged &&
|
||||
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
|
||||
|
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