Skip to content

Commit fe069dc

Browse files
committed
Merge branch 'mt/add-rm-in-sparse-checkout'
"git add" and "git rm" learned not to touch those paths that are outside of sparse checkout. * mt/add-rm-in-sparse-checkout: rm: honor sparse checkout patterns add: warn when asked to update SKIP_WORKTREE entries refresh_index(): add flag to ignore SKIP_WORKTREE entries pathspec: allow to ignore SKIP_WORKTREE entries on index matching add: make --chmod and --renormalize honor sparse checkouts t3705: add tests for `git add` in sparse checkouts add: include magic part of pathspec on --refresh error
2 parents e706aaf + d5f4b82 commit fe069dc

16 files changed

+408
-63
lines changed

Documentation/config/advice.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,8 @@ advice.*::
119119
addEmptyPathspec::
120120
Advice shown if a user runs the add command without providing
121121
the pathspec parameter.
122+
updateSparsePath::
123+
Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
124+
is asked to update index entries outside the current sparse
125+
checkout.
122126
--

Documentation/git-rm.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ branch, and no updates to their contents can be staged in the index,
2323
though that default behavior can be overridden with the `-f` option.
2424
When `--cached` is given, the staged content has to
2525
match either the tip of the branch or the file on disk,
26-
allowing the file to be removed from just the index.
26+
allowing the file to be removed from just the index. When
27+
sparse-checkouts are in use (see linkgit:git-sparse-checkout[1]),
28+
`git rm` will only remove paths within the sparse-checkout patterns.
2729

2830

2931
OPTIONS

advice.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "config.h"
33
#include "color.h"
44
#include "help.h"
5+
#include "string-list.h"
56

67
int advice_fetch_show_forced_updates = 1;
78
int advice_push_update_rejected = 1;
@@ -136,6 +137,7 @@ static struct {
136137
[ADVICE_STATUS_HINTS] = { "statusHints", 1 },
137138
[ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
138139
[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
140+
[ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 },
139141
[ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 },
140142
};
141143

@@ -284,6 +286,24 @@ void NORETURN die_conclude_merge(void)
284286
die(_("Exiting because of unfinished merge."));
285287
}
286288

289+
void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
290+
{
291+
struct string_list_item *item;
292+
293+
if (!pathspec_list->nr)
294+
return;
295+
296+
fprintf(stderr, _("The following pathspecs didn't match any"
297+
" eligible path, but they do match index\n"
298+
"entries outside the current sparse checkout:\n"));
299+
for_each_string_list_item(item, pathspec_list)
300+
fprintf(stderr, "%s\n", item->string);
301+
302+
advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
303+
_("Disable or modify the sparsity rules if you intend"
304+
" to update such entries."));
305+
}
306+
287307
void detach_advice(const char *new_name)
288308
{
289309
const char *fmt =

advice.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#include "git-compat-util.h"
55

6+
struct string_list;
7+
68
extern int advice_fetch_show_forced_updates;
79
extern int advice_push_update_rejected;
810
extern int advice_push_non_ff_current;
@@ -71,6 +73,7 @@ extern int advice_add_empty_pathspec;
7173
ADVICE_STATUS_HINTS,
7274
ADVICE_STATUS_U_OPTION,
7375
ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
76+
ADVICE_UPDATE_SPARSE_PATH,
7477
ADVICE_WAITING_FOR_EDITOR,
7578
};
7679

@@ -92,6 +95,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);
9295
int error_resolve_conflict(const char *me);
9396
void NORETURN die_resolve_conflict(const char *me);
9497
void NORETURN die_conclude_merge(void);
98+
void advise_on_updating_sparse_paths(struct string_list *pathspec_list);
9599
void detach_advice(const char *new_name);
96100

97101
#endif /* ADVICE_H */

builtin/add.c

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only)
4646
struct cache_entry *ce = active_cache[i];
4747
int err;
4848

49+
if (ce_skip_worktree(ce))
50+
continue;
51+
4952
if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL))
5053
continue;
5154

@@ -146,6 +149,8 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
146149
for (i = 0; i < active_nr; i++) {
147150
struct cache_entry *ce = active_cache[i];
148151

152+
if (ce_skip_worktree(ce))
153+
continue;
149154
if (ce_stage(ce))
150155
continue; /* do not touch unmerged paths */
151156
if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode))
@@ -174,24 +179,44 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
174179
*dst++ = entry;
175180
}
176181
dir->nr = dst - dir->entries;
177-
add_pathspec_matches_against_index(pathspec, &the_index, seen);
182+
add_pathspec_matches_against_index(pathspec, &the_index, seen,
183+
PS_IGNORE_SKIP_WORKTREE);
178184
return seen;
179185
}
180186

181-
static void refresh(int verbose, const struct pathspec *pathspec)
187+
static int refresh(int verbose, const struct pathspec *pathspec)
182188
{
183189
char *seen;
184-
int i;
190+
int i, ret = 0;
191+
char *skip_worktree_seen = NULL;
192+
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
193+
int flags = REFRESH_IGNORE_SKIP_WORKTREE |
194+
(verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET);
185195

186196
seen = xcalloc(pathspec->nr, 1);
187-
refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
188-
pathspec, seen, _("Unstaged changes after refreshing the index:"));
197+
refresh_index(&the_index, flags, pathspec, seen,
198+
_("Unstaged changes after refreshing the index:"));
189199
for (i = 0; i < pathspec->nr; i++) {
190-
if (!seen[i])
191-
die(_("pathspec '%s' did not match any files"),
192-
pathspec->items[i].match);
200+
if (!seen[i]) {
201+
if (matches_skip_worktree(pathspec, i, &skip_worktree_seen)) {
202+
string_list_append(&only_match_skip_worktree,
203+
pathspec->items[i].original);
204+
} else {
205+
die(_("pathspec '%s' did not match any files"),
206+
pathspec->items[i].original);
207+
}
208+
}
209+
}
210+
211+
if (only_match_skip_worktree.nr) {
212+
advise_on_updating_sparse_paths(&only_match_skip_worktree);
213+
ret = 1;
193214
}
215+
194216
free(seen);
217+
free(skip_worktree_seen);
218+
string_list_clear(&only_match_skip_worktree, 0);
219+
return ret;
195220
}
196221

197222
int run_add_interactive(const char *revision, const char *patch_mode,
@@ -567,15 +592,18 @@ int cmd_add(int argc, const char **argv, const char *prefix)
567592
}
568593

569594
if (refresh_only) {
570-
refresh(verbose, &pathspec);
595+
exit_status |= refresh(verbose, &pathspec);
571596
goto finish;
572597
}
573598

574599
if (pathspec.nr) {
575600
int i;
601+
char *skip_worktree_seen = NULL;
602+
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
576603

577604
if (!seen)
578-
seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
605+
seen = find_pathspecs_matching_against_index(&pathspec,
606+
&the_index, PS_IGNORE_SKIP_WORKTREE);
579607

580608
/*
581609
* file_exists() assumes exact match
@@ -589,12 +617,24 @@ int cmd_add(int argc, const char **argv, const char *prefix)
589617

590618
for (i = 0; i < pathspec.nr; i++) {
591619
const char *path = pathspec.items[i].match;
620+
592621
if (pathspec.items[i].magic & PATHSPEC_EXCLUDE)
593622
continue;
594-
if (!seen[i] && path[0] &&
595-
((pathspec.items[i].magic &
596-
(PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
597-
!file_exists(path))) {
623+
if (seen[i])
624+
continue;
625+
626+
if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) {
627+
string_list_append(&only_match_skip_worktree,
628+
pathspec.items[i].original);
629+
continue;
630+
}
631+
632+
/* Don't complain at 'git add .' on empty repo */
633+
if (!path[0])
634+
continue;
635+
636+
if ((pathspec.items[i].magic & (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
637+
!file_exists(path)) {
598638
if (ignore_missing) {
599639
int dtype = DT_UNKNOWN;
600640
if (is_excluded(&dir, &the_index, path, &dtype))
@@ -605,7 +645,16 @@ int cmd_add(int argc, const char **argv, const char *prefix)
605645
pathspec.items[i].original);
606646
}
607647
}
648+
649+
650+
if (only_match_skip_worktree.nr) {
651+
advise_on_updating_sparse_paths(&only_match_skip_worktree);
652+
exit_status = 1;
653+
}
654+
608655
free(seen);
656+
free(skip_worktree_seen);
657+
string_list_clear(&only_match_skip_worktree, 0);
609658
}
610659

611660
plug_bulk_checkin();

builtin/check-ignore.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ static int check_ignore(struct dir_struct *dir,
100100
* should not be ignored, in order to be consistent with
101101
* 'git status', 'git add' etc.
102102
*/
103-
seen = find_pathspecs_matching_against_index(&pathspec, &the_index);
103+
seen = find_pathspecs_matching_against_index(&pathspec, &the_index,
104+
PS_HEED_SKIP_WORKTREE);
104105
for (i = 0; i < pathspec.nr; i++) {
105106
full_path = pathspec.items[i].match;
106107
pattern = NULL;

builtin/rm.c

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
#define USE_THE_INDEX_COMPATIBILITY_MACROS
77
#include "builtin.h"
8+
#include "advice.h"
89
#include "config.h"
910
#include "lockfile.h"
1011
#include "dir.h"
@@ -254,7 +255,7 @@ static struct option builtin_rm_options[] = {
254255
int cmd_rm(int argc, const char **argv, const char *prefix)
255256
{
256257
struct lock_file lock_file = LOCK_INIT;
257-
int i;
258+
int i, ret = 0;
258259
struct pathspec pathspec;
259260
char *seen;
260261

@@ -297,6 +298,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
297298
ensure_full_index(&the_index);
298299
for (i = 0; i < active_nr; i++) {
299300
const struct cache_entry *ce = active_cache[i];
301+
if (ce_skip_worktree(ce))
302+
continue;
300303
if (!ce_path_match(&the_index, ce, &pathspec, seen))
301304
continue;
302305
ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
@@ -310,24 +313,34 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
310313
if (pathspec.nr) {
311314
const char *original;
312315
int seen_any = 0;
316+
char *skip_worktree_seen = NULL;
317+
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
318+
313319
for (i = 0; i < pathspec.nr; i++) {
314320
original = pathspec.items[i].original;
315-
if (!seen[i]) {
316-
if (!ignore_unmatch) {
317-
die(_("pathspec '%s' did not match any files"),
318-
original);
319-
}
320-
}
321-
else {
321+
if (seen[i])
322322
seen_any = 1;
323-
}
323+
else if (ignore_unmatch)
324+
continue;
325+
else if (matches_skip_worktree(&pathspec, i, &skip_worktree_seen))
326+
string_list_append(&only_match_skip_worktree, original);
327+
else
328+
die(_("pathspec '%s' did not match any files"), original);
329+
324330
if (!recursive && seen[i] == MATCHED_RECURSIVELY)
325331
die(_("not removing '%s' recursively without -r"),
326332
*original ? original : ".");
327333
}
328334

335+
if (only_match_skip_worktree.nr) {
336+
advise_on_updating_sparse_paths(&only_match_skip_worktree);
337+
ret = 1;
338+
}
339+
free(skip_worktree_seen);
340+
string_list_clear(&only_match_skip_worktree, 0);
341+
329342
if (!seen_any)
330-
exit(0);
343+
exit(ret);
331344
}
332345

333346
if (!index_only)
@@ -407,5 +420,5 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
407420
COMMIT_LOCK | SKIP_IF_UNCHANGED))
408421
die(_("Unable to write new index file"));
409422

410-
return 0;
423+
return ret;
411424
}

cache.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -895,13 +895,14 @@ int match_stat_data_racy(const struct index_state *istate,
895895

896896
void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, struct stat *st);
897897

898-
#define REFRESH_REALLY 0x0001 /* ignore_valid */
899-
#define REFRESH_UNMERGED 0x0002 /* allow unmerged */
900-
#define REFRESH_QUIET 0x0004 /* be quiet about it */
901-
#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */
902-
#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */
903-
#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */
904-
#define REFRESH_PROGRESS 0x0040 /* show progress bar if stderr is tty */
898+
#define REFRESH_REALLY (1 << 0) /* ignore_valid */
899+
#define REFRESH_UNMERGED (1 << 1) /* allow unmerged */
900+
#define REFRESH_QUIET (1 << 2) /* be quiet about it */
901+
#define REFRESH_IGNORE_MISSING (1 << 3) /* ignore non-existent */
902+
#define REFRESH_IGNORE_SUBMODULES (1 << 4) /* ignore submodules */
903+
#define REFRESH_IN_PORCELAIN (1 << 5) /* user friendly output, not "needs update" */
904+
#define REFRESH_PROGRESS (1 << 6) /* show progress bar if stderr is tty */
905+
#define REFRESH_IGNORE_SKIP_WORKTREE (1 << 7) /* ignore skip_worktree entries */
905906
int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
906907
/*
907908
* Refresh the index and write it to disk.

pathspec.c

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
*/
2222
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
2323
struct index_state *istate,
24-
char *seen)
24+
char *seen,
25+
enum ps_skip_worktree_action sw_action)
2526
{
2627
int num_unmatched = 0, i;
2728

@@ -40,6 +41,8 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
4041
ensure_full_index(istate);
4142
for (i = 0; i < istate->cache_nr; i++) {
4243
const struct cache_entry *ce = istate->cache[i];
44+
if (sw_action == PS_IGNORE_SKIP_WORKTREE && ce_skip_worktree(ce))
45+
continue;
4346
ce_path_match(istate, ce, pathspec, seen);
4447
}
4548
}
@@ -53,10 +56,26 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
5356
* given pathspecs achieves against all items in the index.
5457
*/
5558
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
56-
struct index_state *istate)
59+
struct index_state *istate,
60+
enum ps_skip_worktree_action sw_action)
61+
{
62+
char *seen = xcalloc(pathspec->nr, 1);
63+
add_pathspec_matches_against_index(pathspec, istate, seen, sw_action);
64+
return seen;
65+
}
66+
67+
char *find_pathspecs_matching_skip_worktree(const struct pathspec *pathspec)
5768
{
69+
struct index_state *istate = the_repository->index;
5870
char *seen = xcalloc(pathspec->nr, 1);
59-
add_pathspec_matches_against_index(pathspec, istate, seen);
71+
int i;
72+
73+
for (i = 0; i < istate->cache_nr; i++) {
74+
struct cache_entry *ce = istate->cache[i];
75+
if (ce_skip_worktree(ce))
76+
ce_path_match(istate, ce, pathspec, seen);
77+
}
78+
6079
return seen;
6180
}
6281

0 commit comments

Comments
 (0)