+sub log2stack ($$$) {
+ my ($sync, $git, $range) = @_;
+ my $D = $sync->{D}; # OID_BIN => NR (if reindexing, undef otherwise)
+ my ($add, $del);
+ if ($sync->{ibx}->version == 1) {
+ my $path = $hex.'{2}/'.$hex.'{38}';
+ $add = qr!\A:000000 100644 \S+ ($OID) A\t$path$!;
+ $del = qr!\A:100644 000000 ($OID) \S+ D\t$path$!;
+ } else {
+ $del = qr!\A:\d{6} 100644 $OID ($OID) [AM]\td$!;
+ $add = qr!\A:\d{6} 100644 $OID ($OID) [AM]\tm$!;
+ }
+
+ # Count the new files so they can be added newest to oldest
+ # and still have numbers increasing from oldest to newest
+ my @cmd = qw(log --raw -r --pretty=tformat:%at-%ct-%H
+ --no-notes --no-color --no-renames --no-abbrev);
+ for my $k (qw(since until)) {
+ my $v = $sync->{-opt}->{$k} // next;
+ next if !$sync->{-opt}->{reindex};
+ push @cmd, "--$k=$v";
+ }
+ my $fh = $git->popen(@cmd, $range);
+ my ($at, $ct, $stk, $cmt);
+ while (<$fh>) {
+ return if $sync->{quit};
+ if (/\A([0-9]+)-([0-9]+)-($OID)$/o) {
+ ($at, $ct, $cmt) = ($1 + 0, $2 + 0, $3);
+ $stk //= PublicInbox::IdxStack->new($cmt);
+ } elsif (/$del/) {
+ my $oid = $1;
+ if ($D) { # reindex case
+ $D->{pack('H*', $oid)}++;
+ } else { # non-reindex case:
+ $stk->push_rec('d', $at, $ct, $oid, $cmt);
+ }
+ } elsif (/$add/) {
+ my $oid = $1;
+ if ($D) {
+ my $oid_bin = pack('H*', $oid);
+ my $nr = --$D->{$oid_bin};
+ delete($D->{$oid_bin}) if $nr <= 0;
+ # nr < 0 (-1) means it never existed
+ next if $nr >= 0;
+ }
+ $stk->push_rec('m', $at, $ct, $oid, $cmt);
+ }
+ }
+ close $fh or die "git log failed: \$?=$?";
+ $stk //= PublicInbox::IdxStack->new;
+ $stk->read_prepare;
+}
+
+sub prepare_stack ($$) {
+ my ($sync, $range) = @_;
+ my $git = $sync->{ibx}->git;
+
+ if (index($range, '..') < 0) {
+ # don't show annoying git errors to users who run -index
+ # on empty inboxes
+ $git->qx(qw(rev-parse -q --verify), "$range^0");
+ return PublicInbox::IdxStack->new->read_prepare if $?;
+ }
+ $sync->{D} = $sync->{reindex} ? {} : undef; # OID_BIN => NR
+ log2stack($sync, $git, $range);