+package Git::SVN::Log;
+use strict;
+use warnings;
+use POSIX qw/strftime/;
+use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
+ %rusers $show_commit $incremental/;
+my $l_fmt;
+
+sub cmt_showable {
+ my ($c) = @_;
+ return 1 if defined $c->{r};
+ if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
+ $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
+ my @msg = command(qw/cat-file commit/, $c->{c});
+ shift @msg while ($msg[0] ne "\n");
+ shift @msg;
+ @{$c->{l}} = grep !/^git-svn-id: /, @msg;
+
+ (undef, $c->{r}, undef) = ::extract_metadata(
+ (grep(/^git-svn-id: /, @msg))[-1]);
+ }
+ return defined $c->{r};
+}
+
+sub log_use_color {
+ return 1 if $color;
+ my ($dc, $dcvar);
+ $dcvar = 'color.diff';
+ $dc = `git-config --get $dcvar`;
+ if ($dc eq '') {
+ # nothing at all; fallback to "diff.color"
+ $dcvar = 'diff.color';
+ $dc = `git-config --get $dcvar`;
+ }
+ chomp($dc);
+ if ($dc eq 'auto') {
+ my $pc;
+ $pc = `git-config --get color.pager`;
+ if ($pc eq '') {
+ # does not have it -- fallback to pager.color
+ $pc = `git-config --bool --get pager.color`;
+ }
+ else {
+ $pc = `git-config --bool --get color.pager`;
+ if ($?) {
+ $pc = 'false';
+ }
+ }
+ chomp($pc);
+ if (-t *STDOUT || (defined $pager && $pc eq 'true')) {
+ return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
+ }
+ return 0;
+ }
+ return 0 if $dc eq 'never';
+ return 1 if $dc eq 'always';
+ chomp($dc = `git-config --bool --get $dcvar`);
+ return ($dc eq 'true');
+}
+
+sub git_svn_log_cmd {
+ my ($r_min, $r_max) = @_;
+ my $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();
+ return @cmd unless defined $r_max;
+ if ($r_max == $r_min) {
+ push @cmd, '--max-count=1';
+ if (my $c = $gs->rev_db_get($r_max)) {
+ push @cmd, $c;
+ }
+ } else {
+ my ($c_min, $c_max);
+ $c_max = $gs->rev_db_get($r_max);
+ $c_min = $gs->rev_db_get($r_min);
+ if (defined $c_min && defined $c_max) {
+ if ($r_max > $r_max) {
+ push @cmd, "$c_min..$c_max";
+ } else {
+ push @cmd, "$c_max..$c_min";
+ }
+ } elsif ($r_max > $r_min) {
+ push @cmd, $c_max;
+ } else {
+ push @cmd, $c_min;
+ }
+ }
+ return @cmd;
+}
+
+# adapted from pager.c
+sub config_pager {
+ $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
+ if (!defined $pager) {
+ $pager = 'less';
+ } elsif (length $pager == 0 || $pager eq 'cat') {
+ $pager = undef;
+ }
+}
+
+sub run_pager {
+ return unless -t *STDOUT;
+ pipe my $rfd, my $wfd or return;
+ defined(my $pid = fork) or ::fatal "Can't fork: $!\n";
+ if (!$pid) {
+ open STDOUT, '>&', $wfd or
+ ::fatal "Can't redirect to stdout: $!\n";
+ return;
+ }
+ open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n";
+ $ENV{LESS} ||= 'FRSX';
+ exec $pager or ::fatal "Can't run pager: $! ($pager)\n";
+}
+
+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;
+ # 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);
+ }
+ $dest->{t_utc} = $t;
+}
+
+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;
+}
+
+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 '-' x72, "\nr$c->{r} | ";
+ print "$c->{c} | " if $show_commit;
+ print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
+ localtime($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 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
+ if (defined $TZ) {
+ $ENV{TZ} = $TZ;
+ } else {
+ delete $ENV{TZ};
+ }
+ 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\n";
+ }
+ }
+
+ config_pager();
+ @args = (git_svn_log_cmd($r_min, $r_max), @args);
+ my $log = command_output_pipe(@args);
+ run_pager();
+ my (@k, $c, $d);
+ my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
+ while (<$log>) {
+ if (/^${esc_color}commit ($::sha1_short)/o) {
+ 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
+ } elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) {
+ 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 (/^${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) {
+ my $swap = $r_max;
+ $r_max = $r_min;
+ $r_min = $swap;
+ process_commit($_, $r_min, $r_max) foreach reverse @k;
+ }
+out:
+ eval { command_close_pipe($log) };
+ print '-' x72,"\n" unless $incremental || $oneline;
+}
+