Skip to content

Commit d3f52dc

Browse files
committed
Merge branch 'ds/sparse-index-ignored-files' into seen
In cone mode, the sparse-index codepath learned to remove ignored files (like build artifacts) outside the sparse cone, allowing the entire directory outside the sparse cone to be removed, which is especially useful when the sparse patterns change. * ds/sparse-index-ignored-files: sparse-checkout: clear tracked sparse dirs sparse-index: add SPARSE_INDEX_IGNORE_CONFIG flag attr: be careful about sparse directories sparse-checkout: create helper methods unpack-trees: fix nested sparse-dir search sparse-index: silently return when cache tree fails sparse-index: silently return when not using cone-mode patterns t7519: rewrite sparse index test
2 parents 59eca1d + 8f3a3a2 commit d3f52dc

12 files changed

+290
-62
lines changed

Documentation/git-sparse-checkout.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ case-insensitive check. This corrects for case mismatched filenames in the
210210
'git sparse-checkout set' command to reflect the expected cone in the working
211211
directory.
212212

213+
The cone mode sparse-checkout patterns will also remove ignored files that
214+
are not within the sparse-checkout definition. This is important behavior
215+
to preserve the performance of the sparse index, but also matches that
216+
cone mode patterns care about directories, not files. If there exist files
217+
that are untracked and not ignored, then Git will not delete files within
218+
that directory other than the tracked files that are now out of scope.
219+
These files should be removed manually to ensure Git can behave optimally.
220+
213221

214222
SUBMODULES
215223
----------

attr.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "utf8.h"
1515
#include "quote.h"
1616
#include "thread-utils.h"
17+
#include "dir.h"
1718

1819
const char git_attr__true[] = "(builtin)true";
1920
const char git_attr__false[] = "\0(builtin)false";
@@ -744,6 +745,19 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,
744745
if (!istate)
745746
return NULL;
746747

748+
/*
749+
* In the case of cone-mode sparse-checkout, getting the
750+
* .gitattributes file from a directory is meaningless: all
751+
* contained paths will be sparse if the .gitattributes is also
752+
* sparse. In the case of a sparse index, it is critical that we
753+
* don't go looking for one as it will expand the index.
754+
*/
755+
init_sparse_checkout_patterns(istate);
756+
if (istate->sparse_checkout_patterns &&
757+
istate->sparse_checkout_patterns->use_cone_patterns &&
758+
path_in_sparse_checkout(path, istate) == NOT_MATCHED)
759+
return NULL;
760+
747761
buf = read_blob_data_from_index(istate, path, NULL);
748762
if (!buf)
749763
return NULL;

builtin/add.c

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,21 +190,17 @@ static int refresh(int verbose, const struct pathspec *pathspec)
190190
struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP;
191191
int flags = REFRESH_IGNORE_SKIP_WORKTREE |
192192
(verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET);
193-
struct pattern_list pl = { 0 };
194-
int sparse_checkout_enabled = !get_sparse_checkout_patterns(&pl);
195193

196194
seen = xcalloc(pathspec->nr, 1);
197195
refresh_index(&the_index, flags, pathspec, seen,
198196
_("Unstaged changes after refreshing the index:"));
199197
for (i = 0; i < pathspec->nr; i++) {
200198
if (!seen[i]) {
201199
const char *path = pathspec->items[i].original;
202-
int dtype = DT_REG;
203200

204201
if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
205-
(sparse_checkout_enabled &&
206-
!path_matches_pattern_list(path, strlen(path), NULL,
207-
&dtype, &pl, &the_index))) {
202+
(core_apply_sparse_checkout &&
203+
path_in_sparse_checkout(path, &the_index) == NOT_MATCHED)) {
208204
string_list_append(&only_match_skip_worktree,
209205
pathspec->items[i].original);
210206
} else {

builtin/sparse-checkout.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,99 @@ static int sparse_checkout_list(int argc, const char **argv)
100100
return 0;
101101
}
102102

103+
static void clean_tracked_sparse_directories(struct repository *r)
104+
{
105+
int i, was_full = 0;
106+
struct strbuf path = STRBUF_INIT;
107+
size_t pathlen;
108+
struct string_list_item *item;
109+
struct string_list sparse_dirs = STRING_LIST_INIT_DUP;
110+
111+
/*
112+
* If we are not using cone mode patterns, then we cannot
113+
* delete directories outside of the sparse cone.
114+
*/
115+
if (!r || !r->index || !r->worktree)
116+
return;
117+
init_sparse_checkout_patterns(r->index);
118+
if (!r->index->sparse_checkout_patterns ||
119+
!r->index->sparse_checkout_patterns->use_cone_patterns)
120+
return;
121+
122+
/*
123+
* Use the sparse index as a data structure to assist finding
124+
* directories that are safe to delete. This conversion to a
125+
* sparse index will not delete directories that contain
126+
* conflicted entries or submodules.
127+
*/
128+
if (!r->index->sparse_index) {
129+
/*
130+
* If something, such as a merge conflict or other concern,
131+
* prevents us from converting to a sparse index, then do
132+
* not try deleting files.
133+
*/
134+
if (convert_to_sparse(r->index, SPARSE_INDEX_IGNORE_CONFIG))
135+
return;
136+
was_full = 1;
137+
}
138+
139+
strbuf_addstr(&path, r->worktree);
140+
strbuf_complete(&path, '/');
141+
pathlen = path.len;
142+
143+
/*
144+
* Collect directories that have gone out of scope but also
145+
* exist on disk, so there is some work to be done. We need to
146+
* store the entries in a list before exploring, since that might
147+
* expand the sparse-index again.
148+
*/
149+
for (i = 0; i < r->index->cache_nr; i++) {
150+
struct cache_entry *ce = r->index->cache[i];
151+
152+
if (S_ISSPARSEDIR(ce->ce_mode) &&
153+
repo_file_exists(r, ce->name))
154+
string_list_append(&sparse_dirs, ce->name);
155+
}
156+
157+
for_each_string_list_item(item, &sparse_dirs) {
158+
struct dir_struct dir = DIR_INIT;
159+
struct pathspec p = { 0 };
160+
struct strvec s = STRVEC_INIT;
161+
162+
strbuf_setlen(&path, pathlen);
163+
strbuf_addstr(&path, item->string);
164+
165+
dir.flags |= DIR_SHOW_IGNORED_TOO;
166+
167+
setup_standard_excludes(&dir);
168+
strvec_push(&s, path.buf);
169+
170+
parse_pathspec(&p, PATHSPEC_GLOB, 0, NULL, s.v);
171+
fill_directory(&dir, r->index, &p);
172+
173+
if (dir.nr) {
174+
warning(_("directory '%s' contains untracked files,"
175+
" but is not in the sparse-checkout cone"),
176+
item->string);
177+
} else if (remove_dir_recursively(&path, 0)) {
178+
/*
179+
* Removal is "best effort". If something blocks
180+
* the deletion, then continue with a warning.
181+
*/
182+
warning(_("failed to remove directory '%s'"),
183+
item->string);
184+
}
185+
186+
dir_clear(&dir);
187+
}
188+
189+
string_list_clear(&sparse_dirs, 0);
190+
strbuf_release(&path);
191+
192+
if (was_full)
193+
ensure_full_index(r->index);
194+
}
195+
103196
static int update_working_directory(struct pattern_list *pl)
104197
{
105198
enum update_sparsity_result result;
@@ -141,6 +234,8 @@ static int update_working_directory(struct pattern_list *pl)
141234
else
142235
rollback_lock_file(&lock_file);
143236

237+
clean_tracked_sparse_directories(r);
238+
144239
r->index->sparse_checkout_patterns = NULL;
145240
return result;
146241
}

dir.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,35 @@ enum pattern_match_result path_matches_pattern_list(
14391439
return result;
14401440
}
14411441

1442+
int init_sparse_checkout_patterns(struct index_state *istate)
1443+
{
1444+
if (istate->sparse_checkout_patterns)
1445+
return 0;
1446+
1447+
CALLOC_ARRAY(istate->sparse_checkout_patterns, 1);
1448+
1449+
if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0) {
1450+
FREE_AND_NULL(istate->sparse_checkout_patterns);
1451+
return -1;
1452+
}
1453+
1454+
return 0;
1455+
}
1456+
1457+
enum pattern_match_result path_in_sparse_checkout(const char *path,
1458+
struct index_state *istate)
1459+
{
1460+
int dtype = DT_REG;
1461+
init_sparse_checkout_patterns(istate);
1462+
1463+
if (!istate->sparse_checkout_patterns)
1464+
return MATCHED;
1465+
1466+
return path_matches_pattern_list(path, strlen(path), NULL, &dtype,
1467+
istate->sparse_checkout_patterns,
1468+
istate);
1469+
}
1470+
14421471
static struct path_pattern *last_matching_pattern_from_lists(
14431472
struct dir_struct *dir, struct index_state *istate,
14441473
const char *pathname, int pathlen,

dir.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,12 @@ enum pattern_match_result path_matches_pattern_list(const char *pathname,
394394
const char *basename, int *dtype,
395395
struct pattern_list *pl,
396396
struct index_state *istate);
397+
398+
int init_sparse_checkout_patterns(struct index_state *state);
399+
400+
enum pattern_match_result path_in_sparse_checkout(const char *path,
401+
struct index_state *istate);
402+
397403
struct dir_entry *dir_add_ignored(struct dir_struct *dir,
398404
struct index_state *istate,
399405
const char *pathname, int len);

read-cache.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3071,7 +3071,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
30713071
int was_full = !istate->sparse_index;
30723072
struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT;
30733073

3074-
ret = convert_to_sparse(istate);
3074+
ret = convert_to_sparse(istate, 0);
30753075

30763076
if (ret) {
30773077
warning(_("failed to convert to a sparse-index"));
@@ -3187,7 +3187,7 @@ static int write_shared_index(struct index_state *istate,
31873187
int ret, was_full = !istate->sparse_index;
31883188

31893189
move_cache_to_base_index(istate);
3190-
convert_to_sparse(istate);
3190+
convert_to_sparse(istate, 0);
31913191

31923192
trace2_region_enter_printf("index", "shared/do_write_index",
31933193
the_repository, "%s", get_tempfile_path(*temp));

sparse-index.c

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,14 @@ static int convert_to_sparse_rec(struct index_state *istate,
3434
int i, can_convert = 1;
3535
int start_converted = num_converted;
3636
enum pattern_match_result match;
37-
int dtype = DT_UNKNOWN;
3837
struct strbuf child_path = STRBUF_INIT;
39-
struct pattern_list *pl = istate->sparse_checkout_patterns;
4038

4139
/*
4240
* Is the current path outside of the sparse cone?
4341
* Then check if the region can be replaced by a sparse
4442
* directory entry (everything is sparse and merged).
4543
*/
46-
match = path_matches_pattern_list(ct_path, ct_pathlen,
47-
NULL, &dtype, pl, istate);
44+
match = path_in_sparse_checkout(ct_path, istate);
4845
if (match != NOT_MATCHED)
4946
can_convert = 0;
5047

@@ -127,41 +124,47 @@ static int index_has_unmerged_entries(struct index_state *istate)
127124
return 0;
128125
}
129126

130-
int convert_to_sparse(struct index_state *istate)
127+
int convert_to_sparse(struct index_state *istate, int flags)
131128
{
132129
int test_env;
133-
if (istate->split_index || istate->sparse_index ||
130+
131+
if (istate->split_index || istate->sparse_index || !istate->cache_nr ||
134132
!core_apply_sparse_checkout || !core_sparse_checkout_cone)
135133
return 0;
136134

137135
if (!istate->repo)
138136
istate->repo = the_repository;
139137

140-
/*
141-
* The GIT_TEST_SPARSE_INDEX environment variable triggers the
142-
* index.sparse config variable to be on.
143-
*/
144-
test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
145-
if (test_env >= 0)
146-
set_sparse_index_config(istate->repo, test_env);
147-
148-
/*
149-
* Only convert to sparse if index.sparse is set.
150-
*/
151-
prepare_repo_settings(istate->repo);
152-
if (!istate->repo->settings.sparse_index)
153-
return 0;
138+
if (!(flags & SPARSE_INDEX_IGNORE_CONFIG)) {
139+
/*
140+
* The GIT_TEST_SPARSE_INDEX environment variable triggers the
141+
* index.sparse config variable to be on.
142+
*/
143+
test_env = git_env_bool("GIT_TEST_SPARSE_INDEX", -1);
144+
if (test_env >= 0)
145+
set_sparse_index_config(istate->repo, test_env);
154146

155-
if (!istate->sparse_checkout_patterns) {
156-
istate->sparse_checkout_patterns = xcalloc(1, sizeof(struct pattern_list));
157-
if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0)
147+
/*
148+
* Only convert to sparse if index.sparse is set.
149+
*/
150+
prepare_repo_settings(istate->repo);
151+
if (!istate->repo->settings.sparse_index)
158152
return 0;
159153
}
160154

161-
if (!istate->sparse_checkout_patterns->use_cone_patterns) {
162-
warning(_("attempting to use sparse-index without cone mode"));
163-
return -1;
164-
}
155+
if (init_sparse_checkout_patterns(istate) < 0)
156+
return 0;
157+
158+
/*
159+
* We need cone-mode patterns to use sparse-index. If a user edits
160+
* their sparse-checkout file manually, then we can detect during
161+
* parsing that they are not actually using cone-mode patterns and
162+
* hence we need to abort this conversion _without error_. Warnings
163+
* already exist in the pattern parsing to inform the user of their
164+
* bad patterns.
165+
*/
166+
if (!istate->sparse_checkout_patterns->use_cone_patterns)
167+
return 0;
165168

166169
/*
167170
* NEEDSWORK: If we have unmerged entries, then stay full.
@@ -172,10 +175,15 @@ int convert_to_sparse(struct index_state *istate)
172175

173176
/* Clear and recompute the cache-tree */
174177
cache_tree_free(&istate->cache_tree);
175-
if (cache_tree_update(istate, 0)) {
176-
warning(_("unable to update cache-tree, staying full"));
177-
return -1;
178-
}
178+
/*
179+
* Silently return if there is a problem with the cache tree update,
180+
* which might just be due to a conflict state in some entry.
181+
*
182+
* This might create new tree objects, so be sure to use
183+
* WRITE_TREE_MISSING_OK.
184+
*/
185+
if (cache_tree_update(istate, WRITE_TREE_MISSING_OK))
186+
return 0;
179187

180188
remove_fsmonitor(istate);
181189

sparse-index.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
#define SPARSE_INDEX_H__
33

44
struct index_state;
5-
int convert_to_sparse(struct index_state *istate);
5+
#define SPARSE_INDEX_IGNORE_CONFIG (1 << 0)
6+
int convert_to_sparse(struct index_state *istate, int flags);
67

78
/*
89
* Some places in the codebase expect to search for a specific path.

0 commit comments

Comments
 (0)