2012-07-27 02:26:01 +02:00
|
|
|
package Git::SVN::Log;
|
|
|
|
use strict;
|
perl: check for perl warnings while running tests
We set "use warnings" in most of our perl code to catch problems. But as
the name implies, warnings just emit a message to stderr and don't
otherwise affect the program. So our tests are quite likely to miss that
warnings are being spewed, as most of them do not look at stderr.
We could ask perl to make all warnings fatal, but this is likely
annoying for non-developers, who would rather have a running program
with a warning than something that refuses to work at all.
So instead, let's teach the perl code to respect an environment variable
(GIT_PERL_FATAL_WARNINGS) to increase the severity of the warnings. This
can be set for day-to-day running if people want to be really pedantic,
but the primary use is to trigger it within the test suite.
We could also trigger that for every test run, but likewise even the
tests failing may be annoying to distro builders, etc (just as -Werror
would be for compiling C code). So we'll tie it to a special test-mode
variable (GIT_TEST_PERL_FATAL_WARNINGS) that can be set in the
environment or as a Makefile knob, and we'll automatically turn the knob
when DEVELOPER=1 is set. That should give developers and CI the more
careful view without disrupting normal users or packagers.
Note that the mapping from the GIT_TEST_* form to the GIT_* form in
test-lib.sh is necessary even if they had the same name: the perl
scripts need it to be normalized to a perl truth value, and we also have
to make sure it's exported (we might have gotten it from the
environment, but we might also have gotten it from GIT-BUILD-OPTIONS
directly).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-22 05:24:00 +02:00
|
|
|
use warnings $ENV{GIT_PERL_FATAL_WARNINGS} ? qw(FATAL all) : ();
|
2012-07-27 02:26:01 +02:00
|
|
|
use Git::SVN::Utils qw(fatal);
|
2013-02-09 22:46:56 +01:00
|
|
|
use Git qw(command
|
|
|
|
command_oneline
|
|
|
|
command_output_pipe
|
|
|
|
command_close_pipe
|
|
|
|
get_tz_offset);
|
2012-07-27 02:26:01 +02:00
|
|
|
use POSIX qw/strftime/;
|
|
|
|
use constant commit_log_separator => ('-' x 72) . "\n";
|
|
|
|
use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
|
|
|
|
%rusers $show_commit $incremental/;
|
|
|
|
|
|
|
|
# Option set in git-svn
|
|
|
|
our $_git_format;
|
|
|
|
|
|
|
|
sub cmt_showable {
|
|
|
|
my ($c) = @_;
|
|
|
|
return 1 if defined $c->{r};
|
|
|
|
|
|
|
|
# big commit message got truncated by the 16k pretty buffer in rev-list
|
|
|
|
if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
|
|
|
|
$c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
|
|
|
|
@{$c->{l}} = ();
|
|
|
|
my @log = command(qw/cat-file commit/, $c->{c});
|
|
|
|
|
|
|
|
# shift off the headers
|
|
|
|
shift @log while ($log[0] ne '');
|
|
|
|
shift @log;
|
|
|
|
|
|
|
|
# TODO: make $c->{l} not have a trailing newline in the future
|
|
|
|
@{$c->{l}} = map { "$_\n" } grep !/^git-svn-id: /, @log;
|
|
|
|
|
|
|
|
(undef, $c->{r}, undef) = ::extract_metadata(
|
|
|
|
(grep(/^git-svn-id: /, @log))[-1]);
|
|
|
|
}
|
|
|
|
return defined $c->{r};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub log_use_color {
|
|
|
|
return $color || Git->repository->get_colorbool('color.diff');
|
|
|
|
}
|
|
|
|
|
|
|
|
sub git_svn_log_cmd {
|
|
|
|
my ($r_min, $r_max, @args) = @_;
|
|
|
|
my $head = 'HEAD';
|
|
|
|
my (@files, @log_opts);
|
|
|
|
foreach my $x (@args) {
|
|
|
|
if ($x eq '--' || @files) {
|
|
|
|
push @files, $x;
|
|
|
|
} else {
|
|
|
|
if (::verify_ref("$x^0")) {
|
|
|
|
$head = $x;
|
|
|
|
} else {
|
|
|
|
push @log_opts, $x;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my ($url, $rev, $uuid, $gs) = ::working_head_info($head);
|
|
|
|
|
|
|
|
require Git::SVN;
|
|
|
|
$gs ||= Git::SVN->_new;
|
|
|
|
my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
|
|
|
|
$gs->refname);
|
|
|
|
push @cmd, '-r' unless $non_recursive;
|
|
|
|
push @cmd, qw/--raw --name-status/ if $verbose;
|
|
|
|
push @cmd, '--color' if log_use_color();
|
|
|
|
push @cmd, @log_opts;
|
|
|
|
if (defined $r_max && $r_max == $r_min) {
|
|
|
|
push @cmd, '--max-count=1';
|
|
|
|
if (my $c = $gs->rev_map_get($r_max)) {
|
|
|
|
push @cmd, $c;
|
|
|
|
}
|
|
|
|
} elsif (defined $r_max) {
|
|
|
|
if ($r_max < $r_min) {
|
|
|
|
($r_min, $r_max) = ($r_max, $r_min);
|
|
|
|
}
|
|
|
|
my (undef, $c_max) = $gs->find_rev_before($r_max, 1, $r_min);
|
|
|
|
my (undef, $c_min) = $gs->find_rev_after($r_min, 1, $r_max);
|
|
|
|
# If there are no commits in the range, both $c_max and $c_min
|
|
|
|
# will be undefined. If there is at least 1 commit in the
|
|
|
|
# range, both will be defined.
|
|
|
|
return () if !defined $c_min || !defined $c_max;
|
|
|
|
if ($c_min eq $c_max) {
|
|
|
|
push @cmd, '--max-count=1', $c_min;
|
|
|
|
} else {
|
|
|
|
push @cmd, '--boundary', "$c_min..$c_max";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (@cmd, @files);
|
|
|
|
}
|
|
|
|
|
|
|
|
# adapted from pager.c
|
|
|
|
sub config_pager {
|
|
|
|
if (! -t *STDOUT) {
|
|
|
|
$ENV{GIT_PAGER_IN_USE} = 'false';
|
|
|
|
$pager = undef;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
chomp($pager = command_oneline(qw(var GIT_PAGER)));
|
|
|
|
if ($pager eq 'cat') {
|
|
|
|
$pager = undef;
|
|
|
|
}
|
|
|
|
$ENV{GIT_PAGER_IN_USE} = defined($pager);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub run_pager {
|
|
|
|
return unless defined $pager;
|
|
|
|
pipe my ($rfd, $wfd) or return;
|
|
|
|
defined(my $pid = fork) or fatal "Can't fork: $!";
|
|
|
|
if (!$pid) {
|
|
|
|
open STDOUT, '>&', $wfd or
|
|
|
|
fatal "Can't redirect to stdout: $!";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
open STDIN, '<&', $rfd or fatal "Can't redirect stdin: $!";
|
2014-04-30 09:35:25 +02:00
|
|
|
$ENV{LESS} ||= 'FRX';
|
2014-01-07 03:14:05 +01:00
|
|
|
$ENV{LV} ||= '-c';
|
2012-07-27 02:26:01 +02:00
|
|
|
exec $pager or fatal "Can't run pager: $! ($pager)";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub format_svn_date {
|
|
|
|
my $t = shift || time;
|
|
|
|
require Git::SVN;
|
2013-02-09 22:46:56 +01:00
|
|
|
my $gmoff = get_tz_offset($t);
|
2012-07-27 02:26:01 +02:00
|
|
|
return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t));
|
|
|
|
}
|
|
|
|
|
|
|
|
sub parse_git_date {
|
|
|
|
my ($t, $tz) = @_;
|
|
|
|
# Date::Parse isn't in the standard Perl distro :(
|
|
|
|
if ($tz =~ s/^\+//) {
|
|
|
|
$t += tz_to_s_offset($tz);
|
|
|
|
} elsif ($tz =~ s/^\-//) {
|
|
|
|
$t -= tz_to_s_offset($tz);
|
|
|
|
}
|
|
|
|
return $t;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_local_timezone {
|
|
|
|
if (defined $TZ) {
|
|
|
|
$ENV{TZ} = $TZ;
|
|
|
|
} else {
|
|
|
|
delete $ENV{TZ};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub tz_to_s_offset {
|
|
|
|
my ($tz) = @_;
|
|
|
|
$tz =~ s/(\d\d)$//;
|
|
|
|
return ($1 * 60) + ($tz * 3600);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_author_info {
|
|
|
|
my ($dest, $author, $t, $tz) = @_;
|
|
|
|
$author =~ s/(?:^\s*|\s*$)//g;
|
|
|
|
$dest->{a_raw} = $author;
|
|
|
|
my $au;
|
|
|
|
if ($::_authors) {
|
|
|
|
$au = $rusers{$author} || undef;
|
|
|
|
}
|
|
|
|
if (!$au) {
|
|
|
|
($au) = ($author =~ /<([^>]+)\@[^>]+>$/);
|
|
|
|
}
|
|
|
|
$dest->{t} = $t;
|
|
|
|
$dest->{tz} = $tz;
|
|
|
|
$dest->{a} = $au;
|
|
|
|
$dest->{t_utc} = parse_git_date($t, $tz);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub process_commit {
|
|
|
|
my ($c, $r_min, $r_max, $defer) = @_;
|
|
|
|
if (defined $r_min && defined $r_max) {
|
|
|
|
if ($r_min == $c->{r} && $r_min == $r_max) {
|
|
|
|
show_commit($c);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1 if $r_min == $r_max;
|
|
|
|
if ($r_min < $r_max) {
|
|
|
|
# we need to reverse the print order
|
|
|
|
return 0 if (defined $limit && --$limit < 0);
|
|
|
|
push @$defer, $c;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if ($r_min != $r_max) {
|
|
|
|
return 1 if ($r_min < $c->{r});
|
|
|
|
return 1 if ($r_max > $c->{r});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0 if (defined $limit && --$limit < 0);
|
|
|
|
show_commit($c);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $l_fmt;
|
|
|
|
sub show_commit {
|
|
|
|
my $c = shift;
|
|
|
|
if ($oneline) {
|
|
|
|
my $x = "\n";
|
|
|
|
if (my $l = $c->{l}) {
|
|
|
|
while ($l->[0] =~ /^\s*$/) { shift @$l }
|
|
|
|
$x = $l->[0];
|
|
|
|
}
|
|
|
|
$l_fmt ||= 'A' . length($c->{r});
|
|
|
|
print 'r',pack($l_fmt, $c->{r}),' | ';
|
|
|
|
print "$c->{c} | " if $show_commit;
|
|
|
|
print $x;
|
|
|
|
} else {
|
|
|
|
show_commit_normal($c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub show_commit_changed_paths {
|
|
|
|
my ($c) = @_;
|
|
|
|
return unless $c->{changed};
|
|
|
|
print "Changed paths:\n", @{$c->{changed}};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub show_commit_normal {
|
|
|
|
my ($c) = @_;
|
|
|
|
print commit_log_separator, "r$c->{r} | ";
|
|
|
|
print "$c->{c} | " if $show_commit;
|
|
|
|
print "$c->{a} | ", format_svn_date($c->{t_utc}), ' | ';
|
|
|
|
my $nr_line = 0;
|
|
|
|
|
|
|
|
if (my $l = $c->{l}) {
|
|
|
|
while ($l->[$#$l] eq "\n" && $#$l > 0
|
|
|
|
&& $l->[($#$l - 1)] eq "\n") {
|
|
|
|
pop @$l;
|
|
|
|
}
|
|
|
|
$nr_line = scalar @$l;
|
|
|
|
if (!$nr_line) {
|
|
|
|
print "1 line\n\n\n";
|
|
|
|
} else {
|
|
|
|
if ($nr_line == 1) {
|
|
|
|
$nr_line = '1 line';
|
|
|
|
} else {
|
|
|
|
$nr_line .= ' lines';
|
|
|
|
}
|
|
|
|
print $nr_line, "\n";
|
|
|
|
show_commit_changed_paths($c);
|
|
|
|
print "\n";
|
|
|
|
print $_ foreach @$l;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
print "1 line\n";
|
|
|
|
show_commit_changed_paths($c);
|
|
|
|
print "\n";
|
|
|
|
|
|
|
|
}
|
|
|
|
foreach my $x (qw/raw stat diff/) {
|
|
|
|
if ($c->{$x}) {
|
|
|
|
print "\n";
|
|
|
|
print $_ foreach @{$c->{$x}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub cmd_show_log {
|
|
|
|
my (@args) = @_;
|
|
|
|
my ($r_min, $r_max);
|
|
|
|
my $r_last = -1; # prevent dupes
|
|
|
|
set_local_timezone();
|
|
|
|
if (defined $::_revision) {
|
|
|
|
if ($::_revision =~ /^(\d+):(\d+)$/) {
|
|
|
|
($r_min, $r_max) = ($1, $2);
|
|
|
|
} elsif ($::_revision =~ /^\d+$/) {
|
|
|
|
$r_min = $r_max = $::_revision;
|
|
|
|
} else {
|
|
|
|
fatal "-r$::_revision is not supported, use ",
|
|
|
|
"standard 'git log' arguments instead";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
config_pager();
|
|
|
|
@args = git_svn_log_cmd($r_min, $r_max, @args);
|
|
|
|
if (!@args) {
|
|
|
|
print commit_log_separator unless $incremental || $oneline;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
my $log = command_output_pipe(@args);
|
|
|
|
run_pager();
|
|
|
|
my (@k, $c, $d, $stat);
|
|
|
|
my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
|
|
|
|
while (<$log>) {
|
2020-06-22 20:04:12 +02:00
|
|
|
if (/^${esc_color}commit (?:- )?($::oid_short)/o) {
|
2012-07-27 02:26:01 +02:00
|
|
|
my $cmt = $1;
|
|
|
|
if ($c && cmt_showable($c) && $c->{r} != $r_last) {
|
|
|
|
$r_last = $c->{r};
|
|
|
|
process_commit($c, $r_min, $r_max, \@k) or
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
$d = undef;
|
|
|
|
$c = { c => $cmt };
|
|
|
|
} elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) {
|
|
|
|
get_author_info($c, $1, $2, $3);
|
|
|
|
} elsif (/^${esc_color}(?:tree|parent|committer) /o) {
|
|
|
|
# ignore
|
svn: use correct variable name for short OID
The commit 9ab33150a0 ("perl: create and switch variables for hash
constants", 2020-06-22) converted each instance of the variable
$sha1_short into $oid_short in the Subversion code, since git-svn now
understands SHA-256. However, one conversion was missed.
As a result, Perl complains about the use of this variable:
Use of uninitialized value $sha1_short in regexp compilation at
/usr/lib64/perl5/vendor_perl/5.30.3/Git/SVN/Log.pm line 301, <$fh>
line 6.
Because we're parsing raw diff output here, the likelihood is very low
that we'll actually misparse the data, since the only lines we're going
to get starting with colons are the ones we're expecting. Even if we
had a newline in a path, we'd end up with a quoted path. Our regex is
just less strict than we'd like it to be.
However, it's obviously undesirable that our code is emitting Perl
warnings, so let's convert it to use the proper variable name.
Reported-by: Nikos Chantziaras <realnc@gmail.com>
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-22 03:18:11 +02:00
|
|
|
} elsif (/^${esc_color}:\d{6} \d{6} $::oid_short/o) {
|
2012-07-27 02:26:01 +02:00
|
|
|
push @{$c->{raw}}, $_;
|
|
|
|
} elsif (/^${esc_color}[ACRMDT]\t/) {
|
|
|
|
# we could add $SVN->{svn_path} here, but that requires
|
|
|
|
# remote access at the moment (repo_path_split)...
|
|
|
|
s#^(${esc_color})([ACRMDT])\t#$1 $2 #o;
|
|
|
|
push @{$c->{changed}}, $_;
|
|
|
|
} elsif (/^${esc_color}diff /o) {
|
|
|
|
$d = 1;
|
|
|
|
push @{$c->{diff}}, $_;
|
|
|
|
} elsif ($d) {
|
|
|
|
push @{$c->{diff}}, $_;
|
|
|
|
} elsif (/^\ .+\ \|\s*\d+\ $esc_color[\+\-]*
|
|
|
|
$esc_color*[\+\-]*$esc_color$/x) {
|
|
|
|
$stat = 1;
|
|
|
|
push @{$c->{stat}}, $_;
|
|
|
|
} elsif ($stat && /^ \d+ files changed, \d+ insertions/) {
|
|
|
|
push @{$c->{stat}}, $_;
|
|
|
|
$stat = undef;
|
|
|
|
} elsif (/^${esc_color} (git-svn-id:.+)$/o) {
|
|
|
|
($c->{url}, $c->{r}, undef) = ::extract_metadata($1);
|
|
|
|
} elsif (s/^${esc_color} //o) {
|
|
|
|
push @{$c->{l}}, $_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($c && defined $c->{r} && $c->{r} != $r_last) {
|
|
|
|
$r_last = $c->{r};
|
|
|
|
process_commit($c, $r_min, $r_max, \@k);
|
|
|
|
}
|
|
|
|
if (@k) {
|
|
|
|
($r_min, $r_max) = ($r_max, $r_min);
|
|
|
|
process_commit($_, $r_min, $r_max) foreach reverse @k;
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
close $log;
|
|
|
|
print commit_log_separator unless $incremental || $oneline;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub cmd_blame {
|
|
|
|
my $path = pop;
|
|
|
|
|
|
|
|
config_pager();
|
|
|
|
run_pager();
|
|
|
|
|
|
|
|
my ($fh, $ctx, $rev);
|
|
|
|
|
|
|
|
if ($_git_format) {
|
|
|
|
($fh, $ctx) = command_output_pipe('blame', @_, $path);
|
|
|
|
while (my $line = <$fh>) {
|
|
|
|
if ($line =~ /^\^?([[:xdigit:]]+)\s/) {
|
|
|
|
# Uncommitted edits show up as a rev ID of
|
|
|
|
# all zeros, which we can't look up with
|
|
|
|
# cmt_metadata
|
|
|
|
if ($1 !~ /^0+$/) {
|
|
|
|
(undef, $rev, undef) =
|
|
|
|
::cmt_metadata($1);
|
|
|
|
$rev = '0' if (!$rev);
|
|
|
|
} else {
|
|
|
|
$rev = '0';
|
|
|
|
}
|
|
|
|
$rev = sprintf('%-10s', $rev);
|
|
|
|
$line =~ s/^\^?[[:xdigit:]]+(\s)/$rev$1/;
|
|
|
|
}
|
|
|
|
print $line;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
($fh, $ctx) = command_output_pipe('blame', '-p', @_, 'HEAD',
|
|
|
|
'--', $path);
|
|
|
|
my ($sha1);
|
|
|
|
my %authors;
|
|
|
|
my @buffer;
|
|
|
|
my %dsha; #distinct sha keys
|
|
|
|
|
|
|
|
while (my $line = <$fh>) {
|
|
|
|
push @buffer, $line;
|
|
|
|
if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
|
|
|
|
$dsha{$1} = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
my $s2r = ::cmt_sha2rev_batch([keys %dsha]);
|
|
|
|
|
|
|
|
foreach my $line (@buffer) {
|
|
|
|
if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
|
|
|
|
$rev = $s2r->{$1};
|
|
|
|
$rev = '0' if (!$rev)
|
|
|
|
}
|
|
|
|
elsif ($line =~ /^author (.*)/) {
|
|
|
|
$authors{$rev} = $1;
|
|
|
|
$authors{$rev} =~ s/\s/_/g;
|
|
|
|
}
|
|
|
|
elsif ($line =~ /^\t(.*)$/) {
|
|
|
|
printf("%6s %10s %s\n", $rev, $authors{$rev}, $1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
command_close_pipe($fh, $ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|