mirror of
https://github.com/git/git.git
synced 2025-12-12 20:36:24 +01:00
Otherwise the caller will notice (yuck) when the user quits the pager without reading through to the end
296 lines
6.4 KiB
Perl
Executable File
296 lines
6.4 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
|
|
$SIG{'PIPE'} = 'IGNORE';
|
|
|
|
my ($old, $new);
|
|
|
|
if (@ARGV == 7) {
|
|
# called as GIT_EXTERNAL_DIFF script
|
|
$old = parse_cooking($ARGV[1]);
|
|
$new = parse_cooking($ARGV[4]);
|
|
} else {
|
|
# called with old and new
|
|
$old = parse_cooking($ARGV[0]);
|
|
$new = parse_cooking($ARGV[1]);
|
|
}
|
|
compare_cooking($old, $new);
|
|
|
|
################################################################
|
|
|
|
use File::Temp qw(tempfile);
|
|
|
|
sub compare_them {
|
|
local($_);
|
|
my ($a, $b, $force, $soft) = @_;
|
|
|
|
if ($soft) {
|
|
$plus = $minus = ' ';
|
|
} else {
|
|
$plus = '+';
|
|
$minus = '-';
|
|
}
|
|
|
|
if (!defined $a->[0]) {
|
|
return map { "$plus$_\n" } map { split(/\n/) } @{$b};
|
|
} elsif (!defined $b->[0]) {
|
|
return map { "$minus$_\n" } map { split(/\n/) } @{$a};
|
|
} elsif (join('', @$a) eq join('', @$b)) {
|
|
if ($force) {
|
|
return map { " $_\n" } map { split(/\n/) } @{$a};
|
|
} else {
|
|
return ();
|
|
}
|
|
}
|
|
my ($ah, $aname) = tempfile();
|
|
my ($bh, $bname) = tempfile();
|
|
my $cnt = 0;
|
|
my @result = ();
|
|
for (@$a) {
|
|
print $ah $_;
|
|
$cnt += tr/\n/\n/;
|
|
}
|
|
for (@$b) {
|
|
print $bh $_;
|
|
$cnt += tr/\n/\n/;
|
|
}
|
|
close $ah;
|
|
close $bh;
|
|
open(my $fh, "-|", 'diff', "-U$cnt", $aname, $bname);
|
|
$cnt = 0;
|
|
while (<$fh>) {
|
|
next if ($cnt++ < 3);
|
|
push @result, $_;
|
|
}
|
|
close $fh;
|
|
unlink ($aname, $bname);
|
|
return @result;
|
|
}
|
|
|
|
sub flush_topic {
|
|
my ($cooking, $name, $desc) = @_;
|
|
my $section = $cooking->{SECTIONS}[-1];
|
|
|
|
return if (!defined $name);
|
|
|
|
$desc =~ s/\s+\Z/\n/s;
|
|
$desc =~ s/\A\s+//s;
|
|
my $topic = +{
|
|
IN_SECTION => $section,
|
|
NAME => $name,
|
|
DESC => $desc,
|
|
};
|
|
$cooking->{TOPICS}{$name} = $topic;
|
|
push @{$cooking->{TOPIC_ORDER}}, $name;
|
|
}
|
|
|
|
sub parse_section {
|
|
my ($cooking, @line) = @_;
|
|
|
|
while (@line && $line[-1] =~ /^\s*$/) {
|
|
pop @line;
|
|
}
|
|
return if (!@line);
|
|
|
|
if (!exists $cooking->{SECTIONS}) {
|
|
$cooking->{SECTIONS} = [];
|
|
$cooking->{TOPICS} = {};
|
|
$cooking->{TOPIC_ORDER} = [];
|
|
}
|
|
if (!exists $cooking->{HEADER}) {
|
|
my $line = join('', @line);
|
|
$line =~ s/\A.*?\n\n//s;
|
|
$cooking->{HEADER} = $line;
|
|
return;
|
|
}
|
|
if (!exists $cooking->{GREETING}) {
|
|
$cooking->{GREETING} = join('', @line);
|
|
return;
|
|
}
|
|
|
|
my ($section_name, $topic_name, $topic_desc);
|
|
for (@line) {
|
|
if (!defined $section_name && /^\[(.*)\]$/) {
|
|
$section_name = $1;
|
|
push @{$cooking->{SECTIONS}}, $section_name;
|
|
next;
|
|
}
|
|
if (/^\* (\S+) /) {
|
|
my $next_name = $1;
|
|
flush_topic($cooking, $topic_name, $topic_desc);
|
|
$topic_name = $next_name;
|
|
$topic_desc = '';
|
|
}
|
|
$topic_desc .= $_;
|
|
}
|
|
flush_topic($cooking, $topic_name, $topic_desc);
|
|
}
|
|
|
|
sub dump_cooking {
|
|
my ($cooking) = @_;
|
|
print $cooking->{HEADER};
|
|
print "-" x 50, "\n";
|
|
print $cooking->{GREETING};
|
|
for my $section_name (@{$cooking->{SECTIONS}}) {
|
|
print "\n", "-" x 50, "\n";
|
|
print "[$section_name]\n";
|
|
for my $topic_name (@{$cooking->{TOPIC_ORDER}}) {
|
|
$topic = $cooking->{TOPICS}{$topic_name};
|
|
next if ($topic->{IN_SECTION} ne $section_name);
|
|
print "\n", $topic->{DESC};
|
|
}
|
|
}
|
|
}
|
|
|
|
sub parse_cooking {
|
|
my ($filename) = @_;
|
|
my (%cooking, @current, $fh);
|
|
open $fh, "<", $filename
|
|
or die "cannot open $filename: $!";
|
|
while (<$fh>) {
|
|
if (/^-{30,}$/) {
|
|
parse_section(\%cooking, @current);
|
|
@current = ();
|
|
next;
|
|
}
|
|
push @current, $_;
|
|
}
|
|
close $fh;
|
|
parse_section(\%cooking, @current);
|
|
|
|
return \%cooking;
|
|
}
|
|
|
|
sub compare_topics {
|
|
my ($a, $b) = @_;
|
|
if (!@$a || !@$b) {
|
|
print compare_them($a, $b, 1, 1);
|
|
return;
|
|
}
|
|
|
|
# otherwise they both have title.
|
|
$a = [map { "$_\n" } split(/\n/, join('', @$a))];
|
|
$b = [map { "$_\n" } split(/\n/, join('', @$b))];
|
|
my $atitle = shift @$a;
|
|
my $btitle = shift @$b;
|
|
print compare_them([$atitle], [$btitle], 1);
|
|
|
|
my (@atail, @btail);
|
|
while (@$a && $a->[-1] !~ /^\s/) {
|
|
unshift @atail, pop @$a;
|
|
}
|
|
while (@$b && $b->[-1] !~ /^\s/) {
|
|
unshift @btail, pop @$b;
|
|
}
|
|
print compare_them($a, $b);
|
|
print compare_them(\@atail, \@btail);
|
|
}
|
|
|
|
sub compare_class {
|
|
my ($fromto, $names, $topics) = @_;
|
|
|
|
my (@where, %where);
|
|
for my $name (@$names) {
|
|
my $t = $topics->{$name};
|
|
my ($a, $b, $in, $force);
|
|
if ($t->{OLD} && $t->{NEW}) {
|
|
$a = [$t->{OLD}{DESC}];
|
|
$b = [$t->{NEW}{DESC}];
|
|
if ($t->{OLD}{IN_SECTION} ne $t->{NEW}{IN_SECTION}) {
|
|
$force = 1;
|
|
$in = '';
|
|
} else {
|
|
$in = "[$t->{NEW}{IN_SECTION}]";
|
|
}
|
|
} elsif ($t->{OLD}) {
|
|
$a = [$t->{OLD}{DESC}];
|
|
$b = [];
|
|
$in = "Was in [$t->{OLD}{IN_SECTION}]";
|
|
} else {
|
|
$a = [];
|
|
$b = [$t->{NEW}{DESC}];
|
|
$in = "[$t->{NEW}{IN_SECTION}]";
|
|
}
|
|
next if (defined $a->[0] &&
|
|
defined $b->[0] &&
|
|
$a->[0] eq $b->[0] && !$force);
|
|
|
|
if (!exists $where{$in}) {
|
|
push @where, $in;
|
|
$where{$in} = [];
|
|
}
|
|
push @{$where{$in}}, [$a, $b];
|
|
}
|
|
|
|
return if (!@where);
|
|
for my $in (@where) {
|
|
my @bag = @{$where{$in}};
|
|
if (defined $fromto && $fromto ne '') {
|
|
print "\n", '-' x 50, "\n$fromto\n";
|
|
$fromto = undef;
|
|
}
|
|
print "\n$in\n" if ($in ne '');
|
|
for (@bag) {
|
|
my ($a, $b) = @{$_};
|
|
print "\n";
|
|
compare_topics($a, $b);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub compare_cooking {
|
|
my ($old, $new) = @_;
|
|
|
|
print compare_them([$old->{HEADER}], [$new->{HEADER}]);
|
|
print compare_them([$old->{GREETING}], [$new->{GREETING}]);
|
|
|
|
my (@sections, %sections, @topics, %topics, @fromto, %fromto);
|
|
|
|
for my $section_name (@{$old->{SECTIONS}}, @{$new->{SECTIONS}}) {
|
|
next if (exists $sections{$section_name});
|
|
$sections{$section_name} = scalar @sections;
|
|
push @sections, $section_name;
|
|
}
|
|
|
|
my $gone_class = "Gone topics";
|
|
my $born_class = "Born topics";
|
|
my $stay_class = "Other topics";
|
|
|
|
push @fromto, $born_class;
|
|
for my $topic_name (@{$old->{TOPIC_ORDER}}, @{$new->{TOPIC_ORDER}}) {
|
|
next if (exists $topics{$topic_name});
|
|
push @topics, $topic_name;
|
|
|
|
my $oldtopic = $old->{TOPICS}{$topic_name};
|
|
my $newtopic = $new->{TOPICS}{$topic_name};
|
|
$topics{$topic_name} = +{
|
|
OLD => $oldtopic,
|
|
NEW => $newtopic,
|
|
};
|
|
my $oldsec = $oldtopic->{IN_SECTION};
|
|
my $newsec = $newtopic->{IN_SECTION};
|
|
if (defined $oldsec && defined $newsec) {
|
|
if ($oldsec ne $newsec) {
|
|
my $fromto =
|
|
"Moved from [$oldsec] to [$newsec]";
|
|
if (!exists $fromto{$fromto}) {
|
|
$fromto{$fromto} = [];
|
|
push @fromto, $fromto;
|
|
}
|
|
push @{$fromto{$fromto}}, $topic_name;
|
|
} else {
|
|
push @{$fromto{$stay_class}}, $topic_name;
|
|
}
|
|
} elsif (defined $oldsec) {
|
|
push @{$fromto{$gone_class}}, $topic_name;
|
|
} else {
|
|
push @{$fromto{$born_class}}, $topic_name;
|
|
}
|
|
}
|
|
push @fromto, $stay_class;
|
|
push @fromto, $gone_class;
|
|
|
|
for my $fromto (@fromto) {
|
|
compare_class($fromto, $fromto{$fromto}, \%topics);
|
|
}
|
|
}
|