Compare commits

...

8 commits

9 changed files with 328 additions and 49 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) $(PERF) $(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

94
XD.c
View file

@ -1,7 +1,9 @@
#include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h>
#ifdef ERR #ifdef ERR
#include <errno.h> #include <errno.h>
@ -16,42 +18,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
@ -61,6 +32,7 @@ static int explain = 0;
char char
*find_git_repo(void) *find_git_repo(void)
{ {
PS();
char path[PATH_MAX] = ".", fstr[PATH_MAX], *rpath, *res; char path[PATH_MAX] = ".", fstr[PATH_MAX], *rpath, *res;
struct stat s; struct stat s;
FILE *f; FILE *f;
@ -70,6 +42,7 @@ char
rpath = realpath(path, NULL); rpath = realpath(path, NULL);
if (!rpath) { if (!rpath) {
L("realpath: %s", strerror(errno)); L("realpath: %s", strerror(errno));
PE();
return NULL; return NULL;
} }
for (i = c = 0; i < strlen(rpath); i++) { 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 there seems to be a git directory return the directory it was found in */
if (stat(path, &s) == 0) { if (stat(path, &s) == 0) {
if (S_ISDIR(s.st_mode)) { if (S_ISDIR(s.st_mode)) {
PE();
return realpath(path, NULL); return realpath(path, NULL);
} else if (S_ISREG(s.st_mode)) { } else if (S_ISREG(s.st_mode)) {
/* we do some special magic here to check if we're in a submodule */ /* we do some special magic here to check if we're in a submodule */
f = fopen(path, "r"); f = fopen(path, "r");
if (!f) { if (!f) {
L("fopen: %s", strerror(errno)); L("fopen: %s", strerror(errno));
PE();
return NULL; return NULL;
} }
res = fgets(fstr, PATH_MAX, f); res = fgets(fstr, PATH_MAX, f);
fclose(f); fclose(f);
if (!res) { if (!res) {
L("fgets: %s", strerror(errno)); L("fgets: %s", strerror(errno));
PE();
return NULL; return NULL;
} }
if (strncmp(fstr, "gitdir: ", strlen("gitdir: ")) == 0) { if (strncmp(fstr, "gitdir: ", strlen("gitdir: ")) == 0) {
fstr[strlen(fstr) - 1] = '\0'; fstr[strlen(fstr) - 1] = '\0';
PE();
return realpath(fstr + strlen("gitdir: "), NULL); return realpath(fstr + strlen("gitdir: "), NULL);
} }
} }
@ -112,6 +89,7 @@ char
memset(&path[strlen(path) - 2], 0, 2); memset(&path[strlen(path) - 2], 0, 2);
} }
PE();
return NULL; return NULL;
} }
@ -123,11 +101,13 @@ char
git_repository git_repository
*init_git(void) *init_git(void)
{ {
PS();
char *buf; char *buf;
git_repository *repo; git_repository *repo;
/* check for a repo before loading libgit2 */ /* check for a repo before loading libgit2 */
if ((buf = find_git_repo()) == NULL) { if ((buf = find_git_repo()) == NULL) {
PE();
return NULL; return NULL;
} }
@ -143,17 +123,20 @@ git_repository
/* initialize the git library and repository */ /* initialize the git library and repository */
if (git_libgit2_init() < 0) { if (git_libgit2_init() < 0) {
L("Failed to initalize libgit2, proceeding without git functionality enabled."); L("Failed to initalize libgit2, proceeding without git functionality enabled.");
PE();
return NULL; return NULL;
} }
if (git_repository_open(&repo, buf) < 0) { if (git_repository_open(&repo, buf) < 0) {
L("Failed to open git repo: %s", git_error_last()->message); L("Failed to open git repo: %s", git_error_last()->message);
free(buf); free(buf);
PE();
return NULL; return NULL;
} }
/* get rid of object containing git repo path and return the repo */ /* get rid of object containing git repo path and return the repo */
free(buf); free(buf);
PE();
return repo; return repo;
} }
@ -166,20 +149,24 @@ git_repository
int int
has_stashes(git_repository *repo) has_stashes(git_repository *repo)
{ {
PS();
git_reference *stash = NULL; git_reference *stash = NULL;
int e; int e;
e = git_reference_lookup(&stash, repo, "refs/stash"); e = git_reference_lookup(&stash, repo, "refs/stash");
if (e == GIT_ENOTFOUND) { if (e == GIT_ENOTFOUND) {
PE();
return 0; return 0;
} else if (e < 0) { } else if (e < 0) {
L("Error looking up stash reference: %s", git_error_last()->message); L("Error looking up stash reference: %s", git_error_last()->message);
PE();
return 0; return 0;
} else { } else {
e = 1; e = 1;
} }
git_reference_free(stash); git_reference_free(stash);
PE();
return e; return e;
} }
@ -192,27 +179,45 @@ has_stashes(git_repository *repo)
int int
has_untracked(git_repository *repo) has_untracked(git_repository *repo)
{ {
PS();
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;
uint64_t newhash = generate_hash(repo);
int r = 0; int r = 0;
/* FIXME: this is really slow in large git repos :( */ #ifdef GITHASH
opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; 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 | opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
GIT_STATUS_OPT_UPDATE_INDEX |
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
if (git_status_list_new(&list, repo, &opts) < 0) { if (git_status_list_new(&list, repo, &opts) < 0) {
L("Error checking for untracked changes: %s", git_error_last()->message); L("Error checking for untracked changes: %s", git_error_last()->message);
PE();
return 0; return 0;
} }
/* 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 = newhash, .changes = r });
#endif
git_status_list_free(list); git_status_list_free(list);
PE();
return r; return r;
} }
@ -225,6 +230,7 @@ has_untracked(git_repository *repo)
int int
has_staged(git_repository *repo) has_staged(git_repository *repo)
{ {
PS();
git_status_entry entry; git_status_entry entry;
git_status_list *list = NULL; git_status_list *list = NULL;
git_status_options opts = GIT_STATUS_OPTIONS_INIT; 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) { if (git_status_list_new(&list, repo, &opts) < 0) {
L("Error checking for staged changes: %s", git_error_last()->message); L("Error checking for staged changes: %s", git_error_last()->message);
PE();
return 0; return 0;
} }
@ -253,6 +260,7 @@ has_staged(git_repository *repo)
} }
git_status_list_free(list); git_status_list_free(list);
PE();
return r; return r;
} }
#endif #endif
@ -260,17 +268,20 @@ has_staged(git_repository *repo)
inline unsigned inline unsigned
numcat(unsigned x, unsigned y) numcat(unsigned x, unsigned y)
{ {
PS();
unsigned pow = 10; unsigned pow = 10;
while(y >= pow) { while(y >= pow) {
pow *= 10; pow *= 10;
} }
PE();
return (x * pow) + y; return (x * pow) + y;
} }
int int
str_to_int(char *str) str_to_int(char *str)
{ {
PS();
int res = -1; int res = -1;
char *c; char *c;
@ -282,17 +293,20 @@ str_to_int(char *str)
} }
} }
PE();
return res; return res;
} }
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
PS();
int code = -1; int code = -1;
/* print version information */ /* print version information */
if (argc > 1 && strcmp(argv[1], "-v") == 0) { if (argc > 1 && strcmp(argv[1], "-v") == 0) {
printf("XD [number] %s\n", VERSION); printf("XD [number] %s\n", VERSION);
PE();
return 0; return 0;
#ifdef EXPLAIN #ifdef EXPLAIN
} else if (argc > 1 && strcmp(argv[1], "-e") == 0) { } 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]); code = str_to_int(argv[argc - 1]);
if (code < 0) { if (code < 0) {
L("Return code, %d, not valid", code); L("Return code, %d, not valid", code);
PE();
exit(1); exit(1);
} }
} }
@ -377,5 +392,6 @@ main(int argc, char *argv[])
P("|"); /* no code info */ P("|"); /* no code info */
} }
PE();
return code; return code;
} }

View file

@ -1,4 +1,4 @@
VERSION = `git describe --tags --abbrev=0` VERSION := `git describe --tags --dirty`
PKG_CONFIG = pkg-config PKG_CONFIG = pkg-config
@ -7,15 +7,21 @@ 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 =
# uncomment to enable errors # uncomment to enable errors
# ERR = -DERR # ERR = -DERR
PERF =
# uncomment to enable performance logging
# PERF = -DPERF
EXPLAIN = EXPLAIN =
# comment to disable explinations # comment to disable explinations
EXPLAIN = -DEXPLAIN EXPLAIN = -DEXPLAIN
@ -24,15 +30,25 @@ EXPLAIN = -DEXPLAIN
ifneq ($(GIT),) ifneq ($(GIT),)
VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)` VERSION := $(VERSION)"\\nlibgit2 "`$(PKG_CONFIG) --modversion $(GITLIB)`
endif endif
ifeq ($(ERR),) ifeq ($(GITHASH),)
VERSION := $(VERSION)"\\nerrors disabled" VERSION := $(VERSION)"\\ngit hashing disabled"
else else
VERSION := $(VERSION)"\\nerrors enabled" VERSION := $(VERSION)"\\ngit hashing enabled"
endif endif
ifeq ($(EXPLAIN),) ifeq ($(EXPLAIN),)
VERSION := $(VERSION)"\\nexplinations disabled" VERSION := $(VERSION)"\\nexplinations disabled"
else else
VERSION := $(VERSION)"\\nexplinations enabled" VERSION := $(VERSION)"\\nexplinations enabled"
endif 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 CC = cc

152
hash.c Normal file
View file

@ -0,0 +1,152 @@
#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 "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

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

28
helpers.h Normal file
View file

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