diff options
Diffstat (limited to 'lib/PublicInbox')
-rw-r--r-- | lib/PublicInbox/Gcf2.pm | 56 | ||||
-rw-r--r-- | lib/PublicInbox/gcf2_libgit2.h | 139 |
2 files changed, 195 insertions, 0 deletions
diff --git a/lib/PublicInbox/Gcf2.pm b/lib/PublicInbox/Gcf2.pm new file mode 100644 index 00000000..6ac3aa18 --- /dev/null +++ b/lib/PublicInbox/Gcf2.pm @@ -0,0 +1,56 @@ +# Copyright (C) 2020 all contributors <meta@public-inbox.org> +# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt> +# +# git-cat-file based on libgit2 +package PublicInbox::Gcf2; +use strict; +use PublicInbox::Spawn qw(which popen_rd); +use Fcntl qw(LOCK_EX); +my (%CFG, $c_src, $lockfh); +BEGIN { + # PublicInbox::Spawn will set PERL_INLINE_DIRECTORY + # to ~/.cache/public-inbox/inline-c if it exists + my $inline_dir = $ENV{PERL_INLINE_DIRECTORY} // + die 'PERL_INLINE_DIRECTORY not defined'; + my $f = "$inline_dir/.public-inbox.lock"; + open $lockfh, '>', $f or die "failed to open $f: $!\n"; + my $pc = which($ENV{PKG_CONFIG} // 'pkg-config'); + my ($dir) = (__FILE__ =~ m!\A(.+?)/[^/]+\z!); + my $rdr = {}; + open $rdr->{2}, '>', '/dev/null' or die "open /dev/null: $!"; + for my $x (qw(libgit2)) { + my $l = popen_rd([$pc, '--libs', $x], undef, $rdr); + $l = do { local $/; <$l> }; + next if $?; + my $c = popen_rd([$pc, '--cflags', $x], undef, $rdr); + $c = do { local $/; <$c> }; + next if $?; + + # note: we name C source files .h to prevent + # ExtUtils::MakeMaker from automatically trying to + # build them. + my $f = "$dir/gcf2_$x.h"; + if (open(my $fh, '<', $f)) { + chomp($l, $c); + local $/; + $c_src = <$fh>; + $CFG{LIBS} = $l; + $CFG{CCFLAGSEX} = $c; + last; + } else { + die "E: $f: $!\n"; + } + } + die "E: libgit2 not installed\n" unless $c_src; + + # CentOS 7.x ships Inline 0.53, 0.64+ has built-in locking + flock($lockfh, LOCK_EX) or die "LOCK_EX failed on $f: $!\n"; +} + +# we use Capitalized and ALLCAPS for compatibility with old Inline::C +use Inline C => Config => %CFG, BOOT => 'git_libgit2_init();'; +use Inline C => $c_src; +undef $c_src; +undef %CFG; +undef $lockfh; +1; 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)); +} |