зеркало из https://github.com/microsoft/git.git
395 строки
10 KiB
C
395 строки
10 KiB
C
#include "git-compat-util.h"
|
|
#include "environment.h"
|
|
#include "gettext.h"
|
|
#include "repository.h"
|
|
#include "trace2.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 || dtype == DT_LNK) {
|
|
int ret = is_included_in_virtualfilesystem(pathname, pathlen);
|
|
if (ret > 0)
|
|
return 0;
|
|
if (ret == 0)
|
|
return 1;
|
|
return ret;
|
|
}
|
|
|
|
if (dtype == DT_DIR) {
|
|
int ret = is_included_in_virtualfilesystem(pathname, pathlen);
|
|
if (ret > 0)
|
|
return 0;
|
|
|
|
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;
|
|
}
|
|
|
|
struct apply_virtual_filesystem_stats {
|
|
int nr_unknown;
|
|
int nr_vfs_dirs;
|
|
int nr_vfs_rows;
|
|
int nr_bulk_skip;
|
|
int nr_explicit_skip;
|
|
};
|
|
|
|
static void clear_ce_flags_virtualfilesystem_1(struct index_state *istate, int select_mask, int clear_mask,
|
|
struct apply_virtual_filesystem_stats *stats)
|
|
{
|
|
char *buf, *entry;
|
|
int i;
|
|
|
|
if (!virtual_filesystem_data.len)
|
|
get_virtual_filesystem_data(&virtual_filesystem_data);
|
|
|
|
/* clear specified flag bits 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') {
|
|
struct cache_entry *ce;
|
|
int pos, len;
|
|
|
|
stats->nr_vfs_rows++;
|
|
|
|
len = buf + i - entry;
|
|
|
|
/* look for a directory wild card (ie "dir1/") */
|
|
if (buf[i - 1] == '/') {
|
|
stats->nr_vfs_dirs++;
|
|
if (ignore_case)
|
|
adjust_dirname_case(istate, entry);
|
|
pos = index_name_pos(istate, entry, len);
|
|
if (pos < 0) {
|
|
for (pos = -pos - 1; pos < istate->cache_nr; pos++) {
|
|
ce = istate->cache[pos];
|
|
if (fspathncmp(ce->name, entry, len))
|
|
break;
|
|
|
|
if (select_mask && !(ce->ce_flags & select_mask))
|
|
continue;
|
|
|
|
if (ce->ce_flags & clear_mask)
|
|
stats->nr_bulk_skip++;
|
|
ce->ce_flags &= ~clear_mask;
|
|
}
|
|
}
|
|
} else {
|
|
if (ignore_case) {
|
|
ce = index_file_exists(istate, entry, len, ignore_case);
|
|
} else {
|
|
int pos = index_name_pos(istate, entry, len);
|
|
|
|
ce = NULL;
|
|
if (pos >= 0)
|
|
ce = istate->cache[pos];
|
|
}
|
|
|
|
if (ce) {
|
|
do {
|
|
if (!select_mask || (ce->ce_flags & select_mask)) {
|
|
if (ce->ce_flags & clear_mask)
|
|
stats->nr_explicit_skip++;
|
|
ce->ce_flags &= ~clear_mask;
|
|
}
|
|
|
|
/*
|
|
* There may be aliases with different cases of the same
|
|
* name that also need to be modified.
|
|
*/
|
|
if (ignore_case)
|
|
ce = index_file_next_match(istate, ce, ignore_case);
|
|
else
|
|
break;
|
|
|
|
} while (ce);
|
|
} else {
|
|
stats->nr_unknown++;
|
|
}
|
|
}
|
|
|
|
entry += len + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clear the specified flags for all entries in the virtual file system
|
|
* that match the specified select mask. Returns the number of entries
|
|
* processed.
|
|
*/
|
|
int clear_ce_flags_virtualfilesystem(struct index_state *istate, int select_mask, int clear_mask)
|
|
{
|
|
struct apply_virtual_filesystem_stats stats = {0};
|
|
|
|
clear_ce_flags_virtualfilesystem_1(istate, select_mask, clear_mask, &stats);
|
|
return istate->cache_nr;
|
|
}
|
|
|
|
/*
|
|
* Update the CE_SKIP_WORKTREE bits based on the virtual file system.
|
|
*/
|
|
void apply_virtualfilesystem(struct index_state *istate)
|
|
{
|
|
int i;
|
|
struct apply_virtual_filesystem_stats stats = {0};
|
|
|
|
if (!git_config_get_virtualfilesystem())
|
|
return;
|
|
|
|
trace2_region_enter("vfs", "apply", the_repository);
|
|
|
|
/* 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_flags_virtualfilesystem_1(istate, 0, CE_SKIP_WORKTREE, &stats);
|
|
if (stats.nr_vfs_rows > 0) {
|
|
trace2_data_intmax("vfs", the_repository, "apply/tracked", stats.nr_bulk_skip + stats.nr_explicit_skip);
|
|
|
|
trace2_data_intmax("vfs", the_repository, "apply/vfs_rows", stats.nr_vfs_rows);
|
|
trace2_data_intmax("vfs", the_repository, "apply/vfs_dirs", stats.nr_vfs_dirs);
|
|
|
|
trace2_data_intmax("vfs", the_repository, "apply/nr_unknown", stats.nr_unknown);
|
|
trace2_data_intmax("vfs", the_repository, "apply/nr_bulk_skip", stats.nr_bulk_skip);
|
|
trace2_data_intmax("vfs", the_repository, "apply/nr_explicit_skip", stats.nr_explicit_skip);
|
|
}
|
|
|
|
trace2_region_leave("vfs", "apply", the_repository);
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|