about summary refs log tree commit homepage
path: root/lib/PublicInbox/gcf2_libgit2.h
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox/gcf2_libgit2.h')
-rw-r--r--lib/PublicInbox/gcf2_libgit2.h139
1 files changed, 139 insertions, 0 deletions
diff --git a/lib/PublicInbox/gcf2_libgit2.h b/lib/PublicInbox/gcf2_libgit2.h
new file mode 100644
index 00000000..d9c79cf9
--- /dev/null
+++ b/lib/PublicInbox/gcf2_libgit2.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 all contributors <meta@public-inbox.org>
+ * License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+ *
+ * libgit2 for Inline::C
+ * Avoiding Git::Raw since it doesn't guarantee a stable API,
+ * while libgit2 itself seems reasonably stable.
+ */
+#include <git2.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <poll.h>
+
+static void croak_if_err(int rc, const char *msg)
+{
+        if (rc != GIT_OK) {
+                const git_error *e = giterr_last();
+
+                croak("%d %s (%s)", rc, msg, e ? e->message : "unknown");
+        }
+}
+
+SV *new()
+{
+        git_odb *odb;
+        SV *ref, *self;
+        int rc = git_odb_new(&odb);
+        croak_if_err(rc, "git_odb_new");
+
+        ref = newSViv((IV)odb);
+        self = newRV_noinc(ref);
+        sv_bless(self, gv_stashpv("PublicInbox::Gcf2", GV_ADD));
+        SvREADONLY_on(ref);
+
+        return self;
+}
+
+static git_odb *odb_ptr(SV *self)
+{
+        return (git_odb *)SvIV(SvRV(self));
+}
+
+void DESTROY(SV *self)
+{
+        git_odb_free(odb_ptr(self));
+}
+
+/* needs "$GIT_DIR/objects", not $GIT_DIR */
+void add_alternate(SV *self, const char *objects_path)
+{
+        int rc = git_odb_add_disk_alternate(odb_ptr(self), objects_path);
+        croak_if_err(rc, "git_odb_add_disk_alternate");
+}
+
+/* this requires an unabbreviated git OID */
+#define CAPA(v) (sizeof(v) / sizeof((v)[0]))
+void cat_oid(SV *self, int fd, SV *oidsv)
+{
+        /*
+         * adjust when libgit2 gets SHA-256 support, we return the
+         * same header as git-cat-file --batch "$OID $TYPE $SIZE\n"
+         */
+        char hdr[GIT_OID_HEXSZ + sizeof(" commit 18446744073709551615")];
+        struct iovec vec[3];
+        size_t nvec = CAPA(vec);
+        git_oid oid;
+        git_odb_object *object = NULL;
+        int rc, err = 0;
+        STRLEN oidlen;
+        char *oidptr = SvPV(oidsv, oidlen);
+
+        /* same trailer as git-cat-file --batch */
+        vec[2].iov_len = 1;
+        vec[2].iov_base = "\n";
+
+        rc = git_oid_fromstrn(&oid, oidptr, oidlen);
+        if (rc == GIT_OK)
+                rc = git_odb_read(&object, odb_ptr(self), &oid);
+        if (rc == GIT_OK) {
+                vec[0].iov_base = hdr;
+                vec[1].iov_base = (void *)git_odb_object_data(object);
+                vec[1].iov_len = git_odb_object_size(object);
+
+                git_oid_nfmt(hdr, GIT_OID_HEXSZ, git_odb_object_id(object));
+                vec[0].iov_len = GIT_OID_HEXSZ +
+                                snprintf(hdr + GIT_OID_HEXSZ,
+                                        sizeof(hdr) - GIT_OID_HEXSZ,
+                                        " %s %zu\n",
+                                        git_object_type2string(
+                                                git_odb_object_type(object)),
+                                        vec[1].iov_len);
+        } else {
+                vec[0].iov_base = oidptr;
+                vec[0].iov_len = oidlen;
+                vec[1].iov_base = " missing";
+                vec[1].iov_len = strlen(vec[1].iov_base);
+        }
+        while (nvec && !err) {
+                ssize_t w = writev(fd, vec + CAPA(vec) - nvec, nvec);
+
+                if (w > 0) {
+                        size_t done = 0;
+                        size_t i;
+
+                        for (i = CAPA(vec) - nvec; i < CAPA(vec); i++) {
+                                if (w >= vec[i].iov_len) {
+                                        /* fully written vec */
+                                        w -= vec[i].iov_len;
+                                        done++;
+                                } else { /* partially written vec */
+                                        char *p = vec[i].iov_base;
+                                        vec[i].iov_base = p + w;
+                                        vec[i].iov_len -= w;
+                                        break;
+                                }
+                        }
+                        nvec -= done;
+                } else if (w < 0) {
+                        err = errno;
+                        switch (err) {
+                        case EAGAIN: {
+                                struct pollfd pfd;
+                                pfd.events = POLLOUT;
+                                pfd.fd = fd;
+                                poll(&pfd, 1, -1);
+                        }
+                                /* fall-through */
+                        case EINTR:
+                                err = 0;
+                        }
+                } else { /* w == 0 */
+                        err = ENOSPC;
+                }
+        }
+        if (object)
+                git_odb_object_free(object);
+        if (err)
+                croak("writev error: %s", strerror(err));
+}