git/virtualfilesystem.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);
}