shithub: libgit

ref: 485586299c3f6bb71a58ab5348210ca40ee9e8d2
dir: /git.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include "git.h"

char *Enoinit = "not initialized";

//#define DEBUG

char *gitdir = "/mnt/git";
int initialized = 0;
char *repodir = nil;
char *indexfile = nil;

enum {
	FAIL = 0,
	SUCCESS = 1,
};

enum {
	ADD,
	BRANCH,
	COMMIT,
	DIFF,
	LOG,
	RM,
};

char *cmds[] = {
	[ADD] nil,
	[BRANCH] nil,
	[COMMIT] "/bin/git/commit",
	[DIFF] nil,
	[LOG] "/bin/git/log",
	[RM] nil,
};

char *cname[] = {
	[ADD] nil,
	[BRANCH] nil,
	[COMMIT] "commit",
	[DIFF] nil,
	[LOG] "log",
	[RM] nil,
};

static int
isinitialized(void)
{
	return initialized;
}
#define checkinitialized() if (!isinitialized()){ werrstr(Enoinit); return FAIL; }

int
initgit(char *dir)
{
	if (initialized) {
		werrstr("already initialized");
		return FAIL;
	}
	
	repodir = strdup(dir);
	indexfile = smprint("%s/.git/INDEX9", dir);
	if (!repodir || !indexfile) {
		if (repodir) free(repodir);
		if (indexfile) free(indexfile);
		return FAIL;
	}
	
	switch (fork()) {
	case 0: /* child */
		if (chdir(dir) < 0)
			sysfatal("unable to chdir: %r");
		execl("/bin/git/fs", "fs", "-m", gitdir, nil);
		sysfatal("unable to exec: %r");
		break;
	case -1: /* error */
		werrstr("unable to fork: %r");
		return FAIL;
		break;
	default: /* parent */
		break;
	}
	initialized = 1;
	return SUCCESS;
}

/* args[0] reserved for argv0 */
static int
gitcmd(int cmd, char **args, void (*f)(char *line, int n, void *aux), void *aux)
{
	int pid;
	char *c;
	Waitmsg *wmsg;
	int p[2];
	Biobuf *bin;
	char *s;
	
	c = cmds[cmd];
	if (!c) {
		werrstr("not implemented");
		return FAIL;
	}
	args[0] = cname[cmd];
	
	if (pipe(p) < 0) {
		werrstr("gitcmd: %r");
		return FAIL;
	}
	
	// debug output
#ifdef DEBUG
	fprint(2, "command: %s", c);
	for (char **a = args; *a; a++)
		fprint(2, " '%s'", *a);
	fprint(2, "\n");
#endif
	
	switch (pid = fork()) {
	case 0: /* child */
		if (chdir(repodir) < 0)
			sysfatal("%r");
		
		dup(p[1], 1);
		close(p[1]);
		close(p[0]);
		
		exec(c, args);
		free(args);
		break;
	case -1: /* error */
		werrstr("unable to fork: %r");
		return FAIL;
		break;
	default: /* parent */
		free(args); // TODO crashes on gitlog
		break;
	}
	
	close(p[1]);
	bin = Bfdopen(p[0], OREAD);
	while (s = Brdstr(bin, '\n', 1)) {
		if (f)
			f(s, Blinelen(bin), aux);
	}
	Bterm(bin);
	
	for (;;) {
		wmsg = wait();
		if (wmsg->pid == pid)
			break;
	}
	
	if (wmsg->msg && *wmsg->msg) {
		werrstr("%s", wmsg->msg);
		free(wmsg);
		return FAIL;
	}
	if (wmsg) free(wmsg);
	return SUCCESS;
}

int
gitcommitl(char *msg, char **files)
{
	char **args;
	char **f;
	int n, i;
	
	checkinitialized();
	
	for (f = files, n = 0; *f; f++)
		n++;
	
	args = mallocz((n + 1 + 3) * sizeof(char*), 1);
	
	i = 0;
	args[i++] = nil;
	args[i++] = "-m";
	args[i++] = msg;
	
	memcpy(&args[i], files, n * sizeof(char*));
	
	i = gitcmd(COMMIT, args, nil, nil);
	
	return i;
}

static void
filllist(char ***list, int start, int len, va_list args)
{
	char *f;
	
	if (*list == nil) {
		start = 0;
		len = 32;
		*list = mallocz(len * sizeof(char*), 1);
		if (*list == nil)
			sysfatal("%r");
	}
	
	while (f = va_arg(args, char*)) {
		if (start > (len - 2)) {
			len += 32;
			*list = realloc(*list, len * sizeof(char*));
			if (*list == nil)
				sysfatal("%r");
			
			(*list)[start++] = f;
		}
	}
	(*list)[start] = nil;
}

int
gitcommit(char *msg, char *file, ...)
{
	va_list args;
	char **files;
	int n, l;

	checkinitialized();
	
	n = 0;
	l = 32;
	
	files = mallocz(l, 1);
	files[n++] = file;
	
	va_start(args, file);
	filllist(&files, n, l, args);
	va_end(args);
	
	files[n] = nil;
	
	return gitcommitl(msg, files);
}

static int
gitaddrm(char *F, char **files)
{
	char **f;
	int fd;
	
	fd = open(indexfile, OWRITE);
	if (fd < 0)
		return FAIL;
	seek(fd, 0, 2);
	
	for (f = files; *f; f++) {
		fprint(fd, "%s NOQID 0 %s\n", F, *f);
	}
	
	close(fd);
	return SUCCESS;
}

static int
gitaddrmargs(char *F, char *file, va_list args)
{
	char **files;
	int i, l;
	
	l = 32;
	i = 0;
	files = mallocz(l * sizeof(char*), 1);
	
	files[i++] = file;
	filllist(&files, i, l, args);
	va_end(args);
	
	i = gitaddrm(F, files);
	free(files);
	return i;
}

int
gitaddl(char **files)
{
	checkinitialized();
	return gitaddrm("A", files);
}

int
gitadd(char *file, ...)
{
	va_list args;

	checkinitialized();
	
	va_start(args, file);
	return gitaddrmargs("A", file, args);
}

int
gitrml(char **files)
{
	checkinitialized();
	return gitaddrm("R", files);
}

int
gitrm(char *file, ...)
{
	va_list args;

	checkinitialized();
	
	va_start(args, file);
	return gitaddrmargs("R", file, args);
}

typedef struct Logparams Logparams;
struct Logparams {
	Gitlog *logs;
	int n;
	int last;
};

static void
gitlogline(char *s, int n, void *aux)
{
	char *toks[2];
	Gitlog *l;
	Logparams *p = (Logparams *)aux;
	char *o;
	
	if (n > 5 && strncmp("Hash:", s, 5) == 0) {
		/* new entry */
		p->last++;
		l = &p->logs[p->last];
		
		tokenize(s, toks, 2);
		l->hash = strdup(toks[1]);
		free(s);
		return;
	}
	
	if (n > 7 && strncmp("Author:", s, 7) == 0) {
		l = &p->logs[p->last];
		
		tokenize(s, toks, 2);
		l->author = strdup(toks[1]);
		free(s);
		return;
	}
	
	if (n > 5 && strncmp("Date:", s, 5) == 0) {
		l = &p->logs[p->last];
		
		tokenize(s, toks, 2);
		l->date = strdup(toks[1]);
		free(s);
		return;
	}
	
	if (n >= 1 && s[0] == '\t') {
		l = &p->logs[p->last];
		
		if (!l->message) {
			l->message = strdup(s+1);
			free(s);
			return;
		}
		
		o = l->message;
		l->message = mallocz(strlen(o) + strlen(s+1) + 2, 1); // + newline + nil
		sprint(l->message, "%s\n%s", o, s+1);
		
		free(o);
		free(s);
		return;
	}
}

int
gitlogl(Gitlog **logs, int n, char *commit, char **files)
{
	Logparams p;
	char num[5];
	char **argv;
	int nfiles;
	int l, i;
	
	checkinitialized();
	
	if (n <= 0) {
		sysfatal("gitlog: n <= 0");
	}
	
	if (!logs) {
		sysfatal("No gitlog target pointer");
	}
	
	*logs = mallocz((n+1) * sizeof(Gitlog), 1);
	if (!*logs) {
		sysfatal("%r");
	}
	
	p.logs = *logs;
	p.n = n;
	p.last = -1;
	
	for (argv = files, nfiles = 0; *argv; argv++)
		nfiles++;
	
	l = nfiles;
	l += 2; /* ending nil, starting argv0 */
	l += 2; /* num args */
	if (commit)
		l += 2;
	argv = mallocz(l * sizeof(char*), 1);
	
	i = 1; /* reserve [0] for argv0 */
	
	snprint(num, sizeof(num), "%d", n);
	argv[i++] = "-n";
	argv[i++] = num;
	
	if (commit) {
		argv[i++] = "-c";
		argv[i++] = commit;
	}
	
	memcpy(&argv[i], files, nfiles * sizeof(char*));
	return gitcmd(LOG, argv, gitlogline, &p);
}

int
gitlog(Gitlog **logs, int n, char *commit, ...)
{
	va_list args;
	char **files = nil;
	int ret;
	
	checkinitialized();
	
	va_start(args, commit);
	filllist(&files, 0, 0, args);
	va_end(args);
	
	ret = gitlogl(logs, n, commit, files);
	free(files);
	
	return ret;
}

int
freegitlogs(Gitlog *logs)
{
	for (Gitlog *l = logs; l->hash; l++) {
		if (l->hash) free(l->hash);
		if (l->author) free(l->author);
		if (l->date) free(l->date);
		if (l->message) free(l->message);
	}
	free(logs);
	return SUCCESS;
}

#ifdef DEBUG
	#undef DEBUG
#endif