From 9a2f4cec74fdec12daf245ede20664b85e1c82cf Mon Sep 17 00:00:00 2001 From: Laurence McGlashan Date: Sat, 14 Jun 2025 10:20:30 +0100 Subject: [PATCH] Add sparse checkout test repository. Add core.sparseCheckoutCone to the repository config cache. --- src/libgit2/config_cache.c | 2 + src/libgit2/repository.h | 8 +- src/libgit2/sparse_checkout.c | 459 ++++++++++++++++++ src/libgit2/sparse_checkout.h | 21 + tests/libgit2/sparse/config.c | 46 ++ tests/libgit2/sparse/path.c | 88 ++++ tests/resources/sparse/.gitted/HEAD | 1 + tests/resources/sparse/.gitted/config | 9 + .../resources/sparse/.gitted/config.worktree | 5 + tests/resources/sparse/.gitted/description | 1 + tests/resources/sparse/.gitted/index | Bin 0 -> 899 bytes tests/resources/sparse/.gitted/info/exclude | 6 + .../sparse/.gitted/info/sparse-checkout | 15 + .../02/69f9c363ca61eafe80a18381857f645b1a0e47 | Bin 0 -> 36 bytes .../1c/7a4d2ea77803076deb800b6ab85191dceb4b56 | Bin 0 -> 82 bytes .../33/c60f7155d1a2390854133026dc5f67ef8c77e0 | 2 + .../51/63c499306d855963c8a36d80abb8f3d0ceac35 | Bin 0 -> 36 bytes .../54/841cdb70bf59cf37273377a5dae0c17b56ce47 | Bin 0 -> 38 bytes .../5e/325348137c382d932d6fe48d1352380a11c3d8 | Bin 0 -> 38 bytes .../6b/4b3819187deca0bc2a1a35a8e023d4219843d8 | 1 + .../6c/1ec57d81a7de748ba8b302d2192a100cd9b7c7 | Bin 0 -> 159 bytes .../78/fadb360d2ed95db233cc5aa17ad40d86ecbaf9 | Bin 0 -> 81 bytes .../7d/799f3d4592c4c0ec72a1a7cf3e71111b40a46a | Bin 0 -> 36 bytes .../86/e5edc6612366127e2e3a7413e0595db10034b3 | Bin 0 -> 38 bytes .../8e/5fb0934b7422e90be895b355708f8836f2ab41 | Bin 0 -> 36 bytes .../8e/6ae1f061fc4b58e1d8a2725fb707863c0ce7f2 | Bin 0 -> 79 bytes .../a9/6055bb0e7a0e7af3f1783fbfad767479950577 | Bin 0 -> 80 bytes .../e1/ccb792d95e6198f341ece98a7795eac3ca1b6f | Bin 0 -> 32 bytes .../ef/2e5779adebd7829227d5477f4cd737dcc25629 | Bin 0 -> 32 bytes .../resources/sparse/.gitted/refs/heads/main | 1 + tests/resources/sparse/a/fileA.txt | Bin 0 -> 20 bytes tests/resources/sparse/a/fileB.txt | Bin 0 -> 20 bytes tests/resources/sparse/c/d/fileA.txt | Bin 0 -> 24 bytes tests/resources/sparse/c/d/fileB.txt | Bin 0 -> 24 bytes tests/resources/sparse/c/fileA.txt | Bin 0 -> 20 bytes tests/resources/sparse/fileA.txt | Bin 0 -> 16 bytes tests/resources/sparse/fileB.txt | Bin 0 -> 16 bytes 37 files changed, 664 insertions(+), 1 deletion(-) create mode 100644 src/libgit2/sparse_checkout.c create mode 100644 src/libgit2/sparse_checkout.h create mode 100644 tests/libgit2/sparse/config.c create mode 100644 tests/libgit2/sparse/path.c create mode 100644 tests/resources/sparse/.gitted/HEAD create mode 100644 tests/resources/sparse/.gitted/config create mode 100644 tests/resources/sparse/.gitted/config.worktree create mode 100644 tests/resources/sparse/.gitted/description create mode 100644 tests/resources/sparse/.gitted/index create mode 100644 tests/resources/sparse/.gitted/info/exclude create mode 100644 tests/resources/sparse/.gitted/info/sparse-checkout create mode 100644 tests/resources/sparse/.gitted/objects/02/69f9c363ca61eafe80a18381857f645b1a0e47 create mode 100644 tests/resources/sparse/.gitted/objects/1c/7a4d2ea77803076deb800b6ab85191dceb4b56 create mode 100644 tests/resources/sparse/.gitted/objects/33/c60f7155d1a2390854133026dc5f67ef8c77e0 create mode 100644 tests/resources/sparse/.gitted/objects/51/63c499306d855963c8a36d80abb8f3d0ceac35 create mode 100644 tests/resources/sparse/.gitted/objects/54/841cdb70bf59cf37273377a5dae0c17b56ce47 create mode 100644 tests/resources/sparse/.gitted/objects/5e/325348137c382d932d6fe48d1352380a11c3d8 create mode 100644 tests/resources/sparse/.gitted/objects/6b/4b3819187deca0bc2a1a35a8e023d4219843d8 create mode 100644 tests/resources/sparse/.gitted/objects/6c/1ec57d81a7de748ba8b302d2192a100cd9b7c7 create mode 100644 tests/resources/sparse/.gitted/objects/78/fadb360d2ed95db233cc5aa17ad40d86ecbaf9 create mode 100644 tests/resources/sparse/.gitted/objects/7d/799f3d4592c4c0ec72a1a7cf3e71111b40a46a create mode 100644 tests/resources/sparse/.gitted/objects/86/e5edc6612366127e2e3a7413e0595db10034b3 create mode 100644 tests/resources/sparse/.gitted/objects/8e/5fb0934b7422e90be895b355708f8836f2ab41 create mode 100644 tests/resources/sparse/.gitted/objects/8e/6ae1f061fc4b58e1d8a2725fb707863c0ce7f2 create mode 100644 tests/resources/sparse/.gitted/objects/a9/6055bb0e7a0e7af3f1783fbfad767479950577 create mode 100644 tests/resources/sparse/.gitted/objects/e1/ccb792d95e6198f341ece98a7795eac3ca1b6f create mode 100644 tests/resources/sparse/.gitted/objects/ef/2e5779adebd7829227d5477f4cd737dcc25629 create mode 100644 tests/resources/sparse/.gitted/refs/heads/main create mode 100644 tests/resources/sparse/a/fileA.txt create mode 100644 tests/resources/sparse/a/fileB.txt create mode 100644 tests/resources/sparse/c/d/fileA.txt create mode 100644 tests/resources/sparse/c/d/fileB.txt create mode 100644 tests/resources/sparse/c/fileA.txt create mode 100644 tests/resources/sparse/fileA.txt create mode 100644 tests/resources/sparse/fileB.txt diff --git a/src/libgit2/config_cache.c b/src/libgit2/config_cache.c index 05d9d5828e0..79f1d0ae819 100644 --- a/src/libgit2/config_cache.c +++ b/src/libgit2/config_cache.c @@ -86,6 +86,8 @@ static struct map_data _configmaps[] = { {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT }, {"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT }, {"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT }, + {"core.sparsecheckout", NULL, 0, GIT_SPARSECHECKOUT_DEFAULT }, + {"core.sparsecheckoutcone", NULL, 0, GIT_SPARSECHECKOUTCONE_DEFAULT }, }; int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item) diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h index 7da2d165577..324f0fcfb62 100644 --- a/src/libgit2/repository.h +++ b/src/libgit2/repository.h @@ -55,6 +55,8 @@ typedef enum { GIT_CONFIGMAP_PROTECTNTFS, /* core.protectNTFS */ GIT_CONFIGMAP_FSYNCOBJECTFILES, /* core.fsyncObjectFiles */ GIT_CONFIGMAP_LONGPATHS, /* core.longpaths */ + GIT_CONFIGMAP_SPARSECHECKOUT, /* core.sparseCheckout */ + GIT_CONFIGMAP_SPARSECHECKOUTCONE, /* core.sparseCheckoutCone */ GIT_CONFIGMAP_CACHE_MAX } git_configmap_item; @@ -123,7 +125,11 @@ typedef enum { /* core.fsyncObjectFiles */ GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE, /* core.longpaths */ - GIT_LONGPATHS_DEFAULT = GIT_CONFIGMAP_FALSE + GIT_LONGPATHS_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.sparsecheckout */ + GIT_SPARSECHECKOUT_DEFAULT = GIT_CONFIGMAP_FALSE, + /* core.sparsecheckoutcone */ + GIT_SPARSECHECKOUTCONE_DEFAULT = GIT_CONFIGMAP_TRUE } git_configmap_value; /* internal repository init flags */ diff --git a/src/libgit2/sparse_checkout.c b/src/libgit2/sparse_checkout.c new file mode 100644 index 00000000000..1aa3841643b --- /dev/null +++ b/src/libgit2/sparse_checkout.c @@ -0,0 +1,459 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "sparse_checkout.h" +#include "hashmap_str.h" +#include "parse.h" + +typedef struct { + char *path; +} git_sparse_checkout_map_entry; + +GIT_HASHMAP_STR_SETUP( + git_sparse_checkout_parentmap, + git_sparse_checkout_map_entry *); + +GIT_HASHMAP_STR_SETUP( + git_sparse_checkout_recursivemap, + git_sparse_checkout_map_entry *); + +typedef struct { + git_repository *repo; + int ignore_case; + + int full_cone; + git_sparse_checkout_parentmap parentmap; + git_sparse_checkout_recursivemap recursivemap; +} git_sparse_checkout; + +static int git_sparse_checkout__add_parentmap_entry( + git_sparse_checkout *sparse_checkout, + const char *key) +{ + int error = 0; + git_sparse_checkout_map_entry *entry; + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + entry->path = git__strdup(key); + + git_sparse_checkout_parentmap_put( + &sparse_checkout->parentmap, entry->path, entry); + + printf("Added to parent map: %s\n", key); + + return error; +} + +static int git_sparse_checkout__remove_parentmap_entry( + git_sparse_checkout *sparse_checkout, + const char *key) +{ + int error = 0; + git_sparse_checkout_map_entry *entry; + + if (git_sparse_checkout_parentmap_get( + &entry, &sparse_checkout->parentmap, key) != 0) + return GIT_ENOTFOUND; + + if ((error = git_sparse_checkout_parentmap_remove( + &sparse_checkout->parentmap, key)) < 0) + return error; + + printf("Remove from parent map: %s\n", key); + + git__free(entry->path); + git__free(entry); + return error; +} + +static int git_sparse_checkout__add_recursivemap_entry( + git_sparse_checkout *sparse_checkout, + const char *key) +{ + int error = 0; + git_sparse_checkout_map_entry *entry; + + entry = git__calloc(1, sizeof(*entry)); + GIT_ERROR_CHECK_ALLOC(entry); + entry->path = git__strdup(key); + + git_sparse_checkout_recursivemap_put( + &sparse_checkout->recursivemap, entry->path, entry); + + printf("Added to recursive map: %s\n", key); + + return error; +} + +static int git_sparse_checkout__remove_recursivemap_entry( + git_sparse_checkout *sparse_checkout, + const char *key) +{ + int error = 0; + git_sparse_checkout_map_entry *entry; + + if (git_sparse_checkout_recursivemap_get( + &entry, &sparse_checkout->recursivemap, key) != 0) + return GIT_ENOTFOUND; + + if ((error = git_sparse_checkout_recursivemap_remove( + &sparse_checkout->recursivemap, key)) < 0) + return error; + + printf("Remove from recursive map: %s\n", key); + + git__free(entry->path); + git__free(entry); + return error; +} + +static int git_sparse_checkout__init( + git_sparse_checkout *sparse_checkout, + git_repository *repo) +{ + int error = 0; + + memset(sparse_checkout, 0, sizeof(*sparse_checkout)); + + sparse_checkout->repo = repo; + + /* Need to check ignore case */ + error = git_repository__configmap_lookup( + &sparse_checkout->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE); + + return error; +} + +static void git_sparse_checkout__dispose(git_sparse_checkout *sparse_checkout) +{ + git_sparse_checkout_map_entry *e; + git_hashmap_iter_t iter = GIT_HASHMAP_ITER_INIT; + + while (git_sparse_checkout_parentmap_iterate( + &iter, NULL, &e, &sparse_checkout->parentmap) == 0) + git__free(e); + git_sparse_checkout_parentmap_dispose(&sparse_checkout->parentmap); + + while (git_sparse_checkout_recursivemap_iterate( + &iter, NULL, &e, &sparse_checkout->recursivemap) == 0) + git__free(e); + git_sparse_checkout_recursivemap_dispose( + &sparse_checkout->recursivemap); +} + +static int parse_sparse_checkout_buffer( + git_sparse_checkout *sparse_checkout, + const char *buf, + size_t len) +{ + int error = 0; + const char *scan = buf; + git_pool pool = GIT_POOL_INIT; + git_attr_fnmatch *match = NULL; + git_str truncated_match = GIT_STR_INIT; + + if (git_pool_init(&pool, 1) < 0) + goto cleanup; + + // printf("Buffer:\n%.*s\n", (int)len, buf); + + while (*scan) { + match = git__calloc(1, sizeof(git_attr_fnmatch)); + if (!match) + return -1; + + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | + GIT_ATTR_FNMATCH_ALLOWNEG; + + /* + * context not needed as it's always the repository root + */ + if ((error = git_attr_fnmatch__parse( + match, &pool, NULL, &scan)) < 0) { + if (error != GIT_ENOTFOUND) { + goto cleanup; + } + error = 0; + continue; + } + + scan = git__next_line(scan); + + printf("%.*s ", (int)match->length, match->pattern); + printf("Negated = %d, ", + (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0); + printf("Directory = %d, ", + (match->flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0); + printf("Full Path = %d, ", + (match->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0); + printf("Has Wild = %d\n", + (match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0); + + if (git__strncmp("*", match->pattern, match->length) == 0 && + (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0 && + (match->flags & GIT_ATTR_FNMATCH_DIRECTORY) == 0) { + sparse_checkout->full_cone = 1; + git__free(match); + match = NULL; + continue; + } + + if (git__strncmp("*", match->pattern, match->length) == 0 && + (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && + (match->flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0) { + sparse_checkout->full_cone = 0; + git__free(match); + match = NULL; + continue; + } + + if (((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) == 0 || + (match->flags & GIT_ATTR_FNMATCH_FULLPATH) == 0) && + git__strncmp("*", match->pattern, match->length) != 0) { + error = GIT_EINVALID; + git_error_set( + GIT_ERROR_INVALID, + "Invalid cone-mode pattern: %.*s", + (int)match->length, match->pattern); + goto cleanup; + } + + /* Negative matches must come after the identical non-negated + * match. It must exist in the recursive hash map. Suffix must + * equal "\*". + */ + if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0) { + if (match->length < 3 && + git__suffixcmp(match->pattern, "/*") != 0) { + error = GIT_EINVALID; + git_error_set( + GIT_ERROR_INVALID, + "Invalid cone-mode pattern: %.*s", + (int)match->length, match->pattern); + goto cleanup; + } + + git_str_sets(&truncated_match, match->pattern); + git_str_shorten(&truncated_match, 2); + + // If in recursive hash map, remove and add to parent + // hash map. + + if (!git_sparse_checkout_recursivemap_contains( + &sparse_checkout->recursivemap, + truncated_match.ptr)) { + error = GIT_EINVALID; + git_error_set( + GIT_ERROR_INVALID, + "Invalid cone-mode pattern: %.*s", + (int)match->length, match->pattern); + goto cleanup; + } + + git_sparse_checkout__remove_recursivemap_entry( + sparse_checkout, truncated_match.ptr); + git_sparse_checkout__add_parentmap_entry( + sparse_checkout, truncated_match.ptr); + } else { + // Add to recursive hash map + git_sparse_checkout__add_recursivemap_entry( + sparse_checkout, match->pattern); + } + + git__free(match); + match = NULL; + } + +cleanup: + git_str_dispose(&truncated_match); + if (match) + git__free(match); + return error; +} + +static int read_sparse_checkout_file( + git_sparse_checkout *sparse_checkout, + git_repository *repo) +{ + int error = 0; + git_str contents = GIT_STR_INIT; + git_str info_path = GIT_STR_INIT; + git_str sparse_checkout_file_path = GIT_STR_INIT; + + if ((error = git_repository__item_path( + &info_path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || + (error = git_str_joinpath( + &sparse_checkout_file_path, info_path.ptr, + GIT_SPARSE_CHECKOUT_FILE)) < 0 || + (error = git_futils_readbuffer( + &contents, sparse_checkout_file_path.ptr)) < 0 || + (error = parse_sparse_checkout_buffer( + sparse_checkout, contents.ptr, contents.size)) < 0) { + goto cleanup; + } + +cleanup: + git_str_dispose(&contents); + git_str_dispose(&info_path); + git_str_dispose(&sparse_checkout_file_path); + return error; +} + +static int make_dummy_file_from_directory(git_str *path) +{ + if (path->ptr[path->size - 1] == '/') { + return git_str_putc(path, '-'); + } + return 0; +} + +static int path_is_tracked( + int *is_tracked, + git_sparse_checkout *sparse_checkout, + const char *pathname) +{ + int error = 0; + *is_tracked = 0; + git_str path = GIT_STR_INIT; + git_str dirname = GIT_STR_INIT; + + if (sparse_checkout->full_cone) { + *is_tracked = 1; + goto cleanup; + } + + // pathname could be directory or file + // If directory, append a random filename to the folder + // This is what Git does... + if ((error = git_str_sets(&path, pathname)) < 0 || + (error = make_dummy_file_from_directory(&path)) < 0 || + (error = git_fs_path_dirname_r(&dirname, path.ptr)) < 0) { + goto cleanup; + } + error = 0; + printf("dirname: %s\n", dirname.ptr); + + if (dirname.size == 1 && dirname.ptr[0] == '.') { + *is_tracked = 1; + goto cleanup; + } + + if (git_sparse_checkout_recursivemap_contains( + &sparse_checkout->recursivemap, path.ptr) || + git_sparse_checkout_parentmap_contains( + &sparse_checkout->parentmap, dirname.ptr)) { + *is_tracked = 1; + goto cleanup; + } + + while (dirname.size != 1 && dirname.ptr[0] != '.') { + if (git_sparse_checkout_recursivemap_contains( + &sparse_checkout->recursivemap, dirname.ptr)) { + *is_tracked = 1; + goto cleanup; + } + + if ((error = git_fs_path_dirname_r(&dirname, dirname.ptr)) < + 0) { + goto cleanup; + } + error = 0; + printf("dirname: %s\n", dirname.ptr); + } + +cleanup: + git_str_dispose(&dirname); + git_str_dispose(&path); + return error; +} + +int git_sparse_checkout__path_is_tracked( + int *is_tracked, + git_repository *repo, + const char *pathname) +{ + /* + * git: init_sparse_checkout_patterns -> get_sparse_checkout_patterns + * update_working_directory(struct pattern_list *pl) + */ + int error = 0; + int sparse_checkout_enabled = 0; + int sparse_checkout_cone_enabled = 0; + int ignore_case = 0; + git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; + git_sparse_checkout sparse_checkout; + + GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(is_tracked); + GIT_ASSERT_ARG(pathname); + + if ((error = git_repository__configmap_lookup( + &sparse_checkout_enabled, repo, + GIT_CONFIGMAP_SPARSECHECKOUT)) < 0 || + !sparse_checkout_enabled) { + *is_tracked = 1; + return error; + } + + if ((error = git_repository__configmap_lookup( + &sparse_checkout_cone_enabled, repo, + GIT_CONFIGMAP_SPARSECHECKOUTCONE)) < 0) { + *is_tracked = 1; + return error; + } + + if (!sparse_checkout_cone_enabled) { + /* + * We only support cone mode as non-cone mode is deprecated in + * Git. Should we error? What's the plan for Git? + */ + *is_tracked = 1; + git_error_set( + GIT_ERROR_CONFIG, + "non-cone mode for sparse checkout is not supported"); + error = GIT_ENOTSUPPORTED; + return error; + } + + /* + * https://git-scm.com/docs/git-sparse-checkout/2.42.0 + * Unless core.sparseCheckoutCone is explicitly set to false, Git will + * parse the sparse-checkout file expecting patterns of these types. Git + * will warn if the patterns do not match. If the patterns do match the + * expected format, then Git will use faster hash-based algorithms to + * compute inclusion in the sparse-checkout. If they do not match, git + * will behave as though core.sparseCheckoutCone was false, regardless + * of its setting. + */ + + /* In cone mode, .git/info/sparse-checkout file specifies directories to + * include. + * Parent pattern (include all files under "dir" but nothing below + * that): /dir/ + * !/dir/*\/ + * Recursive pattern (include everything in this folder): + * /dir/subdir/ + */ + + /* open and parse the file for the patterns, populating the parent and + * recursive hash maps */ + /* use attr_cache so gets cached on repository? Only real benefit + * appears to be reparsing when the file is updated. */ + + printf("\nCheck path is tracked: %s\n", pathname); + + if ((error = git_sparse_checkout__init(&sparse_checkout, repo)) < 0 || + (error = read_sparse_checkout_file(&sparse_checkout, repo)) < 0 || + (error = path_is_tracked(is_tracked, &sparse_checkout, pathname)) < + 0) + goto cleanup; + +cleanup: + git_sparse_checkout__dispose(&sparse_checkout); + return error; +} diff --git a/src/libgit2/sparse_checkout.h b/src/libgit2/sparse_checkout.h new file mode 100644 index 00000000000..924d04d0d46 --- /dev/null +++ b/src/libgit2/sparse_checkout.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sparse_checkout_h__ +#define INCLUDE_sparse_checkout_h__ + +#include "common.h" + +#include "repository.h" + +#define GIT_SPARSE_CHECKOUT_FILE "sparse-checkout" + +extern int git_sparse_checkout__path_is_tracked( + int *is_tracked, + git_repository *repo, + const char *pathname); + +#endif diff --git a/tests/libgit2/sparse/config.c b/tests/libgit2/sparse/config.c new file mode 100644 index 00000000000..49ee798d6f7 --- /dev/null +++ b/tests/libgit2/sparse/config.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "repository.h" + +static git_repository *g_repo = NULL; + +void test_sparse_config__initialize(void) +{ + g_repo = cl_git_sandbox_init("sparse"); +} + +void test_sparse_config__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_sparse_config__cone(void) +{ + git_config *config, *worktree; + int sparse_checkout = 0; + int sparse_checkout_cone = 0; + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_open_level( + &worktree, config, GIT_CONFIG_LEVEL_WORKTREE)); + + cl_git_pass(git_config_get_bool( + &sparse_checkout, worktree, "core.sparseCheckout")); + cl_assert_equal_b(true, sparse_checkout); + cl_git_pass(git_config_get_bool( + &sparse_checkout_cone, worktree, "core.sparseCheckoutCone")); + cl_assert_equal_b(true, sparse_checkout_cone); + + sparse_checkout = 0; + cl_git_pass(git_repository__configmap_lookup( + &sparse_checkout, g_repo, + GIT_CONFIGMAP_SPARSECHECKOUT)); + cl_assert_equal_b(true, sparse_checkout); + sparse_checkout_cone = 0; + cl_git_pass(git_repository__configmap_lookup( + &sparse_checkout_cone, g_repo, + GIT_CONFIGMAP_SPARSECHECKOUTCONE)); + cl_assert_equal_b(true, sparse_checkout_cone); + + git_config_free(worktree); + git_config_free(config); +} diff --git a/tests/libgit2/sparse/path.c b/tests/libgit2/sparse/path.c new file mode 100644 index 00000000000..544a3c2cf3a --- /dev/null +++ b/tests/libgit2/sparse/path.c @@ -0,0 +1,88 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "sparse_checkout.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +void test_sparse_path__initialize(void) +{ + g_repo = cl_git_sandbox_init("sparse"); +} + +void test_sparse_path__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void assert_is_tracked_( + bool expected, + const char *filepath, + const char *file, + const char *func, + int line) +{ + int is_tracked = 0; + + cl_git_expect( + git_sparse_checkout__path_is_tracked( + &is_tracked, g_repo, filepath), + 0, file, func, line); + + clar__assert_equal( + file, func, line, "expected != is_tracked", 1, "%d", + (int)(expected != 0), (int)(is_tracked != 0)); +} + +#define assert_is_tracked(expected, filepath) \ + assert_is_tracked_(expected, filepath, __FILE__, __func__, __LINE__) + +void test_sparse_path__basic(void) +{ + assert_is_tracked(true, "fileA.txt"); + assert_is_tracked(true, "fileB.txt"); + assert_is_tracked(true, "c"); + assert_is_tracked(true, "c/fileA.txt"); + assert_is_tracked(true, "c/d"); + assert_is_tracked(true, "c/d/fileA.txt"); + assert_is_tracked(true, "c/d/fileB.txt"); + assert_is_tracked(true, "c/d/e/fileB.txt"); + assert_is_tracked(false, "b/fileA.txt"); + assert_is_tracked(false, "b/d/fileA.txt"); + + assert_is_tracked(true, "c/"); + assert_is_tracked(true, "c/d/"); + assert_is_tracked(true, "c/d/e/"); + assert_is_tracked(true, "c/d/e/f/"); + assert_is_tracked(true, "c/d/e/f/g/"); + + assert_is_tracked(true, "b"); + assert_is_tracked(false, "b/"); + + assert_is_tracked(true, "doesnotexist.txt"); + assert_is_tracked(false, "does/not/exist.txt"); + + assert_is_tracked(false, "/"); +} + +void test_sparse_path__error_not_directory(void) +{ + int is_tracked = 0; + + cl_git_rewritefile( + "sparse/.git/info/sparse-checkout", "/a/b/c"); + cl_git_fail_with( + GIT_EINVALID, git_sparse_checkout__path_is_tracked( + &is_tracked, g_repo, "fileA.txt")); +} + +void test_sparse_path__error_space(void) +{ + int is_tracked = 0; + + cl_git_rewritefile("sparse/.git/info/sparse-checkout", " #comment"); + cl_git_fail_with( + GIT_EINVALID, git_sparse_checkout__path_is_tracked( + &is_tracked, g_repo, "fileA.txt")); +} diff --git a/tests/resources/sparse/.gitted/HEAD b/tests/resources/sparse/.gitted/HEAD new file mode 100644 index 00000000000..b870d82622c --- /dev/null +++ b/tests/resources/sparse/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/resources/sparse/.gitted/config b/tests/resources/sparse/.gitted/config new file mode 100644 index 00000000000..2da1759d9bd --- /dev/null +++ b/tests/resources/sparse/.gitted/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true +[extensions] + worktreeConfig = true diff --git a/tests/resources/sparse/.gitted/config.worktree b/tests/resources/sparse/.gitted/config.worktree new file mode 100644 index 00000000000..703fe4cb62b --- /dev/null +++ b/tests/resources/sparse/.gitted/config.worktree @@ -0,0 +1,5 @@ +[core] + sparseCheckout = true + sparseCheckoutCone = true +[index] + sparse = false diff --git a/tests/resources/sparse/.gitted/description b/tests/resources/sparse/.gitted/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests/resources/sparse/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/resources/sparse/.gitted/index b/tests/resources/sparse/.gitted/index new file mode 100644 index 0000000000000000000000000000000000000000..2a81b4c394b0bc8ad311b95a161c902d65cf6c69 GIT binary patch literal 899 zcmZ?q402{*U|LsGpXZlj^8fQc(g`3^NBtBg{ZGuP=VXWbYECm)tL=ZVoNz?=btc+7V`+6RLT0 z_&+iW0L`1j|6_{@4)Y}1p1wVnsGKHLr)O0n{2(%RBZJ9i2VMt;B>fZ=H^R(;(KyT# zsjZxE>pJPkfj319m!G#Q6qI&YlI6e+G7r_qV1MRX{(#Xq%#(;S3ic4LvCy5YoByO& zILLxa@bC==-efe_F)+Z)fzi0k3u%$LU9dm$yt%q@`O;et4pxVqb4Qqm2tk-RFdBz> zB7wkl~06L_x|Ub*)5 z^`=ScSKaG{_Gn3&u6Uq)MRA7njbsL6pdOhjU%llO%cs1)f2Ym03YlRlmGw# literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/1c/7a4d2ea77803076deb800b6ab85191dceb4b56 b/tests/resources/sparse/.gitted/objects/1c/7a4d2ea77803076deb800b6ab85191dceb4b56 new file mode 100644 index 0000000000000000000000000000000000000000..1b2ffd94995dec42ba705061c5afe110f7d5942b GIT binary patch literal 82 zcmV-Y0ImOc0V^p=O;s>6WiT-S0)-TYzO08I690HdJiM`}D1JM8n+?zNPlg5tW+o;I oX_+~xj(Q~(B@BVdM`jx2wniqOSe)CiddKGr=hm1401A5^=*7JtIRF3v literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/33/c60f7155d1a2390854133026dc5f67ef8c77e0 b/tests/resources/sparse/.gitted/objects/33/c60f7155d1a2390854133026dc5f67ef8c77e0 new file mode 100644 index 00000000000..6d2709b7af5 --- /dev/null +++ b/tests/resources/sparse/.gitted/objects/33/c60f7155d1a2390854133026dc5f67ef8c77e0 @@ -0,0 +1,2 @@ +xA +0a9$ "A1 i MV/'9E*R[޺08Fk% /D)'8I^}I\w.ü΀z])ꟌO}y34C3 \ No newline at end of file diff --git a/tests/resources/sparse/.gitted/objects/51/63c499306d855963c8a36d80abb8f3d0ceac35 b/tests/resources/sparse/.gitted/objects/51/63c499306d855963c8a36d80abb8f3d0ceac35 new file mode 100644 index 0000000000000000000000000000000000000000..be2b3665655538331fb7ef44648a7f14cf4255fe GIT binary patch literal 36 scmbcs1H3!>D03H?&!vFvP literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/54/841cdb70bf59cf37273377a5dae0c17b56ce47 b/tests/resources/sparse/.gitted/objects/54/841cdb70bf59cf37273377a5dae0c17b56ce47 new file mode 100644 index 0000000000000000000000000000000000000000..ecffbf26eef9ce0f203181fd128a3021d7dca234 GIT binary patch literal 38 ucmb7wO}wY00Mr$XkcJwVxo|i znUm_MS5i^J@Ln&xa_#HuO_S8Gy4U+$H@|ZzOcP0w6Ijv1GutQKj7yyH+40TGuJWm` N4xf_F2LSOiMGB9)QS$%* literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/78/fadb360d2ed95db233cc5aa17ad40d86ecbaf9 b/tests/resources/sparse/.gitted/objects/78/fadb360d2ed95db233cc5aa17ad40d86ecbaf9 new file mode 100644 index 0000000000000000000000000000000000000000..c9b8d8270cea87783f2f38e4ae041f5fb97e6f6f GIT binary patch literal 81 zcmV-X0IvUd0V^p=O;s>6WiT-S0)-TYY;OxmiP|>{_Gn3&u6Uq)MRA7n4MPJ1GZPbq nw9K4TN4=7Y5{BB!`L?c;jvRPXv~c-(yFx)}hb376fFT;~z~>;? literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/7d/799f3d4592c4c0ec72a1a7cf3e71111b40a46a b/tests/resources/sparse/.gitted/objects/7d/799f3d4592c4c0ec72a1a7cf3e71111b40a46a new file mode 100644 index 0000000000000000000000000000000000000000..3c75a7f81b9e0ab070e0a3bc7e097ecdbe7badf7 GIT binary patch literal 36 scmbh>Koe%03T})p8x;= literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/86/e5edc6612366127e2e3a7413e0595db10034b3 b/tests/resources/sparse/.gitted/objects/86/e5edc6612366127e2e3a7413e0595db10034b3 new file mode 100644 index 0000000000000000000000000000000000000000..e5ba127bb0c36be051026ce8527e54d996b0c116 GIT binary patch literal 38 ucmbh>J{4x03W9hlK=n! literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/8e/6ae1f061fc4b58e1d8a2725fb707863c0ce7f2 b/tests/resources/sparse/.gitted/objects/8e/6ae1f061fc4b58e1d8a2725fb707863c0ce7f2 new file mode 100644 index 0000000000000000000000000000000000000000..49a028733707d0213b7cb38fae8c290fda6b96d9 GIT binary patch literal 79 zcmV-V0I>gf0V^p=O;s>AVK6i>Ff%bxNXyJgb<``VC}D^*3ic4LvCy5YoByO&ILLxa l@bC>JMNVKvAuTeu3-(8zH&-_AVK6i>Ff%bxNXyJgb<``VC}CjA{CPO}RN||D4GWtaTkBJz mrTE;D6ghzv^~G

|LVtlKaKf&7lST9cG_aI|2Z^8XwY1%Os!x literal 0 HcmV?d00001 diff --git a/tests/resources/sparse/.gitted/objects/e1/ccb792d95e6198f341ece98a7795eac3ca1b6f b/tests/resources/sparse/.gitted/objects/e1/ccb792d95e6198f341ece98a7795eac3ca1b6f new file mode 100644 index 0000000000000000000000000000000000000000..e9b8f419e5aeb580a2b0b7ac011b97fc882a05ed GIT binary patch literal 32 ocmb}F(0WMz{D0OGF