Skip to content

Commit 4bcd533

Browse files
Merge pull request #410: Sparse Index: latest integrations
``` 6e74958 p2000: add 'git checkout -' test and decrease depth 3e1d03c p2000: compress repo names cd94f82 commit: integrate with sparse-index 65e79b8 sparse-index: recompute cache-tree e9a9981 checkout: stop expanding sparse indexes 4b801c8 t1092: document bad 'git checkout' behavior 71e3015 unpack-trees: resolve sparse-directory/file conflicts 5e96df4 t1092: test merge conflicts outside cone defab1b add: allow operating on a sparse-only index 9fc4313 pathspec: stop calling ensure_full_index 0ec03ab add: ignore outside the sparse-checkout in refresh() adf5b15 add: remove ensure_full_index() with --renormalize ``` These commits are equivalent to those already in `next` via gitgitgadget#999. ``` 80b8d6c Merge branch 'sparse-index/add' into stolee/sparse-index/add ``` This merge resolves conflicts with some work that happened in parallel, but is already in upstream `master`. ``` c407b2c t7519: rewrite sparse index test 9dad0d2 sparse-index: silently return when not using cone-mode patterns 2974920 sparse-index: silently return when cache tree fails e7cdaa0 unpack-trees: fix nested sparse-dir search 347410c sparse-checkout: create helper methods 4537233 attr: be careful about sparse directories 5282a86 sparse-index: add SPARSE_INDEX_MEMORY_ONLY flag 3a2f316 sparse-checkout: clear tracked sparse dirs fb47b56 sparse-checkout: add config to disable deleting dirs ``` These commits are the ones under review as of gitgitgadget#1009. Recent review made this less stable. It's a slightly different and more robust version of #396. > Note: I'm still not done with the feedback for upstream, but the remaining feedback is "can we add tests that cover these tricky technical bits?" and in `microsoft/git` these are already covered by the Scalar functional tests (since that's how they were found). ``` 080b02c diff: ignore sparse paths in diffstat d91a647 merge: make sparse-aware with ORT df49b5f merge-ort: expand only for out-of-cone conflicts cdecb85 t1092: add cherry-pick, rebase tests 0c1ecfb sequencer: ensure full index if not ORT strategy 406dfbe sparse-index: integrate with cherry-pick and rebase ``` These commits integrate with `git merge`, `git cherry-pick`, `git revert`, and `git rebase` as of gitgitgadget#1019. This got some feedback that changed how the tests were working so they are more robust. This led to a new commit (0c1ecfb). ``` cbb0ab3 Merge branch 'sparse-index/merge' into vfs-2.33.0 acb8623 t7524: test no longer fails ``` Finally, the commits are merged into `vfs-2.33.0` and also we include a fix to a `microsoft/git` test that is no longer broken. Cc: @vdye and @ldennington to get a (possibly overwhelming?) taste of sparse-index stuff. If you focus solely on the `git merge` commits you'll get a feel for what a sparse index integration looks like.
2 parents d5ec357 + acb8623 commit 4bcd533

23 files changed

+524
-76
lines changed

Documentation/config/index.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
index.deleteSparseDirectories::
2+
When enabled, the cone mode sparse-checkout feature will delete
3+
directories that are outside of the sparse-checkout cone, unless
4+
such a directory contains an untracked, non-ignored file. Defaults
5+
to true.
6+
17
index.recordEndOfIndexEntries::
28
Specifies whether the index file should include an "End Of Index
39
Entry" section. This reduces index load time on multiprocessor

Documentation/git-sparse-checkout.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,16 @@ 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+
When changing the sparse-checkout patterns in cone mode, Git will inspect each
214+
tracked directory that is not within the sparse-checkout cone to see if it
215+
contains any untracked files. If all of those files are ignored due to the
216+
`.gitignore` patterns, then the directory will be deleted. If any of the
217+
untracked files within that directory is not ignored, then no deletions will
218+
occur within that directory and a warning message will appear. If these files
219+
are important, then reset your sparse-checkout definition so they are included,
220+
use `git add` and `git commit` to store them, then remove any remaining files
221+
manually to ensure Git can behave optimally.
222+
213223

214224
SUBMODULES
215225
----------

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+
* The .gitattributes file only applies to files within its
750+
* parent directory. In the case of cone-mode sparse-checkout,
751+
* the .gitattributes file is sparse if and only if all paths
752+
* within that directory are also sparse. Thus, don't load the
753+
* .gitattributes file since it will not matter.
754+
*
755+
* In the case of a sparse index, it is critical that we don't go
756+
* looking for a .gitattributes file, as the index will expand.
757+
*/
758+
if (!path_in_cone_modesparse_checkout(path, istate))
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: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,6 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags)
144144
{
145145
int i, retval = 0;
146146

147-
/* TODO: audit for interaction with sparse-index. */
148-
ensure_full_index(&the_index);
149147
for (i = 0; i < active_nr; i++) {
150148
struct cache_entry *ce = active_cache[i];
151149

@@ -198,7 +196,10 @@ static int refresh(int verbose, const struct pathspec *pathspec)
198196
_("Unstaged changes after refreshing the index:"));
199197
for (i = 0; i < pathspec->nr; i++) {
200198
if (!seen[i]) {
201-
if (matches_skip_worktree(pathspec, i, &skip_worktree_seen)) {
199+
const char *path = pathspec->items[i].original;
200+
201+
if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) ||
202+
!path_in_sparse_checkout(path, &the_index)) {
202203
string_list_append(&only_match_skip_worktree,
203204
pathspec->items[i].original);
204205
} else {
@@ -532,6 +533,9 @@ int cmd_add(int argc, const char **argv, const char *prefix)
532533
add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize;
533534
require_pathspec = !(take_worktree_changes || (0 < addremove_explicit));
534535

536+
prepare_repo_settings(the_repository);
537+
the_repository->settings.command_requires_full_index = 0;
538+
535539
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
536540

537541
/*

builtin/merge.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
12761276
if (argc == 2 && !strcmp(argv[1], "-h"))
12771277
usage_with_options(builtin_merge_usage, builtin_merge_options);
12781278

1279+
prepare_repo_settings(the_repository);
1280+
the_repository->settings.command_requires_full_index = 0;
1281+
12791282
/*
12801283
* Check if we are _not_ on a detached HEAD, i.e. if there is a
12811284
* current branch.

builtin/rebase.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
559559
argc = parse_options(argc, argv, prefix, options,
560560
builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
561561

562+
prepare_repo_settings(the_repository);
563+
the_repository->settings.command_requires_full_index = 0;
564+
562565
if (!is_null_oid(&squash_onto))
563566
opts.squash_onto = &squash_onto;
564567

@@ -1430,6 +1433,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
14301433
usage_with_options(builtin_rebase_usage,
14311434
builtin_rebase_options);
14321435

1436+
prepare_repo_settings(the_repository);
1437+
the_repository->settings.command_requires_full_index = 0;
1438+
14331439
options.allow_empty_message = 1;
14341440
git_config(rebase_config, &options);
14351441
/* options.gpg_sign_opt will be either "-S" or NULL */

builtin/revert.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
136136
PARSE_OPT_KEEP_ARGV0 |
137137
PARSE_OPT_KEEP_UNKNOWN);
138138

139+
prepare_repo_settings(the_repository);
140+
the_repository->settings.command_requires_full_index = 0;
141+
139142
/* implies allow_empty */
140143
if (opts->keep_redundant_commits)
141144
opts->allow_empty = 1;

builtin/sparse-checkout.c

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,106 @@ 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, value, 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+
* Users can disable this behavior.
124+
*/
125+
if (!repo_config_get_bool(r, "index.deletesparsedirectories", &value) &&
126+
!value)
127+
return;
128+
129+
/*
130+
* Use the sparse index as a data structure to assist finding
131+
* directories that are safe to delete. This conversion to a
132+
* sparse index will not delete directories that contain
133+
* conflicted entries or submodules.
134+
*/
135+
if (!r->index->sparse_index) {
136+
/*
137+
* If something, such as a merge conflict or other concern,
138+
* prevents us from converting to a sparse index, then do
139+
* not try deleting files.
140+
*/
141+
if (convert_to_sparse(r->index, SPARSE_INDEX_MEMORY_ONLY))
142+
return;
143+
was_full = 1;
144+
}
145+
146+
strbuf_addstr(&path, r->worktree);
147+
strbuf_complete(&path, '/');
148+
pathlen = path.len;
149+
150+
/*
151+
* Collect directories that have gone out of scope but also
152+
* exist on disk, so there is some work to be done. We need to
153+
* store the entries in a list before exploring, since that might
154+
* expand the sparse-index again.
155+
*/
156+
for (i = 0; i < r->index->cache_nr; i++) {
157+
struct cache_entry *ce = r->index->cache[i];
158+
159+
if (S_ISSPARSEDIR(ce->ce_mode) &&
160+
repo_file_exists(r, ce->name))
161+
string_list_append(&sparse_dirs, ce->name);
162+
}
163+
164+
for_each_string_list_item(item, &sparse_dirs) {
165+
struct dir_struct dir = DIR_INIT;
166+
struct pathspec p = { 0 };
167+
struct strvec s = STRVEC_INIT;
168+
169+
strbuf_setlen(&path, pathlen);
170+
strbuf_addstr(&path, item->string);
171+
172+
dir.flags |= DIR_SHOW_IGNORED_TOO;
173+
174+
setup_standard_excludes(&dir);
175+
strvec_push(&s, path.buf);
176+
177+
parse_pathspec(&p, PATHSPEC_GLOB, 0, NULL, s.v);
178+
fill_directory(&dir, r->index, &p);
179+
180+
if (dir.nr) {
181+
warning(_("directory '%s' contains untracked files,"
182+
" but is not in the sparse-checkout cone"),
183+
item->string);
184+
} else if (remove_dir_recursively(&path, 0)) {
185+
/*
186+
* Removal is "best effort". If something blocks
187+
* the deletion, then continue with a warning.
188+
*/
189+
warning(_("failed to remove directory '%s'"),
190+
item->string);
191+
}
192+
193+
dir_clear(&dir);
194+
}
195+
196+
string_list_clear(&sparse_dirs, 0);
197+
strbuf_release(&path);
198+
199+
if (was_full)
200+
ensure_full_index(r->index);
201+
}
202+
103203
static int update_working_directory(struct pattern_list *pl)
104204
{
105205
enum update_sparsity_result result;
@@ -141,6 +241,8 @@ static int update_working_directory(struct pattern_list *pl)
141241
else
142242
rollback_lock_file(&lock_file);
143243

244+
clean_tracked_sparse_directories(r);
245+
144246
r->index->sparse_checkout_patterns = NULL;
145247
return result;
146248
}

diff.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "parse-options.h"
2727
#include "help.h"
2828
#include "promisor-remote.h"
29+
#include "dir.h"
2930

3031
#ifdef NO_FAST_WORKING_DIRECTORY
3132
#define FAST_WORKING_DIRECTORY 0
@@ -3900,6 +3901,13 @@ static int reuse_worktree_file(struct index_state *istate,
39003901
if (!FAST_WORKING_DIRECTORY && !want_file && has_object_pack(oid))
39013902
return 0;
39023903

3904+
/*
3905+
* If this path does not match our sparse-checkout definition,
3906+
* then the file will not be in the working directory.
3907+
*/
3908+
if (!path_in_sparse_checkout(name, istate))
3909+
return 0;
3910+
39033911
/*
39043912
* Similarly, if we'd have to convert the file contents anyway, that
39053913
* makes the optimization not worthwhile.

dir.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,60 @@ enum pattern_match_result path_matches_pattern_list(
14941494
return result;
14951495
}
14961496

1497+
int init_sparse_checkout_patterns(struct index_state *istate)
1498+
{
1499+
if (!core_apply_sparse_checkout ||
1500+
istate->sparse_checkout_patterns)
1501+
return 0;
1502+
1503+
CALLOC_ARRAY(istate->sparse_checkout_patterns, 1);
1504+
1505+
if (get_sparse_checkout_patterns(istate->sparse_checkout_patterns) < 0) {
1506+
FREE_AND_NULL(istate->sparse_checkout_patterns);
1507+
return -1;
1508+
}
1509+
1510+
return 0;
1511+
}
1512+
1513+
static int path_in_sparse_checkout_1(const char *path,
1514+
struct index_state *istate,
1515+
int require_cone_mode)
1516+
{
1517+
const char *base;
1518+
int dtype = DT_REG;
1519+
init_sparse_checkout_patterns(istate);
1520+
1521+
/*
1522+
* We default to accepting a path if there are no patterns or
1523+
* they are of the wrong type.
1524+
*/
1525+
if (!istate->sparse_checkout_patterns ||
1526+
(require_cone_mode &&
1527+
!istate->sparse_checkout_patterns->use_cone_patterns))
1528+
return 1;
1529+
1530+
1531+
1532+
base = strrchr(path, '/');
1533+
return path_matches_pattern_list(path, strlen(path), base ? base + 1 : path,
1534+
&dtype,
1535+
istate->sparse_checkout_patterns,
1536+
istate) > 0;
1537+
}
1538+
1539+
int path_in_sparse_checkout(const char *path,
1540+
struct index_state *istate)
1541+
{
1542+
return path_in_sparse_checkout_1(path, istate, 0);
1543+
}
1544+
1545+
int path_in_cone_modesparse_checkout(const char *path,
1546+
struct index_state *istate)
1547+
{
1548+
return path_in_sparse_checkout_1(path, istate, 1);
1549+
}
1550+
14971551
static struct path_pattern *last_matching_pattern_from_lists(
14981552
struct dir_struct *dir, struct index_state *istate,
14991553
const char *pathname, int pathlen,

dir.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,14 @@ 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+
int path_in_sparse_checkout(const char *path,
401+
struct index_state *istate);
402+
int path_in_cone_modesparse_checkout(const char *path,
403+
struct index_state *istate);
404+
397405
struct dir_entry *dir_add_ignored(struct dir_struct *dir,
398406
struct index_state *istate,
399407
const char *pathname, int len);

merge-ort.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,6 +4058,21 @@ static int record_conflicted_index_entries(struct merge_options *opt)
40584058
if (strmap_empty(&opt->priv->conflicted))
40594059
return 0;
40604060

4061+
/*
4062+
* We are in a conflicted state. These conflicts might be inside
4063+
* sparse-directory entries, so check if any entries are outside
4064+
* of the sparse-checkout cone preemptively.
4065+
*
4066+
* We set original_cache_nr below, but that might change if
4067+
* index_name_pos() calls ask for paths within sparse directories.
4068+
*/
4069+
strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
4070+
if (!path_in_sparse_checkout(e->key, index)) {
4071+
ensure_full_index(index);
4072+
break;
4073+
}
4074+
}
4075+
40614076
/* If any entries have skip_worktree set, we'll have to check 'em out */
40624077
state.force = 1;
40634078
state.quiet = 1;

merge-recursive.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3750,6 +3750,9 @@ int merge_recursive(struct merge_options *opt,
37503750
assert(opt->ancestor == NULL ||
37513751
!strcmp(opt->ancestor, "constructed merge base"));
37523752

3753+
prepare_repo_settings(opt->repo);
3754+
opt->repo->settings.command_requires_full_index = 1;
3755+
37533756
if (merge_start(opt, repo_get_commit_tree(opt->repo, h1)))
37543757
return -1;
37553758
clean = merge_recursive_internal(opt, h1, h2, merge_bases, result);

pathspec.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
3737
num_unmatched++;
3838
if (!num_unmatched)
3939
return;
40-
/* TODO: audit for interaction with sparse-index. */
41-
ensure_full_index(istate);
4240
for (i = 0; i < istate->cache_nr; i++) {
4341
const struct cache_entry *ce = istate->cache[i];
4442
if (sw_action == PS_IGNORE_SKIP_WORKTREE && ce_skip_worktree(ce))

read-cache.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3111,7 +3111,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l
31113111
int ret;
31123112
int was_full = !istate->sparse_index;
31133113

3114-
ret = convert_to_sparse(istate);
3114+
ret = convert_to_sparse(istate, 0);
31153115

31163116
if (ret) {
31173117
warning(_("failed to convert to a sparse-index"));
@@ -3224,7 +3224,7 @@ static int write_shared_index(struct index_state *istate,
32243224
int ret, was_full = !istate->sparse_index;
32253225

32263226
move_cache_to_base_index(istate);
3227-
convert_to_sparse(istate);
3227+
convert_to_sparse(istate, 0);
32283228

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

0 commit comments

Comments
 (0)