+ \$buf;
+}
+
+sub cat_blob {
+ my ($self, $oid) = @_;
+ my ($r, $w) = $self->gfi_start;
+ _cat_blob($r, $w, $oid);
+}
+
+sub check_remove_v1 {
+ my ($r, $w, $tip, $path, $mime) = @_;
+
+ my $info = _check_path($r, $w, $tip, $path) or return ('MISSING',undef);
+ $info =~ m!\A100644 blob ([a-f0-9]{40})\t!s or die "not blob: $info";
+ my $oid = $1;
+ my $msg = _cat_blob($r, $w, $oid) or die "BUG: cat-blob $1 failed";
+ my $cur = PublicInbox::MIME->new($msg);
+ my $cur_s = $cur->header('Subject');
+ $cur_s = '' unless defined $cur_s;
+ my $cur_m = $mime->header('Subject');
+ $cur_m = '' unless defined $cur_m;
+ if ($cur_s ne $cur_m || norm_body($cur) ne norm_body($mime)) {
+ return ('MISMATCH', $cur);
+ }
+ (undef, $cur);
+}
+
+sub checkpoint {
+ my ($self) = @_;
+ return unless $self->{pid};
+ print { $self->{out} } "checkpoint\n" or wfail;
+ undef;
+}
+
+sub progress {
+ my ($self, $msg) = @_;
+ return unless $self->{pid};
+ print { $self->{out} } "progress $msg\n" or wfail;
+ $self->{in}->getline eq "progress $msg\n" or die
+ "progress $msg not received\n";
+ undef;
+}
+
+sub _update_git_info ($$) {
+ my ($self, $do_gc) = @_;
+ # for compatibility with existing ssoma installations
+ # we can probably remove this entirely by 2020
+ my $git_dir = $self->{git}->{git_dir};
+ my @cmd = ('git', "--git-dir=$git_dir");
+ my $index = "$git_dir/ssoma.index";
+ if (-e $index && !$ENV{FAST}) {
+ my $env = { GIT_INDEX_FILE => $index };
+ run_die([@cmd, qw(read-tree -m -v -i), $self->{ref}], $env);
+ }
+ run_die([@cmd, 'update-server-info'], undef);
+ ($self->{path_type} eq '2/38') and eval {
+ require PublicInbox::SearchIdx;
+ my $inbox = $self->{inbox} || $git_dir;
+ my $s = PublicInbox::SearchIdx->new($inbox);
+ $s->index_sync({ ref => $self->{ref} });
+ };
+ eval { run_die([@cmd, qw(gc --auto)], undef) } if $do_gc;
+}
+
+sub barrier {
+ my ($self) = @_;
+
+ # For safety, we ensure git checkpoint is complete before because
+ # the data in git is still more important than what is in Xapian
+ # in v2. Performance may be gained by delaying the ->progress
+ # call but we lose safety
+ if ($self->{nchg}) {
+ $self->checkpoint;
+ $self->progress('checkpoint');
+ _update_git_info($self, 0);
+ $self->{nchg} = 0;
+ }
+}
+
+# used for v2
+sub get_mark {
+ my ($self, $mark) = @_;
+ die "not active\n" unless $self->{pid};
+ my ($r, $w) = $self->gfi_start;
+ print $w "get-mark $mark\n" or wfail;
+ defined(my $oid = <$r>) or die "get-mark failed, need git 2.6.0+\n";
+ chomp($oid);
+ $oid;
+}
+
+# returns undef on non-existent
+# ('MISMATCH', Email::MIME) on mismatch
+# (:MARK, Email::MIME) on success
+#
+# v2 callers should check with Xapian before calling this as
+# it is not idempotent.
+sub remove {
+ my ($self, $mime, $msg) = @_; # mime = Email::MIME
+
+ my $path_type = $self->{path_type};
+ my ($path, $err, $cur, $blob);
+
+ my ($r, $w) = $self->gfi_start;
+ my $tip = $self->{tip};
+ if ($path_type eq '2/38') {
+ $path = mid2path(v1_mid0($mime));
+ ($err, $cur) = check_remove_v1($r, $w, $tip, $path, $mime);
+ return ($err, $cur) if $err;
+ } else {
+ my $sref;
+ if (ref($mime) eq 'SCALAR') { # optimization used by V2Writable
+ $sref = $mime;
+ } else { # XXX should not be necessary:
+ my $str = $mime->as_string;
+ $sref = \$str;
+ }
+ my $len = length($$sref);
+ $blob = $self->{mark}++;
+ print $w "blob\nmark :$blob\ndata $len\n",
+ $$sref, "\n" or wfail;