Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sparse Index] Delete ignored files outside of cone #396

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions builtin/sparse-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "wt-status.h"
#include "quote.h"
#include "sparse-index.h"
#include "run-command.h"

static const char *empty_base = "";

Expand Down Expand Up @@ -100,6 +101,76 @@ static int sparse_checkout_list(int argc, const char **argv)
return 0;
}

static void clean_tracked_sparse_directories(struct repository *r)
{
int i;
struct strvec args = STRVEC_INIT;

/*
* If we are not using cone mode patterns, then we cannot
* delete directories outside of the sparse cone.
*/
if (!r || !r->index || !r->index->sparse_checkout_patterns ||
!r->index->sparse_checkout_patterns->use_cone_patterns)
return;
/*
* NEEDSWORK: For now, only use this behavior when index.sparse
* is enabled. We may want this behavior enabled whenever using
* cone mode patterns.
*/
prepare_repo_settings(r);
if (!r->settings.sparse_index)
return;

strvec_pushl(&args, "clean", "-dfx", "--", NULL);

/*
* Since we now depend on the sparse index to enable this
* behavior, use it to our advantage. This process is more
* complicated without it.
*/
convert_to_sparse(r->index);

for (i = 0; i < r->index->cache_nr; i++) {
struct cache_entry *ce = r->index->cache[i];

/*
* Is this a sparse directory? If so, then definitely
* include it. All contained content is outside of the
* patterns.
*/
if (S_ISSPARSEDIR(ce->ce_mode) &&
repo_file_exists(r, ce->name)) {
strvec_push(&args, ce->name);
continue;
}
}

/*
* Only run if we found an existing sparse directory, otherwise
* the clean will be across the entire worktree!
*/
if (args.nr > 3)
run_command_v_opt(args.v, RUN_GIT_CMD);

/*
* The 'git clean -dfx -- <path> ...' command empties the
* tracked directories outside of the sparse cone, but does not
* delete the directories themselves. Remove them now.
*/
for (i = 3; i < args.nr; i++)
rmdir_or_warn(args.v[i]);

strvec_clear(&args);

/*
* This is temporary: the sparse-checkout builtin is not
* integrated with the sparse-index yet, so we need to keep
* it full during the process.
*/
ensure_full_index(r->index);
}

static int update_working_directory(struct pattern_list *pl)
{
enum update_sparsity_result result;
Expand Down Expand Up @@ -141,6 +212,8 @@ static int update_working_directory(struct pattern_list *pl)
else
rollback_lock_file(&lock_file);

clean_tracked_sparse_directories(r);

r->index->sparse_checkout_patterns = NULL;
return result;
}
Expand Down Expand Up @@ -540,8 +613,11 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
{
int result;
int changed_config = 0;
struct pattern_list *old_pl = xcalloc(1, sizeof(*old_pl));
struct pattern_list *pl = xcalloc(1, sizeof(*pl));

get_sparse_checkout_patterns(old_pl);

switch (m) {
case ADD:
if (core_sparse_checkout_cone)
Expand All @@ -567,7 +643,9 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m)
set_config(MODE_NO_PATTERNS);

clear_pattern_list(pl);
clear_pattern_list(old_pl);
free(pl);
free(old_pl);
return result;
}

Expand Down
35 changes: 35 additions & 0 deletions t/t1091-sparse-checkout-builtin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -642,4 +642,39 @@ test_expect_success MINGW 'cone mode replaces backslashes with slashes' '
check_files repo/deep a deeper1
'

test_expect_success 'cone mode clears ignored subdirectories' '
rm repo/.git/info/sparse-checkout &&

# NEEDSWORK: --sparse-index is required for now
git -C repo sparse-checkout init --cone --sparse-index &&
git -C repo sparse-checkout set deep/deeper1 &&

cat >repo/.gitignore <<-\EOF &&
obj/
*.o
EOF

git -C repo add .gitignore &&
git -C repo commit -m ".gitignore" &&

mkdir -p repo/obj repo/folder1/obj repo/deep/deeper2/obj &&
for file in folder1/obj/a obj/a folder1/file.o folder1.o \
deep/deeper2/obj/a deep/deeper2/file.o file.o
do
echo ignored >repo/$file || return 1
done &&

git -C repo status --porcelain=v2 >out &&
test_must_be_empty out &&

git -C repo sparse-checkout reapply &&
test_path_is_missing repo/folder1 &&
test_path_is_missing repo/deep/deeper2 &&
test_path_is_dir repo/obj &&
test_path_is_file repo/file.o &&

git -C repo status --porcelain=v2 >out &&
test_must_be_empty out
'

test_done
45 changes: 45 additions & 0 deletions t/t1092-sparse-checkout-compatibility.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ test_expect_success 'setup' '
cp -r deep/deeper1/0 folder2 &&
echo >>folder1/0/0/0 &&
echo >>folder2/0/1 &&

cat >.gitignore <<-\EOF &&
obj/
*.o
EOF

git add . &&
git commit -m "initial commit" &&
git checkout -b base &&
Expand All @@ -62,6 +68,7 @@ test_expect_success 'setup' '
EOF
cp folder1/larger-content folder2/ &&
cp folder1/larger-content deep/deeper1/ &&

git add . &&
git commit -m "add interesting rename content" &&

Expand Down Expand Up @@ -587,6 +594,44 @@ test_expect_success 'sparse-index is not expanded' '
ensure_not_expanded add .
'

test_expect_success 'sparse-index is not expanded (with ignored files outside cone)' '
init_repos &&

write_script adjust_repo <<-\EOF &&
mkdir folder1 obj folder1/obj &&
echo ignored >folder1/obj/a &&
echo ignored >obj/a &&c
echo ignored >folder1/file.o &&
echo ignored >folder1.o
EOF

run_on_all ../adjust_repo &&
git -C sparse-index sparse-checkout reapply &&

ensure_not_expanded status &&
ensure_not_expanded commit --allow-empty -m empty &&
echo >>sparse-index/a &&
ensure_not_expanded commit -a -m a &&
echo >>sparse-index/a &&
ensure_not_expanded commit --include a -m a &&
echo >>sparse-index/deep/deeper1/a &&
ensure_not_expanded commit --include deep/deeper1/a -m deeper &&
ensure_not_expanded checkout rename-out-to-out &&
ensure_not_expanded checkout - &&
ensure_not_expanded switch rename-out-to-out &&
ensure_not_expanded switch - &&
git -C sparse-index reset --hard &&
ensure_not_expanded checkout rename-out-to-out -- deep/deeper1 &&
git -C sparse-index reset --hard &&
ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 &&
echo >>sparse-index/README.md &&
ensure_not_expanded add -A &&
echo >>sparse-index/extra.txt &&
ensure_not_expanded add extra.txt &&
echo >>sparse-index/untracked.txt &&
ensure_not_expanded add .
'

test_expect_success 'reset mixed and checkout orphan' '
init_repos &&

Expand Down
38 changes: 21 additions & 17 deletions t/t7519-status-fsmonitor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -389,43 +389,47 @@ test_expect_success 'status succeeds after staging/unstaging' '
# If "!" is supplied, then we verify that we do not call ensure_full_index
derrickstolee marked this conversation as resolved.
Show resolved Hide resolved
# during a call to 'git status'. Otherwise, we verify that we _do_ call it.
check_sparse_index_behavior () {
git status --porcelain=v2 >expect &&
git sparse-checkout init --cone --sparse-index &&
git sparse-checkout set dir1 dir2 &&
git -C full status --porcelain=v2 >expect &&
GIT_TRACE2_EVENT="$(pwd)/trace2.txt" GIT_TRACE2_EVENT_NESTING=10 \
git status --porcelain=v2 >actual &&
git -C sparse status --porcelain=v2 >actual &&
test_region $1 index ensure_full_index trace2.txt &&
test_region fsm_hook query trace2.txt &&
test_cmp expect actual &&
rm trace2.txt &&
git sparse-checkout disable
rm trace2.txt
}

test_expect_success 'status succeeds with sparse index' '
git reset --hard &&
git clone . full &&
git clone . sparse &&
git -C sparse sparse-checkout init --cone --sparse-index &&
git -C sparse sparse-checkout set dir1 dir2 &&

test_config core.fsmonitor "$TEST_DIRECTORY/t7519/fsmonitor-all" &&
check_sparse_index_behavior ! &&

write_script .git/hooks/fsmonitor-test<<-\EOF &&
write_script .git/hooks/fsmonitor-test <<-\EOF &&
printf "last_update_token\0"
EOF
git config core.fsmonitor .git/hooks/fsmonitor-test &&
git -C full config core.fsmonitor ../.git/hooks/fsmonitor-test &&
git -C sparse config core.fsmonitor ../.git/hooks/fsmonitor-test &&
check_sparse_index_behavior ! &&

write_script .git/hooks/fsmonitor-test<<-\EOF &&
write_script .git/hooks/fsmonitor-test <<-\EOF &&
printf "last_update_token\0"
printf "dir1/modified\0"
EOF
check_sparse_index_behavior ! &&

cp -r dir1 dir1a &&
git add dir1a &&
git commit -m "add dir1a" &&
git -C sparse sparse-checkout add dir1a &&

for repo in full sparse
do
cp -r $repo/dir1 $repo/dir1a &&
git -C $repo add dir1a &&
git -C $repo commit -m "add dir1a" || return 1
done &&
git -C sparse sparse-checkout set dir1 dir2 &&

# This one modifies outside the sparse-checkout definition
# and hence we expect to expand the sparse-index.
write_script .git/hooks/fsmonitor-test<<-\EOF &&
write_script .git/hooks/fsmonitor-test <<-\EOF &&
printf "last_update_token\0"
printf "dir1a/modified\0"
EOF
Expand Down
18 changes: 13 additions & 5 deletions unpack-trees.c
Original file line number Diff line number Diff line change
Expand Up @@ -1286,9 +1286,10 @@ static int sparse_dir_matches_path(const struct cache_entry *ce,
static struct cache_entry *find_cache_entry(struct traverse_info *info,
const struct name_entry *p)
{
struct cache_entry *ce;
struct cache_entry *ce = NULL;
int pos = find_cache_pos(info, p->path, p->pathlen);
struct unpack_trees_options *o = info->data;
struct strbuf full_path = STRBUF_INIT;

if (0 <= pos)
return o->src_index->cache[pos];
Expand All @@ -1304,6 +1305,10 @@ static struct cache_entry *find_cache_entry(struct traverse_info *info,
if (pos < 0 || pos >= o->src_index->cache_nr)
return NULL;

strbuf_addstr(&full_path, info->traverse_path);
strbuf_add(&full_path, p->path, p->pathlen);
strbuf_addch(&full_path, '/');

/*
* We might have multiple entries between 'pos' and
* the actual sparse-directory entry, so start walking
Expand All @@ -1315,17 +1320,20 @@ static struct cache_entry *find_cache_entry(struct traverse_info *info,
/*
* Have we walked too far?
*/
if (strncmp(ce->name, p->path, p->pathlen))
return NULL;
if (strncmp(ce->name, full_path.buf, full_path.len)) {
ce = NULL;
break;
}

if (S_ISSPARSEDIR(ce->ce_mode) &&
sparse_dir_matches_path(ce, info, p))
return ce;
break;

pos--;
}

return NULL;
strbuf_release(&full_path);
return ce;
}

static void debug_path(struct traverse_info *info)
Expand Down