shithub: libgit

ref: 3334f29a9e020b0447d81ac4afc8e1b31452276f
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);
		break;
	case -1: /* error */
		werrstr("unable to fork: %r");
		return FAIL;
		break;
	default: /* parent */
		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
gitcommit(char *msg, char *file, ...)
{
	va_list args;
	char *files[32];
	char *f;
	int nfile = 1;

	checkinitialized();
	
	files[nfile++] = "-m";
	files[nfile++] = msg;
	
	files[nfile++] = file;
	
	va_start(args, file);
	while (f = va_arg(args, char*)) {
		files[nfile++] = f;
		if (nfile >= sizeof(files)) {
			files[nfile] = nil;
			if (!gitcmd(COMMIT, files, nil, nil))
				return FAIL;
			nfile = 3;
		}
	}
	va_end(args);
	
	if (nfile > 3) {
		files[nfile] = nil;
		if (!gitcmd(COMMIT, files, nil, nil))
			return FAIL;
	}
	
	return SUCCESS;
}

static int
gitaddrm(char *F, char *file, va_list args)
{
	char *ofile;
	int fd;
	
	ofile = strdup(file);
	file = cleanname(ofile);
	
	fd = open(indexfile, OWRITE);
	if (fd < 0)
		return FAIL;
	seek(fd, 0, 2);
	
	fprint(fd, "%s NOQID 0 %s\n", F, file);
	free(ofile);
	
	while ((ofile = va_arg(args, char*)) != nil) {
		ofile = strdup(ofile);
		file = cleanname(ofile);
		
		fprint(fd, "%s NOQID 0 %s\n", F, file);
		free(ofile);
	}
	
	va_end(args);
	close(fd);
	return SUCCESS;
}

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

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

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

	checkinitialized();
	
	va_start(args, file);
	return gitaddrm("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
gitlog(Gitlog **logs, int n, char *commit, ...)
{
	va_list args;
	char *file;
	char *files[64];
	char num[5];
	int ret;
	int nfile;
	Logparams p;
	
	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;
	
	nfile = 1;
	
	snprint(num, sizeof(num), "%d", n);
	files[nfile++] = "-n";
	files[nfile++] = num;
	
	if (commit) {
		files[nfile++] = "-c";
		files[nfile++] = commit;
	}
	
	va_start(args, commit);
	while (file = va_arg(args, char*)) {
		files[nfile++] = file;
		if (nfile >= sizeof(files)) {
			goto Toomanyfiles;
		}
	}
	va_end(args);
	
	files[nfile] = nil;
	ret = gitcmd(LOG, files, gitlogline, &p);
	
	if (!ret)
		return FAIL;
	
	return ret;

Toomanyfiles:
	va_end(args);
	werrstr("too many files in log command");
	return FAIL;
}

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