shithub: libgit

Download patch

ref: 3334f29a9e020b0447d81ac4afc8e1b31452276f
parent: 073882f6a2a8c065375b94dc77c59eb7d8b2091f
author: sirjofri <[email protected]>
date: Sat Jun 15 15:22:46 EDT 2024

adds man page (README), fixes gitlog

--- a/README
+++ b/README
@@ -1,35 +1,97 @@
-libgit
 
-COMMANDS
+     GIT(2)                                                     GIT(2)
 
-/* initialize git system */
-int initgit(char* dir);
+     NAME
+          Initgit, gitcommit, gitadd, gitrm, gitlog, Gitlog - git C
+          library
 
-/* git commands. files must be relative within repository. The last
-   name must be nil. */
-int gitcommit(char *msg, char* file, ...);
-int gitadd(char* file, ...);
-int gitrm(char* file, ...);
+     SYNOPSIS
+          #include <u.h>
+          #include <libc.h>
+          #include <git.h>
 
+          typedef struct Gitlog
+          {
+               char*hash;
+               char*author;
+               char*date;
+               char*message;
+          } Gitlog;
 
-NOTES
+          int initgit(char *dir)
+          int freegitlogs(Gitlog *logs)
 
-gitrm and gitadd add files directly.
-gitcommit calls "/bin/git/commit" under the hood.
+          int gitcommit(char *msg, char *file, ...)
+          int gitadd(char *file, ...)
+          int gitrm(char *file, ...)
+          int gitlog(Gitlog **logs, int n, char *commit, ...)
 
-initgit is called like that to allow for a future gitinit command
-for initializing new repos.
+     DESCRIPTION
+          Libgit is a wrapper around git9. It does not provide its own
+          git functionality.
 
+          Git functions that receive a list of files expect the last
+          element to be nil.  All the file names are expected to be
+          relative to the repository root directory.
 
-EXAMPLE PROGRAM
+          Initgit initializes libgit with an existing repository at
+          location dir.  This will use git(1) to start the backend
+          filesystem.
 
-void
-main(int argc, char **argv)
-{
-	if (!initgit("/path/to/my/repo"))
-		sysfatal("%r");
-	
-	gitadd("relative/path/to/file", "file2", "file3", nil);
-	gitrm("relative/path/to/filerm", "filerm2", "filerm3", nil);
-	gitcommit("commit message", "relative/path/to/file", "file2", "file3", "relative/path/to/filerm", "filerm2", filerm3", nil);
-}
+          Gitadd and gitrm add and remove files from the git file
+          tracking system by adjusting the INDEX9 file directly.
+
+          Gitcommit will use git(1) to commit the numbered list of
+          files.  Arguments are forwarded unchanged, so it should be
+          possible to use wildcards like . or *.  Msg will be used as
+          the commit message; it can be multiple lines.
+
+          Gitlog allocates the pointer pointed to by logs with as many
+          Gitlog structs as needed, plus an additional sentinel Gitlog
+          structure.  It will fill this array with the last n commits
+          starting from commit or HEAD if commit is nil.  The remain-
+          ing arguments can be a list of files used for filtering,
+          ending with nil.
+
+          The last Gitlog structure in the initialized array acts as a
+          sentinel and has its hash member set to nil.  The array and
+          all its strings are allocated with malloc and should be
+          freed after using.
+
+          Freegitlogs can be used for that, which is recommended for
+          future updates.
+
+     EXAMPLE
+               Gitlogs *logs;
+               if (!initgit("/path/to/repo"))
+                   sysfatal("%r");
+               gitadd("addfileA", "addfileB", nil);
+               gitrm("rmfileA", "rmfileB", nil);
+               gitcommit("adds and removes files\n\nfor demonstration only",
+                   "addfileA", "addfileB", "rmfileA", "rmfileB", nil);
+               gitlog(&logs, 2, nil, nil);
+               for (Gitlog *l = logs; l->hash; l++) {
+                   print("hash:   %s\n", l->hash);
+                   print("author: %s\n", l->author);
+                   print("date:   %s\n", l->date);
+                   print("msg:    %s\n", l->message);
+               }
+               freegitlogs(logs);
+
+     SOURCE
+          /sys/src/libgit/git.c
+
+     SEE ALSO
+          git(1)
+
+     DIAGNOSTICS
+          All commands return 1 or 0 and set errstr.
+
+     BUGS
+          Sure.
+
+          There should be a collection of functions that receive an
+          array of files instead of a va_list.
+
+          Libgit is incomplete.
+
--- /dev/null
+++ b/git.2
@@ -1,0 +1,137 @@
+.TH GIT 2
+.SH NAME
+Initgit, gitcommit, gitadd, gitrm, gitlog, Gitlog \- git C library
+.SH SYNOPSIS
+.ft L
+.nf
+#include <u.h>
+#include <libc.h>
+#include <git.h>
+.fi
+.PP
+.ft L
+.nf
+.ta \w'\fLFile 'u
+typedef struct Gitlog
+{
+	char	*hash;
+	char	*author;
+	char	*date;
+	char	*message;
+} Gitlog;
+.fi
+.PP
+.ft L
+.nf
+.ta \w'\fLint 'u +4n +4n
+int	initgit(char *dir)
+int	freegitlogs(Gitlog *logs)
+.fi
+.PP
+.ft L
+.nf
+.ta \w'\fLint 'u +4n +4n
+int	gitcommit(char *msg, char *file, ...)
+int	gitadd(char *file, ...)
+int	gitrm(char *file, ...)
+int	gitlog(Gitlog **logs, int n, char *commit, ...)
+.fi
+.SH DESCRIPTION
+.I Libgit
+is a wrapper around git9. It does
+.I not
+provide its own git functionality.
+.PP
+Git functions that receive a list of files expect the last element to be
+.BR nil .
+All the file names are expected to be relative to the repository root directory.
+.PP
+.B Initgit
+initializes libgit with an existing repository at location
+.BR dir .
+This will use
+.IR git (1)
+to start the backend filesystem.
+.PP
+.B Gitadd
+and
+.B gitrm
+add and remove files from the git file tracking system by adjusting the
+.I INDEX9
+file directly.
+.PP
+.B Gitcommit
+will use
+.IR git (1)
+to commit the numbered list of files.
+Arguments are forwarded unchanged, so it should be possible to use wildcards like
+.B .
+or
+.BR * .
+.B Msg
+will be used as the commit message; it can be multiple lines.
+.PP
+.B Gitlog
+allocates the pointer pointed to by
+.B logs
+with as many
+.B Gitlog
+structs as needed, plus an additional sentinel Gitlog structure.
+It will fill this array with the last
+.B n
+commits starting from
+.B commit
+or
+.I HEAD
+if
+.B commit
+is
+.BR nil .
+The remaining arguments can be a list of files used for filtering, ending with
+.BR nil .
+.PP
+The last
+.B Gitlog
+structure in the initialized array acts as a sentinel and has its
+.B hash
+member set to
+.BR nil .
+The array and all its strings are allocated with
+.B malloc
+and should be freed after using.
+.PP
+.B Freegitlogs
+can be used for that, which is recommended for future updates.
+.SH EXAMPLE
+.IP
+.EX
+Gitlogs *logs;
+if (!initgit("/path/to/repo"))
+	sysfatal("%r");
+gitadd("addfileA", "addfileB", nil);
+gitrm("rmfileA", "rmfileB", nil);
+gitcommit("adds and removes files\\n\\nfor demonstration only",
+	"addfileA", "addfileB", "rmfileA", "rmfileB", nil);
+gitlog(&logs, 2, nil, nil);
+for (Gitlog *l = logs; l->hash; l++) {
+	print("hash:   %s\\n", l->hash);
+	print("author: %s\\n", l->author);
+	print("date:   %s\\n", l->date);
+	print("msg:    %s\\n", l->message);
+}
+freegitlogs(logs);
+.EE
+.SH SOURCE
+.B /sys/src/libgit/git.c
+.SH SEE ALSO
+.IR git (1)
+.SH DIAGNOSTICS
+All commands return 1 or 0 and set errstr.
+.SH BUGS
+Sure.
+.PP
+There should be a collection of functions that receive an array of files instead of a
+.BR va_list .
+.PP
+.I Libgit
+is incomplete.
--- a/git.c
+++ b/git.c
@@ -1,9 +1,12 @@
 #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;
@@ -28,7 +31,7 @@
 	[BRANCH] nil,
 	[COMMIT] "/bin/git/commit",
 	[DIFF] nil,
-	[LOG] nil,
+	[LOG] "/bin/git/log",
 	[RM] nil,
 };
 
@@ -37,7 +40,7 @@
 	[BRANCH] nil,
 	[COMMIT] "commit",
 	[DIFF] nil,
-	[LOG] nil,
+	[LOG] "log",
 	[RM] nil,
 };
 
@@ -84,11 +87,14 @@
 
 /* args[0] reserved for argv0 */
 static int
-gitcmd(int cmd, char **args)
+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) {
@@ -97,15 +103,28 @@
 	}
 	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 */
@@ -116,6 +135,14 @@
 		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)
@@ -151,7 +178,7 @@
 		files[nfile++] = f;
 		if (nfile >= sizeof(files)) {
 			files[nfile] = nil;
-			if (!gitcmd(COMMIT, files))
+			if (!gitcmd(COMMIT, files, nil, nil))
 				return FAIL;
 			nfile = 3;
 		}
@@ -160,7 +187,7 @@
 	
 	if (nfile > 3) {
 		files[nfile] = nil;
-		if (!gitcmd(COMMIT, files))
+		if (!gitcmd(COMMIT, files, nil, nil))
 			return FAIL;
 	}
 	
@@ -218,3 +245,145 @@
 	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
--- a/git.h
+++ b/git.h
@@ -1,11 +1,21 @@
 #pragma lib "libgit.a"
 #pragma src "/sys/src/libgit"
 
+typedef struct Gitlog Gitlog;
+struct Gitlog {
+	char *hash; /* if nil, end of array */
+	char *author;
+	char *date;
+	char *message;
+};
+
 /* initialize git system */
 int initgit(char* dir);
+int freegitlogs(Gitlog *logs);
 
 /* git commands. files must be relative within repository. The last
    name must be nil. */
-int gitcommit(char *msg, char* file, ...);
-int gitadd(char* file, ...);
-int gitrm(char* file, ...);
+int gitcommit(char *msg, char *file, ...);
+int gitadd(char *file, ...);
+int gitrm(char *file, ...);
+int gitlog(Gitlog **logs, int n, char *commit, ...);
--- a/test/gtest.c
+++ b/test/gtest.c
@@ -5,7 +5,11 @@
 char *dir = "/tmp/repo";
 char *addfile = "test";
 
-#define test(A, B) if (!A) { sysfatal("%s: %r\n", B); }
+#define test(A, B) if (!A) { \
+	sysfatal("× %s: %r\n", B); \
+} else { \
+	print("√ %s\n", B); \
+}
 
 void
 main(void)
@@ -12,6 +16,7 @@
 {
 	int fd;
 	char buf[128];
+	Gitlog *logs;
 	
 	if (!initgit("/tmp/repo"))
 		sysfatal("gitinit: %r");
@@ -24,8 +29,21 @@
 	fprint(fd, "hello world\n");
 	close(fd);
 	
-	test(gitadd(addfile, nil), "add file");
-	test(gitcommit("add file", addfile, nil), "commit add");
-	test(gitrm(addfile, nil), "rm file");
-	test(gitcommit("remove file", addfile, nil), "commit rm");
+	test(gitadd(addfile, nil), "git add file");
+	test(gitcommit("add file\n\nsecond line\nthird line", addfile, nil), "git commit add");
+	test(gitrm(addfile, nil), "git rm file");
+	test(gitcommit("remove file", addfile, nil), "git commit rm");
+	test(gitlog(&logs, 2, nil, nil), "git log 2");
+	
+	print("PRINT LOGS:\n");
+	for (Gitlog *l = logs; l->hash; l++) {
+		print("hash:   %s\n", l->hash);
+		print("author: %s\n", l->author);
+		print("date:   %s\n", l->date);
+		print("msg:    %s\n-\n", l->message);
+	}
+	print("END PRINT LOGS\n");
+	
+	test(freegitlogs(logs), "free gitlogs");
+	logs = nil;
 }
--- a/test/test.rc
+++ b/test/test.rc
@@ -14,7 +14,7 @@
 
 echo testfile > beforetest
 git/add beforetest
-git/commit -m init beforetest
+git/commit -m 'init' beforetest
 
 cat <<EOF >.git/config
 [user]
@@ -22,17 +22,7 @@
 	email = testmail
 EOF
 
-cat .git/INDEX9
-
 echo '========== run test ============'
 $t
 echo '========== end test ============'
-
-echo git/log: should contain "remove file" and "add file" commits
-git/log
-echo ''
-echo INDEX9: should contain "R NOQID 0 test" line
-cat .git/INDEX9
-echo walk
-walk
 rc -i