Skip to content

Commit 7a66e59

Browse files
committed
sparse-checkout: clear tracked sparse dirs
When changing the scope of a sparse-checkout using cone mode, we might have some tracked directories go out of scope. The current logic removes the tracked files from within those directories, but leaves the ignored files within those directories. This is a bit unexpected to users who have given input to Git saying they don't need those directories anymore. Since these ignored files are typically build output or helper files from IDEs, the users should not need the files now that the tracked files are removed. If the tracked files reappear, then they will have newer timestamps than the build artifacts, so the artifacts will need to be regenerated anyway. Leaving these ignored files in the sparse directories makes it impossible to gain performance benefits in the sparse index. When we track into these directories, we need to know if the files are ignored or not, which might depend on the _tracked_ .gitignore file(s) within the sparse directory. This depends on the indexed version of the file, so the sparse directory must be expanded. By deleting the sparse directories when changing scope (or running 'git sparse-checkout reapply') we regain these performance benefits as if the repository was in a clean state. If users depend on ignored files within the sparse directories, then they have created a bad shape in their repository. This shape makes it impossible to get performance benefits using the sparse index, so they can workaround it (currently) by disabling the sparse index. Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
1 parent 88ab367 commit 7a66e59

File tree

3 files changed

+114
-9
lines changed

3 files changed

+114
-9
lines changed

builtin/sparse-checkout.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "wt-status.h"
1616
#include "quote.h"
1717
#include "sparse-index.h"
18+
#include "run-command.h"
1819

1920
static const char *empty_base = "";
2021

@@ -100,6 +101,71 @@ static int sparse_checkout_list(int argc, const char **argv)
100101
return 0;
101102
}
102103

104+
static void clean_tracked_sparse_directories(struct repository *r)
105+
{
106+
int i;
107+
struct strvec args = STRVEC_INIT;
108+
109+
/*
110+
* If we are not using cone mode patterns, then we cannot
111+
* delete directories outside of the sparse cone.
112+
*/
113+
if (!r || !r->index || !r->index->sparse_checkout_patterns ||
114+
!r->index->sparse_checkout_patterns->use_cone_patterns)
115+
return;
116+
/*
117+
* NEEDSWORK: For now, only use this behavior when index.sparse
118+
* is enabled. We may want this behavior enabled whenever using
119+
* cone mode patterns.
120+
*/
121+
prepare_repo_settings(r);
122+
if (!r->settings.sparse_index)
123+
return;
124+
125+
strvec_pushl(&args, "clean", "-dfx", NULL);
126+
127+
/*
128+
* Since we now depend on the sparse index to enable this
129+
* behavior, use it to our advantage. This process is more
130+
* complicated without it.
131+
*/
132+
convert_to_sparse(r->index);
133+
134+
for (i = 0; i < r->index->cache_nr; i++) {
135+
struct cache_entry *ce = r->index->cache[i];
136+
137+
/*
138+
* Is this a sparse directory? If so, then definitely
139+
* include it. All contained content is outside of the
140+
* patterns.
141+
*/
142+
if (S_ISSPARSEDIR(ce->ce_mode) &&
143+
repo_file_exists(r, ce->name)) {
144+
strvec_push(&args, ce->name);
145+
continue;
146+
}
147+
}
148+
149+
run_command_v_opt(args.v, RUN_GIT_CMD);
150+
151+
/*
152+
* The 'git clean -dfx -- <path> ...' command empties the
153+
* tracked directories outside of the sparse cone, but does not
154+
* delete the directories themselves. Remove them now.
155+
*/
156+
for (i = 2; i < args.nr; i++)
157+
rmdir_or_warn(args.v[i]);
158+
159+
strvec_clear(&args);
160+
161+
/*
162+
* This is temporary: the sparse-checkout builtin is not
163+
* integrated with the sparse-index yet, so we need to keep
164+
* it full during the process.
165+
*/
166+
ensure_full_index(r->index);
167+
}
168+
103169
static int update_working_directory(struct pattern_list *pl)
104170
{
105171
enum update_sparsity_result result;
@@ -141,6 +207,8 @@ static int update_working_directory(struct pattern_list *pl)
141207
else
142208
rollback_lock_file(&lock_file);
143209

210+
clean_tracked_sparse_directories(r);
211+
144212
r->index->sparse_checkout_patterns = NULL;
145213
return result;
146214
}
@@ -540,8 +608,11 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
540608
{
541609
int result;
542610
int changed_config = 0;
611+
struct pattern_list *old_pl = xcalloc(1, sizeof(*old_pl));
543612
struct pattern_list *pl = xcalloc(1, sizeof(*pl));
544613

614+
get_sparse_checkout_patterns(old_pl);
615+
545616
switch (m) {
546617
case ADD:
547618
if (core_sparse_checkout_cone)
@@ -567,7 +638,9 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
567638
set_config(MODE_NO_PATTERNS);
568639

569640
clear_pattern_list(pl);
641+
clear_pattern_list(old_pl);
570642
free(pl);
643+
free(old_pl);
571644
return result;
572645
}
573646

t/t1091-sparse-checkout-builtin.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,4 +642,42 @@ test_expect_success MINGW 'cone mode replaces backslashes with slashes' '
642642
check_files repo/deep a deeper1
643643
'
644644

645+
test_expect_success 'cone mode clears ignored subdirectories' '
646+
rm repo/.git/info/sparse-checkout &&
647+
648+
# NEEDSWORK: --sparse-index is required for now
649+
git -C repo sparse-checkout init --cone --sparse-index &&
650+
git -C repo sparse-checkout set deep/deeper1 &&
651+
652+
cat >repo/.gitignore <<-\EOF &&
653+
obj/
654+
*.o
655+
EOF
656+
657+
git -C repo add .gitignore &&
658+
git -C repo commit -m ".gitignore" &&
659+
660+
for dir in folder1 obj folder1/obj deep/deeper2 deep/deeper2/obj
661+
do
662+
mkdir repo/$dir || return 1
663+
done &&
664+
for file in folder1/obj/a obj/a folder1/file.o folder1.o \
665+
deep/deeper2/obj/a deep/deeper2/file.o file.o
666+
do
667+
echo ignored >repo/$file || return 1
668+
done &&
669+
670+
git -C repo status --porcelain=v2 >out &&
671+
test_must_be_empty out &&
672+
673+
git -C repo sparse-checkout reapply &&
674+
test_path_is_missing repo/folder1 &&
675+
test_path_is_missing repo/deep/deeper2 &&
676+
test_path_is_dir repo/obj &&
677+
test_path_is_file repo/file.o &&
678+
679+
git -C repo status --porcelain=v2 >out &&
680+
test_must_be_empty out
681+
'
682+
645683
test_done

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ test_expect_success 'sparse-index is not expanded' '
594594
ensure_not_expanded add .
595595
'
596596

597-
test_expect_failure 'sparse-index is not expanded (with ignored files outside cone)' '
597+
test_expect_success 'sparse-index is not expanded (with ignored files outside cone)' '
598598
init_repos &&
599599
600600
write_script adjust_repo <<-\EOF &&
@@ -606,6 +606,7 @@ test_expect_failure 'sparse-index is not expanded (with ignored files outside co
606606
EOF
607607
608608
run_on_all ../adjust_repo &&
609+
git -C sparse-index sparse-checkout reapply &&
609610
610611
ensure_not_expanded status &&
611612
ensure_not_expanded commit --allow-empty -m empty &&
@@ -622,14 +623,7 @@ test_expect_failure 'sparse-index is not expanded (with ignored files outside co
622623
git -C sparse-index reset --hard &&
623624
ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
624625
git -C sparse-index reset --hard &&
625-
ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
626-
627-
echo >>sparse-index/README.md &&
628-
ensure_not_expanded add -A &&
629-
echo >>sparse-index/extra.txt &&
630-
ensure_not_expanded add extra.txt &&
631-
echo >>sparse-index/untracked.txt &&
632-
ensure_not_expanded add .
626+
ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1
633627
'
634628

635629
test_expect_success 'reset mixed and checkout orphan' '

0 commit comments

Comments
 (0)