From 5e0e140b09b15e66bdc2fb5dff3a2e9d486c194a Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 14 Nov 2025 04:02:16 -0500 Subject: [PATCH] Implement a new caching system to make repeated runs much much faster. --- Makefile | 5 +-- XD.1 | 9 ++-- XD.c | 58 +++++++++--------------- config.mk | 7 +++ hash.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hash.h | 37 +++++++++++++++ helpers.c | 28 ++++++++++++ helpers.h | 20 +++++++++ 8 files changed, 252 insertions(+), 44 deletions(-) create mode 100644 hash.c create mode 100644 hash.h create mode 100644 helpers.c create mode 100644 helpers.h diff --git a/Makefile b/Makefile index 0027568..2b7ff74 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,12 @@ include config.mk # flags and incs PKGS = $(GITLIB) -CFLAGS = -DVERSION=\"$(VERSION)\" -Wall -pedantic -O3 $(GIT) $(ERR) $(EXPLAIN) +CFLAGS = -DVERSION=\"$(VERSION)\" -Wall -pedantic -O3 $(GIT) $(GITHASH) $(ERR) $(EXPLAIN) LIBS = `$(PKG_CONFIG) --libs --cflags $(PKGS)` all: XD -XD: XD.o +XD: XD.o hash.o helpers.o $(CC) *.o $(CFLAGS) $(LIBS) -o $@ -XD.o: XD.c clean: rm -f XD *.o diff --git a/XD.1 b/XD.1 index 380bff6..d7a147d 100644 --- a/XD.1 +++ b/XD.1 @@ -12,9 +12,12 @@ .Sh DESCRIPTION .Nm Displays information using a smiley face like so: :) -to interpret it refer to the following tables: -Displays information using a smiley face. -to interpret XD's output refer to the following tables (or +.Nm +does some special caching to run as fast as possible but there's only so much +that can be done without relying on an external daemon. Therefore when new +changes are made in very large repositories it may take a few seconds to +determine if there are any changes. To interpret XD's output refer to the +following tables (or .Nm \fB-e\fR): .Ss Eyes diff --git a/XD.c b/XD.c index 85b272e..695b854 100644 --- a/XD.c +++ b/XD.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -16,42 +17,11 @@ #include #endif +#include "helpers.h" +#include "hash.h" + #define P(X) fwrite(X, 1, 1, stdout) -#if defined(ERR) || defined(EXPLAIN) -void -l(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { - fputc(' ', stderr); - perror(NULL); - } else { - fputc('\n', stderr); - } -} -#endif - -#ifdef ERR -#define L(...) l(__VA_ARGS__) -#else -#define L(...) -#endif - -#ifdef EXPLAIN -static int explain = 0; -#define E(...) if (explain) { \ - l(__VA_ARGS__); \ - } else -#else -#define E(...) -#endif - #ifdef GIT /** * @brief search all parent directories for a git repo @@ -194,9 +164,20 @@ has_untracked(git_repository *repo) { git_status_options opts = GIT_STATUS_OPTIONS_INIT; git_status_list *list = NULL; + repohash *storedhash; int r = 0; - /* FIXME: this is really slow in large git repos :( */ + #ifdef GITHASH + if ((storedhash = read_hash(repo)) + && storedhash->hash == generate_hash(repo)) { + r = storedhash->changes; + free(storedhash); + return r; + } + #endif + + /* if we need to regen the hash then we need to do a hard check on the real + * git repository */ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | @@ -209,9 +190,10 @@ has_untracked(git_repository *repo) } /* if any changes are found return 1 */ - if (git_status_list_entrycount(list) > 0) { - r = 1; - } + r = git_status_list_entrycount(list) > 0; + #ifdef GITHASH + write_hash(repo, (repohash){ .hash = generate_hash(repo), .changes = r }); + #endif git_status_list_free(list); return r; diff --git a/config.mk b/config.mk index 6cd8f8c..1bd082d 100644 --- a/config.mk +++ b/config.mk @@ -7,9 +7,11 @@ PREFIX = /usr/local MANDIR = $(PREFIX)/share/man GIT = +GITHASH = GITLIB = # comment to disable git support GIT = -DGIT +GITHASH = -DGITHASH GITLIB = libgit2 ERR = @@ -24,6 +26,11 @@ EXPLAIN = -DEXPLAIN ifneq ($(GIT),) VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)` endif +ifeq ($(GITHASH),) + VERSION := $(VERSION)"\\ngit hashing disabled" +else + VERSION := $(VERSION)"\\ngit hashing enabled" +endif ifeq ($(ERR),) VERSION := $(VERSION)"\\nerrors disabled" else diff --git a/hash.c b/hash.c new file mode 100644 index 0000000..55a16f6 --- /dev/null +++ b/hash.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hash.h" +#include "helpers.h" + +#ifdef GITHASH +static uint64_t +murmur64(uint64_t k) +{ + k ^= k >> 33; + k *= 0xff51afd7ed558ccdLLU; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53LLU; + k ^= k >> 33; + + return k; +} + +uint64_t +generate_hash(git_repository *repo) +{ + struct stat dir, index; + char path[PATH_MAX] = { 0 }; + + const char *gitpath = git_repository_path(repo); + if (strlen(gitpath) + strlen("/..") > PATH_MAX - 1) { + L("strlen: %s", strerror(errno)); + return -1; + } + + strcat(path, gitpath); + if (stat(path, &index) < 0) { + return -1; + } + strcat(path, "/.."); + if (stat(path, &dir) < 0) { + return -1; + } + return murmur64(dir.st_mtim.tv_nsec ^ index.st_mtim.tv_nsec); +} + +repohash +*read_hash(git_repository *repo) +{ + FILE *f; + uint64_t data; + uint8_t changes; + repohash *hash = malloc(sizeof(repohash)); + char path[PATH_MAX] = { 0 }; + + const char *gitpath = git_repository_path(repo); + if (strlen(gitpath) + strlen(XD_HASH_PATH) > PATH_MAX - 1) { + L("strlen: %s", strerror(errno)); + return NULL; + } + strcat(path, gitpath); + strcat(path, XD_HASH_PATH); + + f = fopen(path, "r"); + if (!f) { + L("fopen: %s", strerror(errno)); + return NULL; + } + + if (fread(&data, sizeof(uint64_t), 1, f) != 1) { + L("fread: %s", strerror(errno)); + return NULL; + } + + if (fseek(f, sizeof(uint64_t), SEEK_SET) < 0) { + L("fseek: %s", strerror(errno)); + return NULL; + } + + if (fread(&changes, sizeof(uint8_t), 1, f) != 1) { + L("fread: %s", strerror(errno)); + return NULL; + } + fclose(f); + + hash->hash = data; + hash->changes = changes; + return hash; +} + +int +write_hash(git_repository *repo, repohash hash) +{ + FILE *f; + char path[PATH_MAX] = { 0 }; + + const char *gitpath = git_repository_path(repo); + if (strlen(gitpath) + strlen(XD_HASH_PATH) > PATH_MAX - 1) { + L("strlen: %s", strerror(errno)); + return 1; + } + strcat(path, gitpath); + strcat(path, XD_HASH_PATH); + + f = fopen(path, "wb"); + if (!f) { + L("fopen: %s", strerror(errno)); + return 1; + } + + if (fwrite(&hash.hash, sizeof(uint64_t), 1, f) != 1) { + L("fwrite: %s", strerror(errno)); + return 1; + } + + if (fseek(f, sizeof(uint64_t), SEEK_SET) < 0) { + L("fseek: %s", strerror(errno)); + return 1; + } + + if (fwrite(&hash.changes, sizeof(uint8_t), 1, f) != 1) { + L("fwrite: %s", strerror(errno)); + return 1; + } + + fclose(f); + return 0; +} +#endif diff --git a/hash.h b/hash.h new file mode 100644 index 0000000..a78cd99 --- /dev/null +++ b/hash.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +#ifdef GIT +typedef struct { + uint64_t hash; + bool changes; +} repohash; + +#define XD_HASH_PATH "/XDhash" +/** + * @brief generate a hash from the repository state + * + * @param repo the git repository + * @return the hash + */ +uint64_t generate_hash(git_repository *repo); + +/** + * @brief read the hash from the git repo + * + * @param repo the git repository + * @return the hash + */ +repohash *read_hash(git_repository *repo); + +/** + * @brief write a new hash to the repository + * + * @param repo the repository + * @param hash the hash to write + */ +int write_hash(git_repository *repo, repohash hash); +#endif diff --git a/helpers.c b/helpers.c new file mode 100644 index 0000000..e3ef987 --- /dev/null +++ b/helpers.c @@ -0,0 +1,28 @@ +#include +#include +#include + +#include "helpers.h" + +#ifdef EXPLAIN +int explain = 0; +#endif + +#if defined(ERR) || defined(EXPLAIN) +void +l(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } +} +#endif diff --git a/helpers.h b/helpers.h new file mode 100644 index 0000000..81666ab --- /dev/null +++ b/helpers.h @@ -0,0 +1,20 @@ +#pragma once + +#if defined(ERR) || defined(EXPLAIN) +void l(const char *fmt, ...); +#endif + +#ifdef ERR +#define L(...) l(__VA_ARGS__) +#else +#define L(...) +#endif + +#ifdef EXPLAIN +extern int explain; +#define E(...) if (explain) { \ + l(__VA_ARGS__); \ + } else +#else +#define E(...) +#endif