зеркало из https://github.com/microsoft/git.git
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:
Родитель
907058a104
Коммит
44d5d29600
|
@ -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
|
||||
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::
|
||||
If false, the ctime differences between the index and the
|
||||
working tree are ignored; useful when the inode change time
|
||||
|
|
|
@ -751,6 +751,26 @@ and "0" meaning they were not.
|
|||
Only one parameter should be set to "1" when the hook runs. The hook
|
||||
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
|
||||
--------
|
||||
linkgit:git-hook[1]
|
||||
|
|
1
Makefile
1
Makefile
|
@ -1192,6 +1192,7 @@ LIB_OBJS += utf8.o
|
|||
LIB_OBJS += varint.o
|
||||
LIB_OBJS += version.o
|
||||
LIB_OBJS += versioncmp.o
|
||||
LIB_OBJS += virtualfilesystem.o
|
||||
LIB_OBJS += walker.o
|
||||
LIB_OBJS += wildmatch.o
|
||||
LIB_OBJS += worktree.o
|
||||
|
|
30
config.c
30
config.c
|
@ -1821,7 +1821,11 @@ int git_default_core_config(const char *var, const char *value, void *cb)
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -2968,6 +2972,30 @@ int git_config_get_max_percent_split_change(void)
|
|||
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 is_bool, val;
|
||||
|
|
1
config.h
1
config.h
|
@ -661,6 +661,7 @@ int git_config_get_pathname(const char *key, const char **dest);
|
|||
int git_config_get_index_threads(int *dest);
|
||||
int git_config_get_split_index(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 */
|
||||
int git_config_get_expiry(const char *key, const char **output);
|
||||
|
|
32
dir.c
32
dir.c
|
@ -8,6 +8,7 @@
|
|||
#include "git-compat-util.h"
|
||||
#include "abspath.h"
|
||||
#include "alloc.h"
|
||||
#include "virtualfilesystem.h"
|
||||
#include "config.h"
|
||||
#include "convert.h"
|
||||
#include "dir.h"
|
||||
|
@ -1430,6 +1431,17 @@ enum pattern_match_result path_matches_pattern_list(
|
|||
int result = NOT_MATCHED;
|
||||
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) {
|
||||
pattern = last_matching_pattern_from_list(pathname, pathlen, basename,
|
||||
dtype, pl, istate);
|
||||
|
@ -1774,8 +1786,20 @@ struct path_pattern *last_matching_pattern(struct dir_struct *dir,
|
|||
int is_excluded(struct dir_struct *dir, struct index_state *istate,
|
||||
const char *pathname, int *dtype_p)
|
||||
{
|
||||
struct path_pattern *pattern =
|
||||
last_matching_pattern(dir, istate, pathname, dtype_p);
|
||||
struct path_pattern *pattern;
|
||||
|
||||
/*
|
||||
* 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)
|
||||
return pattern->flags & PATTERN_FLAG_NEGATIVE ? 0 : 1;
|
||||
return 0;
|
||||
|
@ -2361,6 +2385,8 @@ static enum path_treatment treat_path(struct dir_struct *dir,
|
|||
ignore_case);
|
||||
if (dtype != DT_DIR && has_path_in_index)
|
||||
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,
|
||||
|
@ -2565,6 +2591,8 @@ static void add_path_to_appropriate_result_list(struct dir_struct *dir,
|
|||
/* add the path to the appropriate result list */
|
||||
switch (state) {
|
||||
case path_excluded:
|
||||
if (is_excluded_from_virtualfilesystem(path->buf, path->len, DT_DIR) > 0)
|
||||
break;
|
||||
if (dir->flags & DIR_SHOW_IGNORED)
|
||||
dir_add_name(dir, istate, path->buf, path->len);
|
||||
else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
|
||||
|
|
|
@ -80,6 +80,7 @@ int core_apply_sparse_checkout;
|
|||
int core_sparse_checkout_cone;
|
||||
int sparse_expect_files_outside_of_patterns;
|
||||
int core_gvfs;
|
||||
const char *core_virtualfilesystem;
|
||||
int merge_log_config = -1;
|
||||
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
|
||||
unsigned long pack_size_limit_cfg;
|
||||
|
|
|
@ -147,6 +147,7 @@ int get_shared_repository(void);
|
|||
void reset_shared_repository(void);
|
||||
|
||||
extern int core_preload_index;
|
||||
extern const char *core_virtualfilesystem;
|
||||
extern int core_gvfs;
|
||||
extern int precomposed_unicode;
|
||||
extern int protect_hfs;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
#include "cache.h"
|
||||
#include "alloc.h"
|
||||
#include "virtualfilesystem.h"
|
||||
#include "config.h"
|
||||
#include "date.h"
|
||||
#include "diff.h"
|
||||
|
@ -2033,6 +2034,7 @@ static void post_read_index_from(struct index_state *istate)
|
|||
tweak_untracked_cache(istate);
|
||||
tweak_split_index(istate);
|
||||
tweak_fsmonitor(istate);
|
||||
apply_virtualfilesystem(istate);
|
||||
}
|
||||
|
||||
static size_t estimate_cache_size_from_compressed(unsigned int entries)
|
||||
|
|
|
@ -496,6 +496,7 @@ void clear_skip_worktree_from_present_files(struct index_state *istate)
|
|||
int restarted = 0;
|
||||
|
||||
if (!core_apply_sparse_checkout ||
|
||||
core_virtualfilesystem ||
|
||||
sparse_expect_files_outside_of_patterns)
|
||||
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' '
|
||||
# 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.
|
||||
test_config sparse.expectfilesoutsideofpatterns true &&
|
||||
test_config core.virtualfilesystem true &&
|
||||
|
||||
test_config core.gvfs 8 &&
|
||||
git checkout -b outside &&
|
||||
|
|
|
@ -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,6 +1,7 @@
|
|||
#include "cache.h"
|
||||
#include "advice.h"
|
||||
#include "gvfs.h"
|
||||
#include "virtualfilesystem.h"
|
||||
#include "strvec.h"
|
||||
#include "repository.h"
|
||||
#include "config.h"
|
||||
|
@ -1693,6 +1694,14 @@ static int clear_ce_flags_1(struct index_state *istate,
|
|||
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))
|
||||
break;
|
||||
|
||||
|
@ -1919,7 +1928,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
|
|||
if (!o->skip_sparse_checkout) {
|
||||
memset(&pl, 0, sizeof(pl));
|
||||
free_pattern_list = 1;
|
||||
populate_from_existing_patterns(o, &pl);
|
||||
if (core_virtualfilesystem)
|
||||
o->internal.pl = &pl;
|
||||
else
|
||||
populate_from_existing_patterns(o, &pl);
|
||||
}
|
||||
|
||||
index_state_init(&o->internal.result, o->src_index->repo);
|
||||
|
|
|
@ -0,0 +1,311 @@
|
|||
#include "git-compat-util.h"
|
||||
#include "environment.h"
|
||||
#include "gettext.h"
|
||||
#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);
|
||||
}
|
|
@ -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
|
|
@ -1590,6 +1590,8 @@ static void show_sparse_checkout_in_use(struct wt_status *s,
|
|||
{
|
||||
if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_DISABLED)
|
||||
return;
|
||||
if (core_virtualfilesystem)
|
||||
return;
|
||||
|
||||
if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_SPARSE_INDEX)
|
||||
status_printf_ln(s, color, _("You are in a sparse checkout."));
|
||||
|
|
Загрузка…
Ссылка в новой задаче