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

9
XD.1
View file

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

94
XD.c
View file

@ -1,7 +1,9 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef ERR
#include <errno.h>
@ -16,42 +18,11 @@
#include <git2.h>
#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;
}

View file

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

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