perf(utils,file-list): index delete keep-set and --files-from lookups

This commit is contained in:
2026-09-13 04:16:28 +02:00
parent ddc71a7df5
commit 1a83e284c4
8 changed files with 470 additions and 50 deletions
+59 -18
View File
@@ -104,8 +104,37 @@ static int normalize_entry(const char* raw, size_t len, bool strip_line_endings,
return result;
}
/* Build the membership index: each non-empty entry plus every ancestor
directory prefix of it. The entry flag lets file_list_affects tell an exact
listed path from an ancestor of a listed path. An empty entry (the source
root) short-circuits every query, so it is recorded as whole_tree. */
static bool file_list_index_build(FileListSet* set, char* err, size_t err_size) {
if (!str_hash_set_init(&set->node_index, (size_t)set->count * 2 + 1)) {
snprintf(err, err_size, "memory allocation failed");
return false;
}
for (int i = 0; i < set->count; i++) {
const char* entry = set->entries[i];
if (entry[0] == '\0') {
set->whole_tree = true;
continue;
}
if (!str_hash_set_insert_ref(&set->node_index, entry, true)) {
snprintf(err, err_size, "memory allocation failed");
return false;
}
for (const char* slash = entry; (slash = strchr(slash, '/')) != NULL; slash++) {
if (!str_hash_set_insert_copy_n(&set->node_index, entry, (size_t)(slash - entry), false)) {
snprintf(err, err_size, "memory allocation failed");
return false;
}
}
}
return true;
}
static FileListSet* string_list_to_set(StringList* raw, char* err, size_t err_size) {
FileListSet* set = malloc(sizeof(FileListSet));
FileListSet* set = calloc(1, sizeof(FileListSet));
if (!set) {
snprintf(err, err_size, "memory allocation failed");
return NULL;
@@ -114,6 +143,10 @@ static FileListSet* string_list_to_set(StringList* raw, char* err, size_t err_si
set->entries = raw->items;
raw->items = NULL;
raw->count = 0;
if (!file_list_index_build(set, err, err_size)) {
file_list_destroy(set);
return NULL;
}
return set;
}
@@ -163,31 +196,39 @@ void file_list_destroy(FileListSet* set) {
for (int i = 0; i < set->count; i++)
free(set->entries[i]);
free(set->entries);
str_hash_set_free(&set->node_index);
free(set);
}
static bool path_has_prefix(const char* path, const char* prefix) {
size_t plen = strlen(prefix);
if (strncmp(path, prefix, plen) != 0)
return false;
return path[plen] == '/' || path[plen] == '\0';
}
bool file_list_affects(const FileListSet* set, const char* rel) {
if (!set)
return true;
if (!rel)
return false;
for (int i = 0; i < set->count; i++) {
const char* entry = set->entries[i];
if (entry[0] == '\0')
return true; /* whole tree listed */
if (strcmp(rel, entry) == 0)
return true; /* the entry itself is listed */
if (path_has_prefix(rel, entry))
return true; /* rel lives under a listed directory */
if (path_has_prefix(entry, rel))
return true; /* rel is an ancestor directory of a listed entry */
if (set->whole_tree)
return true; /* whole tree listed */
/* A node hit means `rel` is a listed entry, or an ancestor directory of one
(rel lives on the path to some listed entry). */
if (str_hash_set_lookup(&set->node_index, rel, NULL))
return true;
/* Otherwise `rel` is affected only when a listed entry is an ancestor of it;
walk rel's directory prefixes (which preserve path-boundary semantics) and
test each for an exact entry. */
size_t len = strlen(rel);
while (len > 0) {
const char* slash = NULL;
for (size_t i = len; i-- > 0;) {
if (rel[i] == '/') {
slash = rel + i;
break;
}
}
if (!slash)
break;
len = (size_t)(slash - rel);
bool is_entry = false;
if (str_hash_set_lookup_n(&set->node_index, rel, len, &is_entry) && is_entry)
return true;
}
return false;
}