Skip to content

Commit 2d498a7

Browse files
committed
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
2 parents 2bd2f25 + 6579e78 commit 2d498a7

13 files changed

+505
-52
lines changed

Documentation/git-add.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
12-
[--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
12+
[--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] [--sparse]
1313
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize]
1414
[--chmod=(+|-)x] [--pathspec-from-file=<file> [--pathspec-file-nul]]
1515
[--] [<pathspec>...]
@@ -79,6 +79,13 @@ in linkgit:gitglossary[7].
7979
--force::
8080
Allow adding otherwise ignored files.
8181

82+
--sparse::
83+
Allow updating index entries outside of the sparse-checkout cone.
84+
Normally, `git add` refuses to update index entries whose paths do
85+
not fit within the sparse-checkout cone, since those files might
86+
be removed from the working tree without warning. See
87+
linkgit:git-sparse-checkout[1] for more details.
88+
8289
-i::
8390
--interactive::
8491
Add modified contents in the working tree interactively to

Documentation/git-rm.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ For more details, see the 'pathspec' entry in linkgit:gitglossary[7].
7272
--ignore-unmatch::
7373
Exit with a zero status even if no files matched.
7474

75+
--sparse::
76+
Allow updating index entries outside of the sparse-checkout cone.
77+
Normally, `git rm` refuses to update index entries whose paths do
78+
not fit within the sparse-checkout cone. See
79+
linkgit:git-sparse-checkout[1] for more.
80+
7581
-q::
7682
--quiet::
7783
`git rm` normally outputs one line (in the form of an `rm` command)

advice.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,16 @@ void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
224224
if (!pathspec_list->nr)
225225
return;
226226

227-
fprintf(stderr, _("The following pathspecs didn't match any"
228-
" eligible path, but they do match index\n"
229-
"entries outside the current sparse checkout:\n"));
227+
fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n"
228+
"outside of your sparse-checkout definition, so will not be\n"
229+
"updated in the index:\n"));
230230
for_each_string_list_item(item, pathspec_list)
231231
fprintf(stderr, "%s\n", item->string);
232232

233233
advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
234-
_("Disable or modify the sparsity rules if you intend"
235-
" to update such entries."));
234+
_("If you intend to update such entries, try one of the following:\n"
235+
"* Use the --sparse option.\n"
236+
"* Disable or modify the sparsity rules."));
236237
}
237238

238239
void detach_advice(const char *new_name)

builtin/add.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static int patch_interactive, add_interactive, edit_interactive;
3030
static int take_worktree_changes;
3131
static int add_renormalize;
3232
static int pathspec_file_nul;
33+
static int include_sparse;
3334
static const char *pathspec_from_file;
3435
static int legacy_stash_p; /* support for the scripted `git stash` */
3536

@@ -46,7 +47,9 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
4647
struct cache_entry *ce = active_cache[i];
4748
int err;
4849

49-
if (ce_skip_worktree(ce))
50+
if (!include_sparse &&
51+
(ce_skip_worktree(ce) ||
52+
!path_in_sparse_checkout(ce->name, &the_index)))
5053
continue;
5154

5255
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
@@ -94,6 +97,10 @@ static void update_callback(struct diff_queue_struct *q,
9497
for (i = 0; i < q->nr; i++) {
9598
struct diff_filepair *p = q->queue[i];
9699
const char *path = p->one->path;
100+
101+
if (!include_sparse && !path_in_sparse_checkout(path, &the_index))
102+
continue;
103+
97104
switch (fix_unmerged_status(p, data)) {
98105
default:
99106
die(_("unexpected diff status %c"), p->status);
@@ -147,7 +154,9 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
147154
for (i = 0; i < active_nr; i++) {
148155
struct cache_entry *ce = active_cache[i];
149156

150-
if (ce_skip_worktree(ce))
157+
if (!include_sparse &&
158+
(ce_skip_worktree(ce) ||
159+
!path_in_sparse_checkout(ce->name, &the_index)))
151160
continue;
152161
if (ce_stage(ce))
153162
continue; /* do not touch unmerged paths */
@@ -377,6 +386,7 @@ static struct option builtin_add_options[] = {
377386
OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")),
378387
OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")),
379388
OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")),
389+
OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
380390
OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x",
381391
N_("override the executable bit of the listed files")),
382392
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
@@ -442,6 +452,7 @@ static void check_embedded_repo(const char *path)
442452
static int add_files(struct dir_struct *dir, int flags)
443453
{
444454
int i, exit_status = 0;
455+
struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP;
445456

446457
if (dir->ignored_nr) {
447458
fprintf(stderr, _(ignore_error));
@@ -455,6 +466,12 @@ static int add_files(struct dir_struct *dir, int flags)
455466
}
456467

457468
for (i = 0; i < dir->nr; i++) {
469+
if (!include_sparse &&
470+
!path_in_sparse_checkout(dir->entries[i]->name, &the_index)) {
471+
string_list_append(&matched_sparse_paths,
472+
dir->entries[i]->name);
473+
continue;
474+
}
458475
if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
459476
if (!ignore_add_errors)
460477
die(_("adding files failed"));
@@ -463,6 +480,14 @@ static int add_files(struct dir_struct *dir, int flags)
463480
check_embedded_repo(dir->entries[i]->name);
464481
}
465482
}
483+
484+
if (matched_sparse_paths.nr) {
485+
advise_on_updating_sparse_paths(&matched_sparse_paths);
486+
exit_status = 1;
487+
}
488+
489+
string_list_clear(&matched_sparse_paths, 0);
490+
466491
return exit_status;
467492
}
468493

@@ -627,7 +652,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
627652
if (seen[i])
628653
continue;
629654

630-
if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
655+
if (!include_sparse &&
656+
matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
631657
string_list_append(&only_match_skip_worktree,
632658
pathspec.items[i].original);
633659
continue;

builtin/mv.c

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,23 @@ static int index_range_of_same_dir(const char *src, int length,
118118
int cmd_mv(int argc, const char **argv, const char *prefix)
119119
{
120120
int i, flags, gitmodules_modified = 0;
121-
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
121+
int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0;
122122
struct option builtin_mv_options[] = {
123123
OPT__VERBOSE(&verbose, N_("be verbose")),
124124
OPT__DRY_RUN(&show_only, N_("dry run")),
125125
OPT__FORCE(&force, N_("force move/rename even if target exists"),
126126
PARSE_OPT_NOCOMPLETE),
127127
OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
128+
OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
128129
OPT_END(),
129130
};
130131
const char **source, **destination, **dest_path, **submodule_gitfile;
131-
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
132+
enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes;
132133
struct stat st;
133134
struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
134135
struct lock_file lock_file = LOCK_INIT;
135136
struct cache_entry *ce;
137+
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
136138

137139
git_config(git_default_config, NULL);
138140

@@ -176,14 +178,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
176178
const char *src = source[i], *dst = destination[i];
177179
int length, src_is_dir;
178180
const char *bad = NULL;
181+
int skip_sparse = 0;
179182

180183
if (show_only)
181184
printf(_("Checking rename of '%s' to '%s'\n"), src, dst);
182185

183186
length = strlen(src);
184-
if (lstat(src, &st) < 0)
185-
bad = _("bad source");
186-
else if (!strncmp(src, dst, length) &&
187+
if (lstat(src, &st) < 0) {
188+
/* only error if existence is expected. */
189+
if (modes[i] != SPARSE)
190+
bad = _("bad source");
191+
} else if (!strncmp(src, dst, length) &&
187192
(dst[length] == 0 || dst[length] == '/')) {
188193
bad = _("can not move directory into itself");
189194
} 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)
212217
dst_len = strlen(dst);
213218

214219
for (j = 0; j < last - first; j++) {
215-
const char *path = active_cache[first + j]->name;
220+
const struct cache_entry *ce = active_cache[first + j];
221+
const char *path = ce->name;
216222
source[argc + j] = path;
217223
destination[argc + j] =
218224
prefix_path(dst, dst_len, path + length + 1);
219-
modes[argc + j] = INDEX;
225+
modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX;
220226
submodule_gitfile[argc + j] = NULL;
221227
}
222228
argc += last - first;
@@ -244,14 +250,36 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
244250
bad = _("multiple sources for the same target");
245251
else if (is_dir_sep(dst[strlen(dst) - 1]))
246252
bad = _("destination directory does not exist");
247-
else
253+
else {
254+
/*
255+
* We check if the paths are in the sparse-checkout
256+
* definition as a very final check, since that
257+
* allows us to point the user to the --sparse
258+
* option as a way to have a successful run.
259+
*/
260+
if (!ignore_sparse &&
261+
!path_in_sparse_checkout(src, &the_index)) {
262+
string_list_append(&only_match_skip_worktree, src);
263+
skip_sparse = 1;
264+
}
265+
if (!ignore_sparse &&
266+
!path_in_sparse_checkout(dst, &the_index)) {
267+
string_list_append(&only_match_skip_worktree, dst);
268+
skip_sparse = 1;
269+
}
270+
271+
if (skip_sparse)
272+
goto remove_entry;
273+
248274
string_list_insert(&src_for_dst, dst);
275+
}
249276

250277
if (!bad)
251278
continue;
252279
if (!ignore_errors)
253280
die(_("%s, source=%s, destination=%s"),
254281
bad, src, dst);
282+
remove_entry:
255283
if (--argc > 0) {
256284
int n = argc - i;
257285
memmove(source + i, source + i + 1,
@@ -266,6 +294,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
266294
}
267295
}
268296

297+
if (only_match_skip_worktree.nr) {
298+
advise_on_updating_sparse_paths(&only_match_skip_worktree);
299+
if (!ignore_errors)
300+
return 1;
301+
}
302+
269303
for (i = 0; i < argc; i++) {
270304
const char *src = source[i], *dst = destination[i];
271305
enum update_mode mode = modes[i];
@@ -274,7 +308,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
274308
printf(_("Renaming %s to %s\n"), src, dst);
275309
if (show_only)
276310
continue;
277-
if (mode != INDEX && rename(src, dst) < 0) {
311+
if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) {
278312
if (ignore_errors)
279313
continue;
280314
die_errno(_("renaming '%s' failed"), src);

builtin/rm.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ static int check_local_mod(struct object_id *head, int index_only)
237237

238238
static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
239239
static int ignore_unmatch = 0, pathspec_file_nul;
240+
static int include_sparse;
240241
static char *pathspec_from_file;
241242

242243
static struct option builtin_rm_options[] = {
@@ -247,6 +248,7 @@ static struct option builtin_rm_options[] = {
247248
OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")),
248249
OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
249250
N_("exit with a zero status even if nothing matched")),
251+
OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")),
250252
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
251253
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
252254
OPT_END(),
@@ -298,7 +300,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
298300
ensure_full_index(&the_index);
299301
for (i = 0; i < active_nr; i++) {
300302
const struct cache_entry *ce = active_cache[i];
301-
if (ce_skip_worktree(ce))
303+
304+
if (!include_sparse &&
305+
(ce_skip_worktree(ce) ||
306+
!path_in_sparse_checkout(ce->name, &the_index)))
302307
continue;
303308
if (!ce_path_match(&the_index, ce, &pathspec, seen))
304309
continue;
@@ -322,7 +327,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
322327
seen_any = 1;
323328
else if (ignore_unmatch)
324329
continue;
325-
else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
330+
else if (!include_sparse &&
331+
matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
326332
string_list_append(&only_match_skip_worktree, original);
327333
else
328334
die(_("pathspec '%s' did not match any files"), original);

0 commit comments

Comments
 (0)