+ $fh = undef;
+ delete $self->{reindex_pipe};
+ update_last_commit($self, $git, $i, $cmt) if defined $cmt;
+}
+
+# public, called by public-inbox-index
+sub index_sync {
+ my ($self, $opt) = @_;
+ $opt ||= {};
+ my $pr = $opt->{-progress};
+ my $epoch_max;
+ my $latest = git_dir_latest($self, \$epoch_max);
+ return unless defined $latest;
+ $self->idx_init($opt); # acquire lock
+ my $sync = {
+ D => {}, # "$mid\0$cid" => $oid
+ unindex_range => {}, # EPOCH => oid_old..oid_new
+ reindex => $opt->{reindex},
+ -opt => $opt
+ };
+ $sync->{ranges} = sync_ranges($self, $sync, $epoch_max);
+ $sync->{regen} = sync_prepare($self, $sync, $epoch_max);
+
+ if ($sync->{regen}) {
+ # tmp_clone seems to fail if inside a transaction, so
+ # we rollback here (because we opened {mm} for reading)
+ # Note: we do NOT rely on DBI transactions for atomicity;
+ # only for batch performance.
+ $self->{mm}->{dbh}->rollback;
+ $self->{mm}->{dbh}->begin_work;
+ $sync->{mm_tmp} = $self->{mm}->tmp_clone;
+ }
+
+ # work backwards through history
+ for (my $i = $epoch_max; $i >= 0; $i--) {
+ index_epoch($self, $sync, $i);
+ }
+
+ # unindex is required for leftovers if "deletes" affect messages
+ # in a previous fetch+index window:
+ my $git;
+ if (my @leftovers = values %{delete $sync->{D}}) {
+ $git = $self->{-inbox}->git;
+ for my $oid (@leftovers) {
+ $self->{current_info} = "leftover $oid";
+ unindex_oid($self, $git, $oid);
+ }
+ }
+ if (my $multi_mid = delete $sync->{multi_mid}) {
+ $git //= $self->{-inbox}->git;
+
+ while (defined(my $oid = pop(@$multi_mid))) {
+ $self->{current_info} = "multi_mid $oid";
+ reindex_oid_m($self, $sync, $git, $oid);
+ }
+ $git->cleanup if $git;
+ }
+ $self->done;
+
+ if (my $nr = $sync->{nr}) {
+ my $pr = $sync->{-opt}->{-progress};
+ $pr->('all.git '.sprintf($sync->{-regen_fmt}, $nr)) if $pr;
+ }
+
+ # reindex does not pick up new changes, so we rerun w/o it:
+ if ($opt->{reindex}) {
+ my %again = %$opt;
+ $sync = undef;
+ delete @again{qw(reindex -skip_lock)};
+ index_sync($self, \%again);
+ }