+ fail($self, "Unexpected result from async git cat-file: $head");
+ }
+ eval { $cb->($bref, $oid, $type, $size, $arg) };
+ $self->{cat_rbuf} = $rbuf if $$rbuf ne '';
+ warn "E: $oid: $@\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));
+}
+
+sub _cat_file_cb {
+ my ($bref, undef, undef, $size, $result) = @_;
+ @$result = ($bref, $size);
+}
+
+sub cat_file {
+ my ($self, $oid, $sizeref) = @_;
+ my $result = [];
+ cat_async($self, $oid, \&_cat_file_cb, $result);
+ cat_async_wait($self);
+ $$sizeref = $result->[1] if $sizeref;
+ $result->[0];
+}
+
+sub check_async_step ($$) {
+ my ($self, $inflight_c) = @_;
+ die 'BUG: inflight empty or odd' if scalar(@$inflight_c) < 3;
+ my ($req, $cb, $arg) = splice(@$inflight_c, 0, 3);
+ my $rbuf = delete($self->{rbuf_c}) // \(my $new = '');
+ chomp(my $line = my_readline($self->{in_c}, $rbuf));
+ my ($hex, $type, $size) = split(/ /, $line);
+
+ # Future versions of git.git may have type=ambiguous, but for now,
+ # we must handle 'dangling' below (and maybe some other oddball
+ # stuff):
+ # https://public-inbox.org/git/20190118033845.s2vlrb3wd3m2jfzu@dcvr/T/
+ if ($hex eq 'dangling' || $hex eq 'notdir' || $hex eq 'loop') {
+ my $ret = my_read($self->{in_c}, $rbuf, $type + 1);
+ fail($self, defined($ret) ? 'read EOF' : "read: $!") if !$ret;