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