about summary refs log tree commit homepage
path: root/lib/PublicInbox/LeiRediff.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox/LeiRediff.pm')
-rw-r--r--lib/PublicInbox/LeiRediff.pm156
1 files changed, 99 insertions, 57 deletions
diff --git a/lib/PublicInbox/LeiRediff.pm b/lib/PublicInbox/LeiRediff.pm
index ea9b2a64..35728330 100644
--- a/lib/PublicInbox/LeiRediff.pm
+++ b/lib/PublicInbox/LeiRediff.pm
@@ -1,4 +1,4 @@
-# Copyright (C) 2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 
 # The "lei rediff" sub-command, regenerates diffs with new options
@@ -7,7 +7,7 @@ use strict;
 use v5.10.1;
 use parent qw(PublicInbox::IPC PublicInbox::LeiInput);
 use File::Temp 0.19 (); # 0.19 for ->newdir
-use PublicInbox::Spawn qw(spawn which);
+use PublicInbox::Spawn qw(run_wait popen_wr which);
 use PublicInbox::MsgIter qw(msg_part_text);
 use PublicInbox::ViewDiff;
 use PublicInbox::LeiBlob;
@@ -30,7 +30,7 @@ sub rediff_user_cb { # called by solver when done
 
         # don't try to support all the git-show(1) options for non-blob,
         # this is just a convenience:
-        $type ne 'blob' and return $lei->err(<<EOF);
+        $type ne 'blob' and return warn(<<EOF);
 # $oid is a $type of $size bytes in:
 # $git->{git_dir} (wanted: $oid_want)
 EOF
@@ -56,6 +56,35 @@ sub solve_1 ($$$) {
         $self->{blob}->{$oid_want}; # full OID
 }
 
+sub _lei_diff_prepare ($$) {
+        my ($lei, $cmd) = @_;
+        my $opt = $lei->{opt};
+        push @$cmd, '--'.($opt->{color} && !$opt->{'no-color'} ? '' : 'no-').
+                        'color';
+        for my $o (@PublicInbox::LEI::diff_opt) {
+                my $c = '';
+                # remove single char short option
+                $o =~ s/\|([a-z0-9])\b//i and $c = $1;
+                if ($o =~ s/=[is]@\z//) {
+                        my $v = $opt->{$o} or next;
+                        push @$cmd, map { $c ? "-$c$_" : "--$o=$_" } @$v;
+                } elsif ($o =~ s/=[is]\z//) {
+                        my $v = $opt->{$o} // next;
+                        push @$cmd, $c ? "-$c$v" : "--$o=$v";
+                } elsif ($o =~ s/:[is]\z//) {
+                        my $v = $opt->{$o} // next;
+                        push @$cmd, $c ? "-$c$v" :
+                                        ($v eq '' ? "--$o" : "--$o=$v");
+                } elsif ($o =~ s/!\z//) {
+                        my $v = $opt->{$o} // next;
+                        push @$cmd, $v ? "--$o" : "--no-$o";
+                } elsif ($opt->{$o}) {
+                        push @$cmd, $c ? "-$c" : "--$o";
+                }
+        }
+        push(@$cmd, "-O$opt->{'order-file'}") if $opt->{'order-file'};
+}
+
 sub diff_ctxq ($$) {
         my ($self, $ctxq) = @_;
         return unless $ctxq;
@@ -85,63 +114,51 @@ EOM
         if (!$rw->{-tmp}) {
                 my $d = "$self->{rdtmp}/for_tree.git";
                 -d $d or PublicInbox::Import::init_bare($d);
-                my $f = "$d/objects/info/alternates"; # always overwrite
-                open my $fh, '>', $f or die "open $f: $!";
-                for my $git (@{$self->{gits}}) {
-                        print $fh $git->git_path('objects'),"\n";
-                }
-                close $fh or die "close $f: $!";
+                # always overwrite
+                PublicInbox::IO::write_file '>', "$d/objects/info/alternates",
+                        map { $_->git_path('objects')."\n" } @{$self->{gits}};
                 $rw = PublicInbox::Git->new($d);
         }
-        pipe(my ($r, $w)) or die "pipe: $!";
-        my $pid = spawn(['git', "--git-dir=$rw->{git_dir}",
+        my $w = popen_wr(['git', "--git-dir=$rw->{git_dir}",
                         qw(fast-import --quiet --done --date-format=raw)],
-                        $lei->{env}, { 2 => $lei->{2}, 0 => $r });
-        close $r or die "close r fast-import: $!";
+                        $lei->{env}, { 2 => $lei->{2} });
         print $w $ta, "\n", $tb, "\ndone\n" or die "print fast-import: $!";
-        close $w or die "close w fast-import: $!";
-        waitpid($pid, 0);
-        die "fast-import failed: \$?=$?" if $?;
+        $w->close or die "close w fast-import: \$?=$? \$!=$!";
 
-        my @cmd = qw(diff);
-        my $opt = $lei->{opt};
-        push @cmd, '--'.($opt->{color} && !$opt->{'no-color'} ? '' : 'no-').
-                        'color';
-        for my $o (@PublicInbox::LEI::diff_opt) {
-                my $c = '';
-                # remove single char short option
-                $o =~ s/\|([a-z0-9])\b//i and $c = $1;
-                if ($o =~ s/=[is]@\z//) {
-                        my $v = $opt->{$o} or next;
-                        push @cmd, map { $c ? "-$c$_" : "--$o=$_" } @$v;
-                } elsif ($o =~ s/=[is]\z//) {
-                        my $v = $opt->{$o} // next;
-                        push @cmd, $c ? "-$c$v" : "--$o=$v";
-                } elsif ($o =~ s/:[is]\z//) {
-                        my $v = $opt->{$o} // next;
-                        push @cmd, $c ? "-$c$v" :
-                                        ($v eq '' ? "--$o" : "--$o=$v");
-                } elsif ($o =~ s/!\z//) {
-                        my $v = $opt->{$o} // next;
-                        push @cmd, $v ? "--$o" : "--no-$o";
-                } elsif ($opt->{$o}) {
-                        push @cmd, $c ? "-$c" : "--$o";
-                }
-        }
-        $lei->qerr("# git @cmd");
-        push @cmd, qw(A B);
-        unshift @cmd, 'git', "--git-dir=$rw->{git_dir}";
-        $pid = spawn(\@cmd, $lei->{env}, { 2 => $lei->{2}, 1 => $lei->{1} });
-        waitpid($pid, 0);
-        $lei->child_error($?) if $?; # for git diff --exit-code
+        my $cmd = [ 'diff' ];
+        _lei_diff_prepare($lei, $cmd);
+        $lei->qerr("# git @$cmd");
+        push @$cmd, qw(A B);
+        unshift @$cmd, 'git', "--git-dir=$rw->{git_dir}";
+        run_wait($cmd, $lei->{env}, { 2 => $lei->{2}, 1 => $lei->{1} }) and
+                $lei->child_error($?); # for git diff --exit-code
+        undef;
+}
+
+# awaitpid callback
+sub wait_requote { $_[1]->child_error($?) if $? }
+
+sub requote ($$) { # '> ' prefix(es) lei->{1}
+        my ($lei, $pfx) = @_;
+        my $opt = { 1 => $lei->{1}, 2 => $lei->{2} };
+        # $^X (perl) is overkill, but maybe there's a weird system w/o sed
+        my $w = popen_wr([$^X, '-pe', "s/^/$pfx/"], $lei->{env}, $opt,
+                         \&wait_requote, $lei);
+        binmode $w, ':utf8';
+        $w;
 }
 
 sub extract_oids { # Eml each_part callback
         my ($ary, $self) = @_;
+        my $lei = $self->{lei};
         my ($p, undef, $idx) = @$ary;
-        $self->{lei}->out($p->header_obj->as_string, "\n");
+        $lei->out($p->header_obj->as_string, "\n");
         my ($s, undef) = msg_part_text($p, $p->content_type || 'text/plain');
         defined $s or return;
+
+        $self->{dqre} && $s =~ s/$self->{dqre}//g && $lei->{opt}->{drq} and
+                local $lei->{1} = requote($lei, $1);
+
         my @top = split($PublicInbox::ViewDiff::EXTRACT_DIFFS, $s);
         undef $s;
         my $blobs = $self->{blobs}; # blobs to resolve
@@ -191,17 +208,42 @@ sub extract_oids { # Eml each_part callback
         $ctxq = diff_ctxq($self, $ctxq);
 }
 
+# ensure dequoted parts are available for rebuilding patches:
+sub dequote_add { # Eml each_part callback
+        my ($ary, $self) = @_;
+        my ($p, undef, $idx) = @$ary;
+        my ($s, undef) = msg_part_text($p, $p->content_type || 'text/plain');
+        defined $s or return;
+        if ($s =~ s/$self->{dqre}//g) { # remove '> ' prefix(es)
+                substr($s, 0, 0, "part-dequoted: $idx\n\n");
+                utf8::encode($s);
+                $self->{tmp_sto}->add_eml(PublicInbox::Eml->new(\$s));
+        }
+}
+
 sub input_eml_cb { # callback for all emails
         my ($self, $eml) = @_;
-        $self->{tmp_sto}->add_eml($eml);
-        $self->{tmp_sto}->done;
+        {
+                local $SIG{__WARN__} = sub {
+                        return if "@_" =~ /^no email in From: .*? or Sender:/;
+                        return if PublicInbox::Eml::warn_ignore(@_);
+                        warn @_;
+                };
+                $self->{tmp_sto}->add_eml($eml);
+                $eml->each_part(\&dequote_add, $self) if $self->{dqre};
+                $self->{tmp_sto}->done;
+        }
         $eml->each_part(\&extract_oids, $self, 1);
 }
 
 sub lei_rediff {
         my ($lei, @inputs) = @_;
+        ($lei->{opt}->{drq} && $lei->{opt}->{'dequote-only'}) and return
+                $lei->fail('--drq and --dequote-only are mutually exclusive');
+        ($lei->{opt}->{drq} && !$lei->{opt}->{verbose}) and
+                $lei->{opt}->{quiet} //= 1;
         $lei->_lei_store(1)->write_prepare($lei);
-        $lei->{opt}->{'in-format'} //= 'eml';
+        $lei->{opt}->{'in-format'} //= 'eml' if $lei->{opt}->{stdin};
         # maybe it's a non-email (code) blob from a coderepo
         my $git_dirs = $lei->{opt}->{'git-dir'} //= [];
         if ($lei->{opt}->{cwd} // 1) {
@@ -213,7 +255,7 @@ sub lei_rediff {
         if ($lxs->remotes) {
                 require PublicInbox::LeiRemote;
                 $lei->{curl} //= which('curl') or return
-                        $lei->fail('curl needed for', $lxs->remotes);
+                        $lei->fail('curl needed for '.join(', ',$lxs->remotes));
         }
         $lei->ale->refresh_externals($lxs, $lei);
         my $self = bless {
@@ -224,10 +266,7 @@ sub lei_rediff {
         my $isatty = -t $lei->{1};
         $lei->{opt}->{color} //= $isatty;
         $lei->start_pager if $isatty;
-        my ($op_c, $ops) = $lei->workers_start($self, 1);
-        $lei->{wq1} = $self;
-        net_merge_all_done($self) unless $lei->{auth};
-        $lei->wait_wq_events($op_c, $ops);
+        $lei->wq1_start($self);
 }
 
 sub ipc_atfork_child {
@@ -248,8 +287,11 @@ sub ipc_atfork_child {
         $self->{gits} = [ map {
                         PublicInbox::Git->new($lei->rel2abs($_))
                 } @{$self->{lei}->{opt}->{'git-dir'}} ];
-        $lei->{env}->{'psgi.errors'} = $lei->{2}; # ugh...
         $lei->{env}->{TMPDIR} = $self->{rdtmp}->dirname;
+        if (my $nr = ($lei->{opt}->{drq} || $lei->{opt}->{'dequote-only'})) {
+                my $re = '\s*> ' x $nr;
+                $self->{dqre} = qr/^($re)/ms;
+        }
         undef;
 }