diff --git a/.cache/clangd/index/XD.c.CF9B5320BE29FE6C.idx b/.cache/clangd/index/XD.c.CF9B5320BE29FE6C.idx deleted file mode 100644 index 7699412..0000000 Binary files a/.cache/clangd/index/XD.c.CF9B5320BE29FE6C.idx and /dev/null differ diff --git a/Makefile b/Makefile index 0027568..66be544 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) $(PERF) $(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 c5af3cd..de8d694 100644 --- a/XD.c +++ b/XD.c @@ -1,7 +1,9 @@ +#include #include #include #include #include +#include #ifdef ERR #include @@ -16,42 +18,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 @@ -61,6 +32,7 @@ static int explain = 0; char *find_git_repo(void) { + PS(); char path[PATH_MAX] = ".", fstr[PATH_MAX], *rpath, *res; struct stat s; FILE *f; @@ -70,6 +42,7 @@ char rpath = realpath(path, NULL); if (!rpath) { L("realpath: %s", strerror(errno)); + PE(); return NULL; } for (i = c = 0; i < strlen(rpath); i++) { @@ -86,22 +59,26 @@ char /* if there seems to be a git directory return the directory it was found in */ if (stat(path, &s) == 0) { if (S_ISDIR(s.st_mode)) { + PE(); return realpath(path, NULL); } else if (S_ISREG(s.st_mode)) { /* we do some special magic here to check if we're in a submodule */ f = fopen(path, "r"); if (!f) { L("fopen: %s", strerror(errno)); + PE(); return NULL; } res = fgets(fstr, PATH_MAX, f); fclose(f); if (!res) { L("fgets: %s", strerror(errno)); + PE(); return NULL; } if (strncmp(fstr, "gitdir: ", strlen("gitdir: ")) == 0) { fstr[strlen(fstr) - 1] = '\0'; + PE(); return realpath(fstr + strlen("gitdir: "), NULL); } } @@ -112,6 +89,7 @@ char memset(&path[strlen(path) - 2], 0, 2); } + PE(); return NULL; } @@ -123,11 +101,13 @@ char git_repository *init_git(void) { + PS(); char *buf; git_repository *repo; /* check for a repo before loading libgit2 */ if ((buf = find_git_repo()) == NULL) { + PE(); return NULL; } @@ -143,17 +123,20 @@ git_repository /* initialize the git library and repository */ if (git_libgit2_init() < 0) { L("Failed to initalize libgit2, proceeding without git functionality enabled."); + PE(); return NULL; } if (git_repository_open(&repo, buf) < 0) { L("Failed to open git repo: %s", git_error_last()->message); free(buf); + PE(); return NULL; } /* get rid of object containing git repo path and return the repo */ free(buf); + PE(); return repo; } @@ -166,20 +149,24 @@ git_repository int has_stashes(git_repository *repo) { + PS(); git_reference *stash = NULL; int e; e = git_reference_lookup(&stash, repo, "refs/stash"); if (e == GIT_ENOTFOUND) { + PE(); return 0; } else if (e < 0) { L("Error looking up stash reference: %s", git_error_last()->message); + PE(); return 0; } else { e = 1; } git_reference_free(stash); + PE(); return e; } @@ -192,27 +179,45 @@ has_stashes(git_repository *repo) int has_untracked(git_repository *repo) { + PS(); git_status_options opts = GIT_STATUS_OPTIONS_INIT; git_status_list *list = NULL; + repohash *storedhash; + uint64_t newhash = generate_hash(repo); int r = 0; - /* FIXME: this is really slow in large git repos :( */ - opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + #ifdef GITHASH + if ((storedhash = read_hash(repo)) + && storedhash->hash == newhash) { + r = storedhash->changes; + free(storedhash); + PE(); + 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 | + GIT_STATUS_OPT_UPDATE_INDEX | GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; if (git_status_list_new(&list, repo, &opts) < 0) { L("Error checking for untracked changes: %s", git_error_last()->message); + PE(); return 0; } /* 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 = newhash, .changes = r }); + #endif git_status_list_free(list); + PE(); return r; } @@ -225,6 +230,7 @@ has_untracked(git_repository *repo) int has_staged(git_repository *repo) { + PS(); git_status_entry entry; git_status_list *list = NULL; git_status_options opts = GIT_STATUS_OPTIONS_INIT; @@ -235,6 +241,7 @@ has_staged(git_repository *repo) if (git_status_list_new(&list, repo, &opts) < 0) { L("Error checking for staged changes: %s", git_error_last()->message); + PE(); return 0; } @@ -253,6 +260,7 @@ has_staged(git_repository *repo) } git_status_list_free(list); + PE(); return r; } #endif @@ -260,17 +268,20 @@ has_staged(git_repository *repo) inline unsigned numcat(unsigned x, unsigned y) { + PS(); unsigned pow = 10; while(y >= pow) { pow *= 10; } + PE(); return (x * pow) + y; } int str_to_int(char *str) { + PS(); int res = -1; char *c; @@ -282,17 +293,20 @@ str_to_int(char *str) } } + PE(); return res; } int main(int argc, char *argv[]) { + PS(); int code = -1; /* print version information */ if (argc > 1 && strcmp(argv[1], "-v") == 0) { printf("XD [number] %s\n", VERSION); + PE(); return 0; #ifdef EXPLAIN } else if (argc > 1 && strcmp(argv[1], "-e") == 0) { @@ -344,6 +358,7 @@ main(int argc, char *argv[]) code = str_to_int(argv[argc - 1]); if (code < 0) { L("Return code, %d, not valid", code); + PE(); exit(1); } } @@ -377,5 +392,6 @@ main(int argc, char *argv[]) P("|"); /* no code info */ } + PE(); return code; } diff --git a/config.mk b/config.mk index b7f4032..2614a75 100644 --- a/config.mk +++ b/config.mk @@ -1,4 +1,4 @@ -VERSION = `git describe --tags --abbrev=0` +VERSION := `git describe --tags --dirty` PKG_CONFIG = pkg-config @@ -7,15 +7,21 @@ PREFIX = /usr/local MANDIR = $(PREFIX)/share/man GIT = +GITHASH = GITLIB = # comment to disable git support GIT = -DGIT +GITHASH = -DGITHASH GITLIB = libgit2 ERR = # uncomment to enable errors # ERR = -DERR +PERF = +# uncomment to enable performance logging +# PERF = -DPERF + EXPLAIN = # comment to disable explinations EXPLAIN = -DEXPLAIN @@ -24,15 +30,25 @@ EXPLAIN = -DEXPLAIN ifneq ($(GIT),) VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)` endif -ifeq ($(ERR),) - VERSION := $(VERSION)"\\nerrors disabled" +ifeq ($(GITHASH),) + VERSION := $(VERSION)"\\ngit hashing disabled" else - VERSION := $(VERSION)"\\nerrors enabled" + VERSION := $(VERSION)"\\ngit hashing enabled" endif ifeq ($(EXPLAIN),) VERSION := $(VERSION)"\\nexplinations disabled" else VERSION := $(VERSION)"\\nexplinations enabled" endif +ifeq ($(ERR),) + VERSION := $(VERSION)"\\nerrors disabled" +else + VERSION := $(VERSION)"\\nerrors enabled" +endif +ifeq ($(PERF),) + VERSION := $(VERSION)"\\nperformance logging disabled" +else + VERSION := $(VERSION)"\\nperformance logging enabled" +endif CC = cc diff --git a/hash.c b/hash.c new file mode 100644 index 0000000..5403d50 --- /dev/null +++ b/hash.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hash.h" +#include "helpers.h" + +#ifdef GITHASH +static uint64_t +murmur64(uint64_t k) +{ + PS(); + k ^= k >> 33; + k *= 0xff51afd7ed558ccdLLU; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53LLU; + k ^= k >> 33; + + PE(); + return k; +} + +uint64_t +generate_hash(git_repository *repo) +{ + PS(); + 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)); + PE(); + return -1; + } + + strcat(path, gitpath); + if (stat(path, &index) < 0) { + PE(); + return -1; + } + strcat(path, "/.."); + if (stat(path, &dir) < 0) { + PE(); + return -1; + } + PE(); + return murmur64(dir.st_mtim.tv_nsec ^ index.st_mtim.tv_nsec); +} + +repohash +*read_hash(git_repository *repo) +{ + PS(); + 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)); + PE(); + return NULL; + } + strcat(path, gitpath); + strcat(path, XD_HASH_PATH); + + f = fopen(path, "r"); + if (!f) { + L("fopen: %s", strerror(errno)); + PE(); + return NULL; + } + + if (fread(&data, sizeof(uint64_t), 1, f) != 1) { + L("fread: %s", strerror(errno)); + PE(); + return NULL; + } + + if (fseek(f, sizeof(uint64_t), SEEK_SET) < 0) { + L("fseek: %s", strerror(errno)); + PE(); + return NULL; + } + + if (fread(&changes, sizeof(uint8_t), 1, f) != 1) { + L("fread: %s", strerror(errno)); + PE(); + return NULL; + } + fclose(f); + + hash->hash = data; + hash->changes = changes; + PE(); + return hash; +} + +int +write_hash(git_repository *repo, repohash hash) +{ + PS(); + 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)); + PE(); + return 1; + } + strcat(path, gitpath); + strcat(path, XD_HASH_PATH); + + f = fopen(path, "wb"); + if (!f) { + L("fopen: %s", strerror(errno)); + PE(); + return 1; + } + + if (fwrite(&hash.hash, sizeof(uint64_t), 1, f) != 1) { + L("fwrite: %s", strerror(errno)); + PE(); + return 1; + } + + if (fseek(f, sizeof(uint64_t), SEEK_SET) < 0) { + L("fseek: %s", strerror(errno)); + PE(); + return 1; + } + + if (fwrite(&hash.changes, sizeof(uint8_t), 1, f) != 1) { + L("fwrite: %s", strerror(errno)); + PE(); + return 1; + } + + fclose(f); + PE(); + 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..b106667 --- /dev/null +++ b/helpers.h @@ -0,0 +1,28 @@ +#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 + +#ifdef PERF +#define PS() long __start = clock() +#define PE() l("%s: %fs", __func__, ((double) (clock() - __start)) / CLOCKS_PER_SEC) +#else +#define PS() +#define PE() +#endif