+sub my_read ($$$) {
+ my ($fh, $rbuf, $len) = @_;
+ my $left = $len - length($$rbuf);
+ my $r;
+ while ($left > 0) {
+ $r = sysread($fh, $$rbuf, $PIPE_BUFSIZ, length($$rbuf));
+ if ($r) {
+ $left -= $r;
+ } else {
+ next if (!defined($r) && $! == EINTR);
+ return $r;
+ }
+ }
+ \substr($$rbuf, 0, $len, '');
+}
+
+sub my_readline ($$) {
+ my ($fh, $rbuf) = @_;
+ while (1) {
+ if ((my $n = index($$rbuf, "\n")) >= 0) {
+ return substr($$rbuf, 0, $n + 1, '');
+ }
+ my $r = sysread($fh, $$rbuf, $PIPE_BUFSIZ, length($$rbuf));
+ next if $r || (!defined($r) && $! == EINTR);
+ return defined($r) ? '' : undef; # EOF or error
+ }
+}
+
+sub cat_async_step ($$) {
+ my ($self, $inflight) = @_;
+ die 'BUG: inflight empty or odd' if scalar(@$inflight) < 2;
+ my ($cb, $arg) = splice(@$inflight, 0, 2);
+ my $head = my_readline($self->{in}, $self->{'--batch'});
+ $head =~ / missing$/ and return
+ eval { $cb->(undef, undef, undef, undef, $arg) };
+
+ $head =~ /^([0-9a-f]{40}) (\S+) ([0-9]+)$/ or
+ fail($self, "Unexpected result from async git cat-file: $head");
+ my ($oid_hex, $type, $size) = ($1, $2, $3 + 0);
+ my $ret = my_read($self->{in}, $self->{'--batch'}, $size + 1);
+ fail($self, defined($ret) ? 'read EOF' : "read: $!") if !$ret;
+ chop($$ret) eq "\n" or fail($self, 'newline missing after blob');
+ eval { $cb->($ret, $oid_hex, $type, $size, $arg) };
+ warn "E: $oid_hex $@\n" if $@;
+}
+
+sub cat_async_wait ($) {
+ my ($self) = @_;
+ my $inflight = delete $self->{inflight} or return;
+ while (scalar(@$inflight)) {
+ cat_async_step($self, $inflight);
+ }
+}
+
+sub batch_prepare ($) {
+ _bidi_pipe($_[0], qw(--batch in out pid));
+}