Implement a new caching system to make repeated runs much much faster.

This commit is contained in:
Squibid 2025-11-14 04:02:16 -05:00
parent 4a008a82b0
commit 5e0e140b09
Signed by: squibid
GPG key ID: BECE5684D3C4005D
8 changed files with 252 additions and 44 deletions

View file

@ -2,13 +2,12 @@ include config.mk
# flags and incs # flags and incs
PKGS = $(GITLIB) 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)` LIBS = `$(PKG_CONFIG) --libs --cflags $(PKGS)`
all: XD all: XD
XD: XD.o XD: XD.o hash.o helpers.o
$(CC) *.o $(CFLAGS) $(LIBS) -o $@ $(CC) *.o $(CFLAGS) $(LIBS) -o $@
XD.o: XD.c
clean: clean:
rm -f XD *.o rm -f XD *.o

9
XD.1
View file

@ -12,9 +12,12 @@
.Sh DESCRIPTION .Sh DESCRIPTION
.Nm .Nm
Displays information using a smiley face like so: :) Displays information using a smiley face like so: :)
to interpret it refer to the following tables: .Nm
Displays information using a smiley face. does some special caching to run as fast as possible but there's only so much
to interpret XD's output refer to the following tables (or 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 .Nm
\fB-e\fR): \fB-e\fR):
.Ss Eyes .Ss Eyes

58
XD.c
View file

@ -1,3 +1,4 @@
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -16,42 +17,11 @@
#include <git2.h> #include <git2.h>
#endif #endif
#include "helpers.h"
#include "hash.h"
#define P(X) fwrite(X, 1, 1, stdout) #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 #ifdef GIT
/** /**
* @brief search all parent directories for a git repo * @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_options opts = GIT_STATUS_OPTIONS_INIT;
git_status_list *list = NULL; git_status_list *list = NULL;
repohash *storedhash;
int r = 0; 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.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
@ -209,9 +190,10 @@ has_untracked(git_repository *repo)
} }
/* if any changes are found return 1 */ /* if any changes are found return 1 */
if (git_status_list_entrycount(list) > 0) { r = git_status_list_entrycount(list) > 0;
r = 1; #ifdef GITHASH
} write_hash(repo, (repohash){ .hash = generate_hash(repo), .changes = r });
#endif
git_status_list_free(list); git_status_list_free(list);
return r; return r;

View file

@ -7,9 +7,11 @@ PREFIX = /usr/local
MANDIR = $(PREFIX)/share/man MANDIR = $(PREFIX)/share/man
GIT = GIT =
GITHASH =
GITLIB = GITLIB =
# comment to disable git support # comment to disable git support
GIT = -DGIT GIT = -DGIT
GITHASH = -DGITHASH
GITLIB = libgit2 GITLIB = libgit2
ERR = ERR =
@ -24,6 +26,11 @@ EXPLAIN = -DEXPLAIN
ifneq ($(GIT),) ifneq ($(GIT),)
VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)` VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)`
endif endif
ifeq ($(GITHASH),)
VERSION := $(VERSION)"\\ngit hashing disabled"
else
VERSION := $(VERSION)"\\ngit hashing enabled"
endif
ifeq ($(ERR),) ifeq ($(ERR),)
VERSION := $(VERSION)"\\nerrors disabled" VERSION := $(VERSION)"\\nerrors disabled"
else else

132
hash.c Normal file
View file

@ -0,0 +1,132 @@
#include <git2/repository.h>
#include <git2/types.h>
#include <linux/limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <stdint.h>
#include <errno.h>
#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

37
hash.h Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <git2/repository.h>
#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

28
helpers.c Normal file
View file

@ -0,0 +1,28 @@
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#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

20
helpers.h Normal file
View file

@ -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