Add virtual file system settings and hook proc

On index load, clear/set the skip worktree bits based on the virtual
file system data. Use virtual file system data to update skip-worktree
bit in unpack-trees. Use virtual file system data to exclude files and
folders not explicitly requested.

Update 2022-04-05: disable the "present-despite-SKIP_WORKTREE" file removal
behavior when 'core.virtualfilesystem' is enabled.

Signed-off-by: Ben Peart <benpeart@microsoft.com>
This commit is contained in:
Ben Peart 2018-01-11 16:25:08 -05:00 коммит произвёл Johannes Schindelin
Родитель 58bc0487a6
Коммит 8352584b85
16 изменённых файлов: 794 добавлений и 6 удалений

Просмотреть файл

@ -111,6 +111,14 @@ Version 2 uses an opaque string so that the monitor can return
something that can be used to determine what files have changed something that can be used to determine what files have changed
without race conditions. without race conditions.
core.virtualFilesystem::
If set, the value of this variable is used as a command which
will identify all files and directories that are present in
the working directory. Git will only track and update files
listed in the virtual file system. Using the virtual file system
will supersede the sparse-checkout settings which will be ignored.
See the "virtual file system" section of linkgit:githooks[5].
core.trustctime:: core.trustctime::
If false, the ctime differences between the index and the If false, the ctime differences between the index and the
working tree are ignored; useful when the inode change time working tree are ignored; useful when the inode change time

Просмотреть файл

@ -710,6 +710,26 @@ and "0" meaning they were not.
Only one parameter should be set to "1" when the hook runs. The hook Only one parameter should be set to "1" when the hook runs. The hook
running passing "1", "1" should not be possible. running passing "1", "1" should not be possible.
virtualFilesystem
~~~~~~~~~~~~~~~~~~
"Virtual File System" allows populating the working directory sparsely.
The projection data is typically automatically generated by an external
process. Git will limit what files it checks for changes as well as which
directories are checked for untracked files based on the path names given.
Git will also only update those files listed in the projection.
The hook is invoked when the configuration option core.virtualFilesystem
is set. It takes one argument, a version (currently 1).
The hook should output to stdout the list of all files in the working
directory that git should track. The paths are relative to the root
of the working directory and are separated by a single NUL. Full paths
('dir1/a.txt') as well as directories are supported (ie 'dir1/').
The exit status determines whether git will use the data from the
hook. On error, git will abort the command with an error message.
SEE ALSO SEE ALSO
-------- --------
linkgit:git-hook[1] linkgit:git-hook[1]

Просмотреть файл

@ -1190,6 +1190,7 @@ LIB_OBJS += utf8.o
LIB_OBJS += varint.o LIB_OBJS += varint.o
LIB_OBJS += version.o LIB_OBJS += version.o
LIB_OBJS += versioncmp.o LIB_OBJS += versioncmp.o
LIB_OBJS += virtualfilesystem.o
LIB_OBJS += walker.o LIB_OBJS += walker.o
LIB_OBJS += wildmatch.o LIB_OBJS += wildmatch.o
LIB_OBJS += worktree.o LIB_OBJS += worktree.o

Просмотреть файл

@ -1054,6 +1054,7 @@ enum fsync_method {
extern enum fsync_method fsync_method; extern enum fsync_method fsync_method;
extern int core_preload_index; extern int core_preload_index;
extern const char *core_virtualfilesystem;
extern int core_gvfs; extern int core_gvfs;
extern int precomposed_unicode; extern int precomposed_unicode;
extern int protect_hfs; extern int protect_hfs;

Просмотреть файл

@ -1750,7 +1750,11 @@ int git_default_core_config(const char *var, const char *value, void *cb)
} }
if (!strcmp(var, "core.sparsecheckout")) { if (!strcmp(var, "core.sparsecheckout")) {
core_apply_sparse_checkout = git_config_bool(var, value); /* virtual file system relies on the sparse checkout logic so force it on */
if (core_virtualfilesystem)
core_apply_sparse_checkout = 1;
else
core_apply_sparse_checkout = git_config_bool(var, value);
return 0; return 0;
} }
@ -2800,6 +2804,30 @@ int git_config_get_max_percent_split_change(void)
return -1; /* default value */ return -1; /* default value */
} }
int git_config_get_virtualfilesystem(void)
{
/* Run only once. */
static int virtual_filesystem_result = -1;
if (virtual_filesystem_result >= 0)
return virtual_filesystem_result;
if (git_config_get_pathname("core.virtualfilesystem", &core_virtualfilesystem))
core_virtualfilesystem = getenv("GIT_VIRTUALFILESYSTEM_TEST");
if (core_virtualfilesystem && !*core_virtualfilesystem)
core_virtualfilesystem = NULL;
/* virtual file system relies on the sparse checkout logic so force it on */
if (core_virtualfilesystem) {
core_apply_sparse_checkout = 1;
virtual_filesystem_result = 1;
return 1;
}
virtual_filesystem_result = 0;
return 0;
}
int git_config_get_index_threads(int *dest) int git_config_get_index_threads(int *dest)
{ {
int is_bool, val; int is_bool, val;

Просмотреть файл

@ -606,6 +606,7 @@ int git_config_get_pathname(const char *key, const char **dest);
int git_config_get_index_threads(int *dest); int git_config_get_index_threads(int *dest);
int git_config_get_split_index(void); int git_config_get_split_index(void);
int git_config_get_max_percent_split_change(void); int git_config_get_max_percent_split_change(void);
int git_config_get_virtualfilesystem(void);
/* This dies if the configured or default date is in the future */ /* This dies if the configured or default date is in the future */
int git_config_get_expiry(const char *key, const char **output); int git_config_get_expiry(const char *key, const char **output);

32
dir.c
Просмотреть файл

@ -6,6 +6,7 @@
* Junio Hamano, 2005-2006 * Junio Hamano, 2005-2006
*/ */
#include "cache.h" #include "cache.h"
#include "virtualfilesystem.h"
#include "config.h" #include "config.h"
#include "dir.h" #include "dir.h"
#include "object-store.h" #include "object-store.h"
@ -1419,6 +1420,17 @@ enum pattern_match_result path_matches_pattern_list(
int result = NOT_MATCHED; int result = NOT_MATCHED;
size_t slash_pos; size_t slash_pos;
/*
* The virtual file system data is used to prevent git from traversing
* any part of the tree that is not in the virtual file system. Return
* 1 to exclude the entry if it is not found in the virtual file system,
* else fall through to the regular excludes logic as it may further exclude.
*/
if (*dtype == DT_UNKNOWN)
*dtype = resolve_dtype(DT_UNKNOWN, istate, pathname, pathlen);
if (is_excluded_from_virtualfilesystem(pathname, pathlen, *dtype) > 0)
return 1;
if (!pl->use_cone_patterns) { if (!pl->use_cone_patterns) {
pattern = last_matching_pattern_from_list(pathname, pathlen, basename, pattern = last_matching_pattern_from_list(pathname, pathlen, basename,
dtype, pl, istate); dtype, pl, istate);
@ -1762,8 +1774,20 @@ struct path_pattern *last_matching_pattern(struct dir_struct *dir,
int is_excluded(struct dir_struct *dir, struct index_state *istate, int is_excluded(struct dir_struct *dir, struct index_state *istate,
const char *pathname, int *dtype_p) const char *pathname, int *dtype_p)
{ {
struct path_pattern *pattern = struct path_pattern *pattern;
last_matching_pattern(dir, istate, pathname, dtype_p);
/*
* The virtual file system data is used to prevent git from traversing
* any part of the tree that is not in the virtual file system. Return
* 1 to exclude the entry if it is not found in the virtual file system,
* else fall through to the regular excludes logic as it may further exclude.
*/
if (*dtype_p == DT_UNKNOWN)
*dtype_p = resolve_dtype(DT_UNKNOWN, istate, pathname, strlen(pathname));
if (is_excluded_from_virtualfilesystem(pathname, strlen(pathname), *dtype_p) > 0)
return 1;
pattern = last_matching_pattern(dir, istate, pathname, dtype_p);
if (pattern) if (pattern)
return pattern->flags & PATTERN_FLAG_NEGATIVE ? 0 : 1; return pattern->flags & PATTERN_FLAG_NEGATIVE ? 0 : 1;
return 0; return 0;
@ -2349,6 +2373,8 @@ static enum path_treatment treat_path(struct dir_struct *dir,
ignore_case); ignore_case);
if (dtype != DT_DIR && has_path_in_index) if (dtype != DT_DIR && has_path_in_index)
return path_none; return path_none;
if (is_excluded_from_virtualfilesystem(path->buf, path->len, dtype) > 0)
return path_excluded;
/* /*
* When we are looking at a directory P in the working tree, * When we are looking at a directory P in the working tree,
@ -2553,6 +2579,8 @@ static void add_path_to_appropriate_result_list(struct dir_struct *dir,
/* add the path to the appropriate result list */ /* add the path to the appropriate result list */
switch (state) { switch (state) {
case path_excluded: case path_excluded:
if (is_excluded_from_virtualfilesystem(path->buf, path->len, DT_DIR) > 0)
break;
if (dir->flags & DIR_SHOW_IGNORED) if (dir->flags & DIR_SHOW_IGNORED)
dir_add_name(dir, istate, path->buf, path->len); dir_add_name(dir, istate, path->buf, path->len);
else if ((dir->flags & DIR_SHOW_IGNORED_TOO) || else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||

Просмотреть файл

@ -73,6 +73,7 @@ int core_apply_sparse_checkout;
int core_sparse_checkout_cone; int core_sparse_checkout_cone;
int sparse_expect_files_outside_of_patterns; int sparse_expect_files_outside_of_patterns;
int core_gvfs; int core_gvfs;
const char *core_virtualfilesystem;
int merge_log_config = -1; int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
unsigned long pack_size_limit_cfg; unsigned long pack_size_limit_cfg;

Просмотреть файл

@ -4,6 +4,7 @@
* Copyright (C) Linus Torvalds, 2005 * Copyright (C) Linus Torvalds, 2005
*/ */
#include "cache.h" #include "cache.h"
#include "virtualfilesystem.h"
#include "config.h" #include "config.h"
#include "diff.h" #include "diff.h"
#include "diffcore.h" #include "diffcore.h"
@ -2078,6 +2079,7 @@ static void post_read_index_from(struct index_state *istate)
tweak_untracked_cache(istate); tweak_untracked_cache(istate);
tweak_split_index(istate); tweak_split_index(istate);
tweak_fsmonitor(istate); tweak_fsmonitor(istate);
apply_virtualfilesystem(istate);
} }
static size_t estimate_cache_size_from_compressed(unsigned int entries) static size_t estimate_cache_size_from_compressed(unsigned int entries)

Просмотреть файл

@ -493,6 +493,7 @@ void clear_skip_worktree_from_present_files(struct index_state *istate)
int restarted = 0; int restarted = 0;
if (!core_apply_sparse_checkout || if (!core_apply_sparse_checkout ||
core_virtualfilesystem ||
sparse_expect_files_outside_of_patterns) sparse_expect_files_outside_of_patterns)
return; return;

Просмотреть файл

@ -107,9 +107,9 @@ test_expect_success 'in partial clone, sparse checkout only fetches needed blobs
' '
test_expect_success 'checkout does not delete items outside the sparse checkout file' ' test_expect_success 'checkout does not delete items outside the sparse checkout file' '
# The "sparse.expectfilesoutsideofpatterns" config will prevent the # The "core.virtualfilesystem" config will prevent the
# SKIP_WORKTREE flag from being dropped on files present on-disk. # SKIP_WORKTREE flag from being dropped on files present on-disk.
test_config sparse.expectfilesoutsideofpatterns true && test_config core.virtualfilesystem true &&
test_config core.gvfs 8 && test_config core.gvfs 8 &&
git checkout -b outside && git checkout -b outside &&

350
t/t1093-virtualfilesystem.sh Executable file
Просмотреть файл

@ -0,0 +1,350 @@
#!/bin/sh
test_description='virtual file system tests'
. ./test-lib.sh
clean_repo () {
rm .git/index &&
git -c core.virtualfilesystem= reset --hard HEAD &&
git -c core.virtualfilesystem= clean -fd &&
touch untracked.txt &&
touch dir1/untracked.txt &&
touch dir2/untracked.txt
}
test_expect_success 'setup' '
git branch -M main &&
mkdir -p .git/hooks/ &&
cat > .gitignore <<-\EOF &&
.gitignore
expect*
actual*
EOF
mkdir -p dir1 &&
touch dir1/file1.txt &&
touch dir1/file2.txt &&
mkdir -p dir2 &&
touch dir2/file1.txt &&
touch dir2/file2.txt &&
git add . &&
git commit -m "initial" &&
git config --local core.virtualfilesystem .git/hooks/virtualfilesystem
'
test_expect_success 'test hook parameters and version' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
if test "$#" -ne 1
then
echo "$0: Exactly 1 argument expected" >&2
exit 2
fi
if test "$1" != 1
then
echo "$0: Unsupported hook version." >&2
exit 1
fi
EOF
git status &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
exit 3
EOF
test_must_fail git status
'
test_expect_success 'verify status is clean' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir2/file1.txt\0"
EOF
rm -f .git/index &&
git checkout -f &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir2/file1.txt\0"
printf "dir1/file1.txt\0"
printf "dir1/file2.txt\0"
EOF
git status > actual &&
cat > expected <<-\EOF &&
On branch main
nothing to commit, working tree clean
EOF
test_cmp expected actual
'
test_expect_success 'verify skip-worktree bit is set for absolute path' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file1.txt\0"
EOF
git ls-files -v > actual &&
cat > expected <<-\EOF &&
H dir1/file1.txt
S dir1/file2.txt
S dir2/file1.txt
S dir2/file2.txt
EOF
test_cmp expected actual
'
test_expect_success 'verify skip-worktree bit is cleared for absolute path' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file2.txt\0"
EOF
git ls-files -v > actual &&
cat > expected <<-\EOF &&
S dir1/file1.txt
H dir1/file2.txt
S dir2/file1.txt
S dir2/file2.txt
EOF
test_cmp expected actual
'
test_expect_success 'verify folder wild cards' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/\0"
EOF
git ls-files -v > actual &&
cat > expected <<-\EOF &&
H dir1/file1.txt
H dir1/file2.txt
S dir2/file1.txt
S dir2/file2.txt
EOF
test_cmp expected actual
'
test_expect_success 'verify folders not included are ignored' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file1.txt\0"
printf "dir1/file2.txt\0"
EOF
mkdir -p dir1/dir2 &&
touch dir1/a &&
touch dir1/b &&
touch dir1/dir2/a &&
touch dir1/dir2/b &&
git add . &&
git ls-files -v > actual &&
cat > expected <<-\EOF &&
H dir1/file1.txt
H dir1/file2.txt
S dir2/file1.txt
S dir2/file2.txt
EOF
test_cmp expected actual
'
test_expect_success 'verify including one file doesnt include the rest' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file1.txt\0"
printf "dir1/file2.txt\0"
printf "dir1/dir2/a\0"
EOF
mkdir -p dir1/dir2 &&
touch dir1/a &&
touch dir1/b &&
touch dir1/dir2/a &&
touch dir1/dir2/b &&
git add . &&
git ls-files -v > actual &&
cat > expected <<-\EOF &&
H dir1/dir2/a
H dir1/file1.txt
H dir1/file2.txt
S dir2/file1.txt
S dir2/file2.txt
EOF
test_cmp expected actual
'
test_expect_success 'verify files not listed are ignored by git clean -f -x' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "untracked.txt\0"
printf "dir1/\0"
EOF
mkdir -p dir3 &&
touch dir3/untracked.txt &&
git clean -f -x &&
test ! -f untracked.txt &&
test -d dir1 &&
test -f dir1/file1.txt &&
test -f dir1/file2.txt &&
test ! -f dir1/untracked.txt &&
test -f dir2/file1.txt &&
test -f dir2/file2.txt &&
test -f dir2/untracked.txt &&
test -d dir3 &&
test -f dir3/untracked.txt
'
test_expect_success 'verify files not listed are ignored by git clean -f -d -x' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "untracked.txt\0"
printf "dir1/\0"
printf "dir3/\0"
EOF
mkdir -p dir3 &&
touch dir3/untracked.txt &&
git clean -f -d -x &&
test ! -f untracked.txt &&
test -d dir1 &&
test -f dir1/file1.txt &&
test -f dir1/file2.txt &&
test ! -f dir1/untracked.txt &&
test -f dir2/file1.txt &&
test -f dir2/file2.txt &&
test -f dir2/untracked.txt &&
test ! -d dir3 &&
test ! -f dir3/untracked.txt
'
test_expect_success 'verify folder entries include all files' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/\0"
EOF
mkdir -p dir1/dir2 &&
touch dir1/a &&
touch dir1/b &&
touch dir1/dir2/a &&
touch dir1/dir2/b &&
git status -su > actual &&
cat > expected <<-\EOF &&
?? dir1/a
?? dir1/b
?? dir1/untracked.txt
EOF
test_cmp expected actual
'
test_expect_success 'verify case insensitivity of virtual file system entries' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/a\0"
printf "Dir1/Dir2/a\0"
printf "DIR2/\0"
EOF
mkdir -p dir1/dir2 &&
touch dir1/a &&
touch dir1/b &&
touch dir1/dir2/a &&
touch dir1/dir2/b &&
git -c core.ignorecase=false status -su > actual &&
cat > expected <<-\EOF &&
?? dir1/a
EOF
test_cmp expected actual &&
git -c core.ignorecase=true status -su > actual &&
cat > expected <<-\EOF &&
?? dir1/a
?? dir1/dir2/a
?? dir2/untracked.txt
EOF
test_cmp expected actual
'
test_expect_success 'on file created' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file3.txt\0"
EOF
touch dir1/file3.txt &&
git add . &&
git ls-files -v > actual &&
cat > expected <<-\EOF &&
S dir1/file1.txt
S dir1/file2.txt
H dir1/file3.txt
S dir2/file1.txt
S dir2/file2.txt
EOF
test_cmp expected actual
'
test_expect_success 'on file renamed' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file1.txt\0"
printf "dir1/file3.txt\0"
EOF
mv dir1/file1.txt dir1/file3.txt &&
git status -su > actual &&
cat > expected <<-\EOF &&
D dir1/file1.txt
?? dir1/file3.txt
EOF
test_cmp expected actual
'
test_expect_success 'on file deleted' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file1.txt\0"
EOF
rm dir1/file1.txt &&
git status -su > actual &&
cat > expected <<-\EOF &&
D dir1/file1.txt
EOF
test_cmp expected actual
'
test_expect_success 'on file overwritten' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/file1.txt\0"
EOF
echo "overwritten" > dir1/file1.txt &&
git status -su > actual &&
cat > expected <<-\EOF &&
M dir1/file1.txt
EOF
test_cmp expected actual
'
test_expect_success 'on folder created' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir1/dir1/\0"
EOF
mkdir -p dir1/dir1 &&
git status -su > actual &&
cat > expected <<-\EOF &&
EOF
test_cmp expected actual &&
git clean -fd &&
test ! -d "/dir1/dir1"
'
test_expect_success 'on folder renamed' '
clean_repo &&
write_script .git/hooks/virtualfilesystem <<-\EOF &&
printf "dir3/\0"
printf "dir1/file1.txt\0"
printf "dir1/file2.txt\0"
printf "dir3/file1.txt\0"
printf "dir3/file2.txt\0"
EOF
mv dir1 dir3 &&
git status -su > actual &&
cat > expected <<-\EOF &&
D dir1/file1.txt
D dir1/file2.txt
?? dir3/file1.txt
?? dir3/file2.txt
?? dir3/untracked.txt
EOF
test_cmp expected actual
'
test_done

Просмотреть файл

@ -1,5 +1,6 @@
#include "cache.h" #include "cache.h"
#include "gvfs.h" #include "gvfs.h"
#include "virtualfilesystem.h"
#include "strvec.h" #include "strvec.h"
#include "repository.h" #include "repository.h"
#include "config.h" #include "config.h"
@ -1685,6 +1686,14 @@ static int clear_ce_flags_1(struct index_state *istate,
continue; continue;
} }
/* if it's not in the virtual file system, exit early */
if (core_virtualfilesystem) {
if (is_included_in_virtualfilesystem(ce->name, ce->ce_namelen) > 0)
ce->ce_flags &= ~clear_mask;
cache++;
continue;
}
if (prefix->len && strncmp(ce->name, prefix->buf, prefix->len)) if (prefix->len && strncmp(ce->name, prefix->buf, prefix->len))
break; break;
@ -1907,7 +1916,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
if (!o->skip_sparse_checkout && !o->pl) { if (!o->skip_sparse_checkout && !o->pl) {
memset(&pl, 0, sizeof(pl)); memset(&pl, 0, sizeof(pl));
free_pattern_list = 1; free_pattern_list = 1;
populate_from_existing_patterns(o, &pl); if (core_virtualfilesystem)
o->pl = &pl;
else
populate_from_existing_patterns(o, &pl);
} }
index_state_init(&o->result, o->src_index->repo); index_state_init(&o->result, o->src_index->repo);

308
virtualfilesystem.c Normal file
Просмотреть файл

@ -0,0 +1,308 @@
#include "cache.h"
#include "config.h"
#include "dir.h"
#include "hashmap.h"
#include "run-command.h"
#include "virtualfilesystem.h"
#define HOOK_INTERFACE_VERSION (1)
static struct strbuf virtual_filesystem_data = STRBUF_INIT;
static struct hashmap virtual_filesystem_hashmap;
static struct hashmap parent_directory_hashmap;
struct virtualfilesystem {
struct hashmap_entry ent; /* must be the first member! */
const char *pattern;
int patternlen;
};
static unsigned int(*vfshash)(const void *buf, size_t len);
static int(*vfscmp)(const char *a, const char *b, size_t len);
static int vfs_hashmap_cmp(const void *unused_cmp_data,
const struct hashmap_entry *he1,
const struct hashmap_entry *he2,
const void *key)
{
const struct virtualfilesystem *vfs1 =
container_of(he1, const struct virtualfilesystem, ent);
const struct virtualfilesystem *vfs2 =
container_of(he2, const struct virtualfilesystem, ent);
return vfscmp(vfs1->pattern, vfs2->pattern, vfs1->patternlen);
}
static void get_virtual_filesystem_data(struct strbuf *vfs_data)
{
struct child_process cp = CHILD_PROCESS_INIT;
int err;
strbuf_init(vfs_data, 0);
strvec_push(&cp.args, core_virtualfilesystem);
strvec_pushf(&cp.args, "%d", HOOK_INTERFACE_VERSION);
cp.use_shell = 1;
cp.dir = get_git_work_tree();
err = capture_command(&cp, vfs_data, 1024);
if (err)
die("unable to load virtual file system");
}
static int check_includes_hashmap(struct hashmap *map, const char *pattern, int patternlen)
{
struct strbuf sb = STRBUF_INIT;
struct virtualfilesystem vfs;
char *slash;
/* Check straight mapping */
strbuf_reset(&sb);
strbuf_add(&sb, pattern, patternlen);
vfs.pattern = sb.buf;
vfs.patternlen = sb.len;
hashmap_entry_init(&vfs.ent, vfshash(vfs.pattern, vfs.patternlen));
if (hashmap_get_entry(map, &vfs, ent, NULL)) {
strbuf_release(&sb);
return 1;
}
/*
* Check to see if it matches a directory or any path
* underneath it. In other words, 'a/b/foo.txt' will match
* '/', 'a/', and 'a/b/'.
*/
slash = strchr(sb.buf, '/');
while (slash) {
vfs.pattern = sb.buf;
vfs.patternlen = slash - sb.buf + 1;
hashmap_entry_init(&vfs.ent, vfshash(vfs.pattern, vfs.patternlen));
if (hashmap_get_entry(map, &vfs, ent, NULL)) {
strbuf_release(&sb);
return 1;
}
slash = strchr(slash + 1, '/');
}
strbuf_release(&sb);
return 0;
}
static void includes_hashmap_add(struct hashmap *map, const char *pattern, const int patternlen)
{
struct virtualfilesystem *vfs;
vfs = xmalloc(sizeof(struct virtualfilesystem));
vfs->pattern = pattern;
vfs->patternlen = patternlen;
hashmap_entry_init(&vfs->ent, vfshash(vfs->pattern, vfs->patternlen));
hashmap_add(map, &vfs->ent);
}
static void initialize_includes_hashmap(struct hashmap *map, struct strbuf *vfs_data)
{
char *buf, *entry;
size_t len;
int i;
/*
* Build a hashmap of the virtual file system data we can use to look
* for cache entry matches quickly
*/
vfshash = ignore_case ? memihash : memhash;
vfscmp = ignore_case ? strncasecmp : strncmp;
hashmap_init(map, vfs_hashmap_cmp, NULL, 0);
entry = buf = vfs_data->buf;
len = vfs_data->len;
for (i = 0; i < len; i++) {
if (buf[i] == '\0') {
includes_hashmap_add(map, entry, buf + i - entry);
entry = buf + i + 1;
}
}
}
/*
* Return 1 if the requested item is found in the virtual file system,
* 0 for not found and -1 for undecided.
*/
int is_included_in_virtualfilesystem(const char *pathname, int pathlen)
{
if (!core_virtualfilesystem)
return -1;
if (!virtual_filesystem_hashmap.tablesize && virtual_filesystem_data.len)
initialize_includes_hashmap(&virtual_filesystem_hashmap, &virtual_filesystem_data);
if (!virtual_filesystem_hashmap.tablesize)
return -1;
return check_includes_hashmap(&virtual_filesystem_hashmap, pathname, pathlen);
}
static void parent_directory_hashmap_add(struct hashmap *map, const char *pattern, const int patternlen)
{
char *slash;
struct virtualfilesystem *vfs;
/*
* Add any directories leading up to the file as the excludes logic
* needs to match directories leading up to the files as well. Detect
* and prevent unnecessary duplicate entries which will be common.
*/
if (patternlen > 1) {
slash = strchr(pattern + 1, '/');
while (slash) {
vfs = xmalloc(sizeof(struct virtualfilesystem));
vfs->pattern = pattern;
vfs->patternlen = slash - pattern + 1;
hashmap_entry_init(&vfs->ent, vfshash(vfs->pattern, vfs->patternlen));
if (hashmap_get_entry(map, vfs, ent, NULL))
free(vfs);
else
hashmap_add(map, &vfs->ent);
slash = strchr(slash + 1, '/');
}
}
}
static void initialize_parent_directory_hashmap(struct hashmap *map, struct strbuf *vfs_data)
{
char *buf, *entry;
size_t len;
int i;
/*
* Build a hashmap of the parent directories contained in the virtual
* file system data we can use to look for matches quickly
*/
vfshash = ignore_case ? memihash : memhash;
vfscmp = ignore_case ? strncasecmp : strncmp;
hashmap_init(map, vfs_hashmap_cmp, NULL, 0);
entry = buf = vfs_data->buf;
len = vfs_data->len;
for (i = 0; i < len; i++) {
if (buf[i] == '\0') {
parent_directory_hashmap_add(map, entry, buf + i - entry);
entry = buf + i + 1;
}
}
}
static int check_directory_hashmap(struct hashmap *map, const char *pathname, int pathlen)
{
struct strbuf sb = STRBUF_INIT;
struct virtualfilesystem vfs;
/* Check for directory */
strbuf_reset(&sb);
strbuf_add(&sb, pathname, pathlen);
strbuf_addch(&sb, '/');
vfs.pattern = sb.buf;
vfs.patternlen = sb.len;
hashmap_entry_init(&vfs.ent, vfshash(vfs.pattern, vfs.patternlen));
if (hashmap_get_entry(map, &vfs, ent, NULL)) {
strbuf_release(&sb);
return 0;
}
strbuf_release(&sb);
return 1;
}
/*
* Return 1 for exclude, 0 for include and -1 for undecided.
*/
int is_excluded_from_virtualfilesystem(const char *pathname, int pathlen, int dtype)
{
if (!core_virtualfilesystem)
return -1;
if (dtype != DT_REG && dtype != DT_DIR && dtype != DT_LNK)
die(_("is_excluded_from_virtualfilesystem passed unhandled dtype"));
if (dtype == DT_REG) {
int ret = is_included_in_virtualfilesystem(pathname, pathlen);
if (ret > 0)
return 0;
if (ret == 0)
return 1;
return ret;
}
if (dtype == DT_DIR || dtype == DT_LNK) {
if (!parent_directory_hashmap.tablesize && virtual_filesystem_data.len)
initialize_parent_directory_hashmap(&parent_directory_hashmap, &virtual_filesystem_data);
if (!parent_directory_hashmap.tablesize)
return -1;
return check_directory_hashmap(&parent_directory_hashmap, pathname, pathlen);
}
return -1;
}
/*
* Update the CE_SKIP_WORKTREE bits based on the virtual file system.
*/
void apply_virtualfilesystem(struct index_state *istate)
{
char *buf, *entry;
int i;
if (!git_config_get_virtualfilesystem())
return;
if (!virtual_filesystem_data.len)
get_virtual_filesystem_data(&virtual_filesystem_data);
/* set CE_SKIP_WORKTREE bit on all entries */
for (i = 0; i < istate->cache_nr; i++)
istate->cache[i]->ce_flags |= CE_SKIP_WORKTREE;
/* clear CE_SKIP_WORKTREE bit for everything in the virtual file system */
entry = buf = virtual_filesystem_data.buf;
for (i = 0; i < virtual_filesystem_data.len; i++) {
if (buf[i] == '\0') {
int pos, len;
len = buf + i - entry;
/* look for a directory wild card (ie "dir1/") */
if (buf[i - 1] == '/') {
if (ignore_case)
adjust_dirname_case(istate, entry);
pos = index_name_pos(istate, entry, len - 1);
if (pos < 0) {
pos = -pos - 1;
while (pos < istate->cache_nr && !fspathncmp(istate->cache[pos]->name, entry, len)) {
istate->cache[pos]->ce_flags &= ~CE_SKIP_WORKTREE;
pos++;
}
}
} else {
if (ignore_case) {
struct cache_entry *ce = index_file_exists(istate, entry, len, ignore_case);
if (ce)
ce->ce_flags &= ~CE_SKIP_WORKTREE;
} else {
int pos = index_name_pos(istate, entry, len);
if (pos >= 0)
istate->cache[pos]->ce_flags &= ~CE_SKIP_WORKTREE;
}
}
entry += len + 1;
}
}
}
/*
* Free the virtual file system data structures.
*/
void free_virtualfilesystem(void) {
hashmap_clear_and_free(&virtual_filesystem_hashmap, struct virtualfilesystem, ent);
hashmap_clear_and_free(&parent_directory_hashmap, struct virtualfilesystem, ent);
strbuf_release(&virtual_filesystem_data);
}

25
virtualfilesystem.h Normal file
Просмотреть файл

@ -0,0 +1,25 @@
#ifndef VIRTUALFILESYSTEM_H
#define VIRTUALFILESYSTEM_H
/*
* Update the CE_SKIP_WORKTREE bits based on the virtual file system.
*/
void apply_virtualfilesystem(struct index_state *istate);
/*
* Return 1 if the requested item is found in the virtual file system,
* 0 for not found and -1 for undecided.
*/
int is_included_in_virtualfilesystem(const char *pathname, int pathlen);
/*
* Return 1 for exclude, 0 for include and -1 for undecided.
*/
int is_excluded_from_virtualfilesystem(const char *pathname, int pathlen, int dtype);
/*
* Free the virtual file system data structures.
*/
void free_virtualfilesystem(void);
#endif

Просмотреть файл

@ -1581,6 +1581,8 @@ static void show_sparse_checkout_in_use(struct wt_status *s,
{ {
if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_DISABLED) if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_DISABLED)
return; return;
if (core_virtualfilesystem)
return;
if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_SPARSE_INDEX) if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_SPARSE_INDEX)
status_printf_ln(s, color, _("You are in a sparse checkout.")); status_printf_ln(s, color, _("You are in a sparse checkout."));