]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/gcf2_libgit2.h
gcf2: libgit2-based git cat-file alternative
[public-inbox.git] / lib / PublicInbox / gcf2_libgit2.h
1 /*
2  * Copyright (C) 2020 all contributors <meta@public-inbox.org>
3  * License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4  *
5  * libgit2 for Inline::C
6  * Avoiding Git::Raw since it doesn't guarantee a stable API,
7  * while libgit2 itself seems reasonably stable.
8  */
9 #include <git2.h>
10 #include <sys/uio.h>
11 #include <errno.h>
12 #include <poll.h>
13
14 static void croak_if_err(int rc, const char *msg)
15 {
16         if (rc != GIT_OK) {
17                 const git_error *e = giterr_last();
18
19                 croak("%d %s (%s)", rc, msg, e ? e->message : "unknown");
20         }
21 }
22
23 SV *new()
24 {
25         git_odb *odb;
26         SV *ref, *self;
27         int rc = git_odb_new(&odb);
28         croak_if_err(rc, "git_odb_new");
29
30         ref = newSViv((IV)odb);
31         self = newRV_noinc(ref);
32         sv_bless(self, gv_stashpv("PublicInbox::Gcf2", GV_ADD));
33         SvREADONLY_on(ref);
34
35         return self;
36 }
37
38 static git_odb *odb_ptr(SV *self)
39 {
40         return (git_odb *)SvIV(SvRV(self));
41 }
42
43 void DESTROY(SV *self)
44 {
45         git_odb_free(odb_ptr(self));
46 }
47
48 /* needs "$GIT_DIR/objects", not $GIT_DIR */
49 void add_alternate(SV *self, const char *objects_path)
50 {
51         int rc = git_odb_add_disk_alternate(odb_ptr(self), objects_path);
52         croak_if_err(rc, "git_odb_add_disk_alternate");
53 }
54
55 /* this requires an unabbreviated git OID */
56 #define CAPA(v) (sizeof(v) / sizeof((v)[0]))
57 void cat_oid(SV *self, int fd, SV *oidsv)
58 {
59         /*
60          * adjust when libgit2 gets SHA-256 support, we return the
61          * same header as git-cat-file --batch "$OID $TYPE $SIZE\n"
62          */
63         char hdr[GIT_OID_HEXSZ + sizeof(" commit 18446744073709551615")];
64         struct iovec vec[3];
65         size_t nvec = CAPA(vec);
66         git_oid oid;
67         git_odb_object *object = NULL;
68         int rc, err = 0;
69         STRLEN oidlen;
70         char *oidptr = SvPV(oidsv, oidlen);
71
72         /* same trailer as git-cat-file --batch */
73         vec[2].iov_len = 1;
74         vec[2].iov_base = "\n";
75
76         rc = git_oid_fromstrn(&oid, oidptr, oidlen);
77         if (rc == GIT_OK)
78                 rc = git_odb_read(&object, odb_ptr(self), &oid);
79         if (rc == GIT_OK) {
80                 vec[0].iov_base = hdr;
81                 vec[1].iov_base = (void *)git_odb_object_data(object);
82                 vec[1].iov_len = git_odb_object_size(object);
83
84                 git_oid_nfmt(hdr, GIT_OID_HEXSZ, git_odb_object_id(object));
85                 vec[0].iov_len = GIT_OID_HEXSZ +
86                                 snprintf(hdr + GIT_OID_HEXSZ,
87                                         sizeof(hdr) - GIT_OID_HEXSZ,
88                                         " %s %zu\n",
89                                         git_object_type2string(
90                                                 git_odb_object_type(object)),
91                                         vec[1].iov_len);
92         } else {
93                 vec[0].iov_base = oidptr;
94                 vec[0].iov_len = oidlen;
95                 vec[1].iov_base = " missing";
96                 vec[1].iov_len = strlen(vec[1].iov_base);
97         }
98         while (nvec && !err) {
99                 ssize_t w = writev(fd, vec + CAPA(vec) - nvec, nvec);
100
101                 if (w > 0) {
102                         size_t done = 0;
103                         size_t i;
104
105                         for (i = CAPA(vec) - nvec; i < CAPA(vec); i++) {
106                                 if (w >= vec[i].iov_len) {
107                                         /* fully written vec */
108                                         w -= vec[i].iov_len;
109                                         done++;
110                                 } else { /* partially written vec */
111                                         char *p = vec[i].iov_base;
112                                         vec[i].iov_base = p + w;
113                                         vec[i].iov_len -= w;
114                                         break;
115                                 }
116                         }
117                         nvec -= done;
118                 } else if (w < 0) {
119                         err = errno;
120                         switch (err) {
121                         case EAGAIN: {
122                                 struct pollfd pfd;
123                                 pfd.events = POLLOUT;
124                                 pfd.fd = fd;
125                                 poll(&pfd, 1, -1);
126                         }
127                                 /* fall-through */
128                         case EINTR:
129                                 err = 0;
130                         }
131                 } else { /* w == 0 */
132                         err = ENOSPC;
133                 }
134         }
135         if (object)
136                 git_odb_object_free(object);
137         if (err)
138                 croak("writev error: %s", strerror(err));
139 }