use File::Basename qw(basename);
binmode STDOUT, ':utf8';
+BEGIN {
+ CGI->compile() if $ENV{MOD_PERL};
+}
+
our $cgi = new CGI;
our $version = "++GIT_VERSION++";
our $my_url = $cgi->url();
## ----------------------------------------------------------------------
## functions returning short HTML fragments, or transforming HTML fragments
-## which don't beling to other sections
+## which don't belong to other sections
# format line of commit message.
sub format_log_line_html {
$key =~ s/^gitweb\.//;
return if ($key =~ m/\W/);
- my @x = (git_cmd(), 'repo-config');
+ my @x = (git_cmd(), 'config');
if (defined $type) { push @x, $type; }
push @x, "--get";
push @x, "gitweb.$key";
return %tag
}
-sub parse_commit {
- my $commit_id = shift;
- my $commit_text = shift;
-
- my @commit_lines;
+sub parse_commit_text {
+ my ($commit_text, $withparents) = @_;
+ my @commit_lines = split '\n', $commit_text;
my %co;
- if (defined $commit_text) {
- @commit_lines = @$commit_text;
- } else {
- local $/ = "\0";
- open my $fd, "-|", git_cmd(), "rev-list",
- "--header", "--parents", "--max-count=1",
- $commit_id, "--"
- or return;
- @commit_lines = split '\n', <$fd>;
- close $fd or return;
- pop @commit_lines;
- }
+ pop @commit_lines; # Remove '\0'
+
my $header = shift @commit_lines;
if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
return;
}
($co{'id'}, my @parents) = split ' ', $header;
- $co{'parents'} = \@parents;
- $co{'parent'} = $parents[0];
while (my $line = shift @commit_lines) {
last if $line eq "\n";
if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
$co{'tree'} = $1;
+ } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
+ push @parents, $1;
} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
$co{'author'} = $1;
$co{'author_epoch'} = $2;
if (!defined $co{'tree'}) {
return;
};
+ $co{'parents'} = \@parents;
+ $co{'parent'} = $parents[0];
foreach my $title (@commit_lines) {
$title =~ s/^ //;
return %co;
}
+sub parse_commit {
+ my ($commit_id) = @_;
+ my %co;
+
+ local $/ = "\0";
+
+ open my $fd, "-|", git_cmd(), "rev-list",
+ "--parents",
+ "--header",
+ "--max-count=1",
+ $commit_id,
+ "--",
+ or die_error(undef, "Open git-rev-list failed");
+ %co = parse_commit_text(<$fd>, 1);
+ close $fd;
+
+ return %co;
+}
+
+sub parse_commits {
+ my ($commit_id, $maxcount, $skip, $arg, $filename) = @_;
+ my @cos;
+
+ $maxcount ||= 1;
+ $skip ||= 0;
+
+ local $/ = "\0";
+
+ open my $fd, "-|", git_cmd(), "rev-list",
+ "--header",
+ ($arg ? ($arg) : ()),
+ ("--max-count=" . $maxcount),
+ ("--skip=" . $skip),
+ $commit_id,
+ "--",
+ ($filename ? ($filename) : ())
+ or die_error(undef, "Open git-rev-list failed");
+ while (my $line = <$fd>) {
+ my %co = parse_commit_text($line);
+ push @cos, \%co;
+ }
+ close $fd;
+
+ return wantarray ? @cos : \@cos;
+}
+
# parse ref from ref_file, given by ref_id, with given type
sub parse_ref {
my $ref_file = shift;
my $title = "$site_name";
if (defined $project) {
- $title .= " - $project";
+ $title .= " - " . to_utf8($project);
if (defined $action) {
$title .= "/$action";
if (defined $file_name) {
}
print $cgi->header(-type=>$content_type, -charset => 'utf-8',
-status=> $status, -expires => $expires);
+ my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
print <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- git core binaries version $git_version -->
<head>
<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
-<meta name="generator" content="gitweb/$version git/$git_version"/>
+<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
<meta name="robots" content="index, nofollow"/>
<title>$title</title>
EOF
print "<div class=\"page_path\">";
print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
- -title => 'tree root'}, "[$project]");
+ -title => 'tree root'}, to_utf8("[$project]"));
print " / ";
if (defined $name) {
my @dirname = split '/', $name;
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'})},
- "blob") . " | ";
+ "blob");
print "</td>\n";
} elsif ($diff{'status'} eq "D") { # deleted
my $mode_chnge = "";
if ($diff{'from_mode'} != $diff{'to_mode'}) {
$mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
- if ($from_file_type != $to_file_type) {
+ if ($from_file_type ne $to_file_type) {
$mode_chnge .= " from $from_file_type to $to_file_type";
}
if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
my $patch_line;
my $diffinfo;
my (%from, %to);
- my ($from_id, $to_id);
print "<div class=\"patchset\">\n";
PATCH:
while ($patch_line) {
my @diff_header;
+ my ($from_id, $to_id);
# git diff header
#assert($patch_line =~ m/^diff /) if DEBUG;
while ($patch_line = <$fd>) {
chomp $patch_line;
- last EXTENDED_HEADER if ($patch_line =~ m/^--- /);
+ last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
$from_id = $1;
push @diff_header, $patch_line;
}
- #last PATCH unless $patch_line;
my $last_patch_line = $patch_line;
# check if current patch belong to current raw line
$from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
hash=>$diffinfo->{'from_id'},
file_name=>$from{'file'});
+ } else {
+ delete $from{'href'};
}
if ($diffinfo->{'status'} ne "D") { # not deleted file
$to{'href'} = href(action=>"blob", hash_base=>$hash,
hash=>$diffinfo->{'to_id'},
file_name=>$to{'file'});
+ } else {
+ delete $to{'href'};
}
# this is first patch for raw difftree line with $patch_idx index
# we index @$difftree array from 0, but number patches from 1
# match <path>
if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
$patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
- esc_path($from{'file'}));
+ esc_path($from{'file'}));
}
if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
- $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"},
- esc_path($to{'file'}));
+ $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"},
+ esc_path($to{'file'}));
}
# match <mode>
if ($patch_line =~ m/\s(\d{6})$/) {
# from-file/to-file diff header
$patch_line = $last_patch_line;
+ if (! $patch_line) {
+ print "</div>\n"; # class="patch"
+ last PATCH;
+ }
+ next PATCH if ($patch_line =~ m/^diff /);
#assert($patch_line =~ m/^---/) if DEBUG;
- if ($from{'href'}) {
+ if ($from{'href'} && $patch_line =~ m!^--- "?a/!) {
$patch_line = '--- a/' .
$cgi->a({-href=>$from{'href'}, -class=>"path"},
esc_path($from{'file'}));
print "<div class=\"diff from_file\">$patch_line</div>\n";
$patch_line = <$fd>;
- #last PATCH unless $patch_line;
chomp $patch_line;
#assert($patch_line =~ m/^+++/) if DEBUG;
- if ($to{'href'}) {
+ if ($to{'href'} && $patch_line =~ m!^\+\+\+ "?b/!) {
$patch_line = '+++ b/' .
$cgi->a({-href=>$to{'href'}, -class=>"path"},
esc_path($to{'file'}));
sub git_shortlog_body {
# uses global variable $project
- my ($revlist, $from, $to, $refs, $extra) = @_;
+ my ($commitlist, $from, $to, $refs, $extra) = @_;
my $have_snapshot = gitweb_have_snapshot();
$from = 0 unless defined $from;
- $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
+ $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
print "<table class=\"shortlog\" cellspacing=\"0\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
- my $commit = $revlist->[$i];
- #my $ref = defined $refs ? format_ref_marker($refs, $commit) : '';
+ my %co = %{$commitlist->[$i]};
+ my $commit = $co{'id'};
my $ref = format_ref_marker($refs, $commit);
- my %co = parse_commit($commit);
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
sub git_history_body {
# Warning: assumes constant type (blob or tree) during history
- my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
+ my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
$from = 0 unless defined $from;
- $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist});
+ $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
print "<table class=\"history\" cellspacing=\"0\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
- if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
- next;
- }
-
- my $commit = $1;
- my %co = parse_commit($commit);
+ my %co = %{$commitlist->[$i]};
if (!%co) {
next;
}
+ my $commit = $co{'id'};
my $ref = format_ref_marker($refs, $commit);
print "<tr class=\"light\">\n";
}
$alternate ^= 1;
- print "<td><i>$tag{'age'}</i></td>\n" .
- "<td>" .
+ if (defined $tag{'age'}) {
+ print "<td><i>$tag{'age'}</i></td>\n";
+ } else {
+ print "<td></td>\n";
+ }
+ print "<td>" .
$cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
-class => "list name"}, esc_html($tag{'name'})) .
"</td>\n" .
}
sub git_search_grep_body {
- my ($greplist, $from, $to, $extra) = @_;
+ my ($commitlist, $from, $to, $extra) = @_;
$from = 0 unless defined $from;
- $to = $#{$greplist} if (!defined $to || $#{$greplist} < $to);
+ $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
print "<table class=\"grep\" cellspacing=\"0\">\n";
my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
- my $commit = $greplist->[$i];
- my %co = parse_commit($commit);
+ my %co = %{$commitlist->[$i]};
if (!%co) {
next;
}
+ my $commit = $co{'id'};
if ($alternate) {
print "<tr class=\"dark\">\n";
} else {
foreach my $pr (@projects) {
if (!exists $pr->{'owner'}) {
- $pr->{'owner'} = get_file_owner("$projectroot/$project");
+ $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}");
}
my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
# we need to request one more than 16 (0..15) to check if
# those 16 are all
- open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
- $head, "--"
- or die_error(undef, "Open git-rev-list failed");
- my @revlist = map { chomp; $_ } <$fd>;
- close $fd;
+ my @commitlist = parse_commits($head, 17);
git_print_header_div('shortlog');
- git_shortlog_body(\@revlist, 0, 15, $refs,
- $#revlist <= 15 ? undef :
+ git_shortlog_body(\@commitlist, 0, 15, $refs,
+ $#commitlist <= 15 ? undef :
$cgi->a({-href => href(action=>"shortlog")}, "..."));
if (@taglist) {
esc_html($rev));
print "</td>\n";
}
+ open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
+ or die_error("could not open git-rev-parse");
+ my $parent_commit = <$dd>;
+ close $dd;
+ chomp($parent_commit);
my $blamed = href(action => 'blame',
file_name => $meta->{'filename'},
- hash_base => $full_rev);
+ hash_base => $parent_commit);
print "<td class=\"linenr\">";
print $cgi->a({ -href => "$blamed#l$orig_lineno",
-id => "l$lineno",
$hash = git_get_head_hash($project);
}
- my $filename = basename($project) . "-$hash.tar.$suffix";
+ my $filename = to_utf8(basename($project)) . "-$hash.tar.$suffix";
print $cgi->header(
-type => "application/$ctype",
}
my $refs = git_get_references();
- my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
- open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
- or die_error(undef, "Open git-rev-list failed");
- my @revlist = map { chomp; $_ } <$fd>;
- close $fd;
+ my @commitlist = parse_commits($hash, 101, (100 * $page));
- my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist);
+ my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1)));
git_header_html();
git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
- if (!@revlist) {
+ if (!@commitlist) {
my %co = parse_commit($hash);
git_print_header_div('summary', $project);
print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
}
- for (my $i = ($page * 100); $i <= $#revlist; $i++) {
- my $commit = $revlist[$i];
- my $ref = format_ref_marker($refs, $commit);
- my %co = parse_commit($commit);
+ my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
+ for (my $i = 0; $i <= $to; $i++) {
+ my %co = %{$commitlist[$i]};
next if !%co;
+ my $commit = $co{'id'};
+ my $ref = format_ref_marker($refs, $commit);
my %ad = parse_date($co{'author_epoch'});
git_print_header_div('commit',
"<span class=\"age\">$co{'age_string'}</span>" .
git_print_log($co{'comment'}, -final_empty_line=> 1);
print "</div>\n";
}
+ if ($#commitlist >= 100) {
+ print "<div class=\"page_nav\">\n";
+ print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1),
+ -accesskey => "n", -title => "Alt-n"}, "next");
+ print "</div>\n";
+ }
git_footer_html();
}
$ftype = git_get_type($hash);
}
- open my $fd, "-|",
- git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name
- or die_error(undef, "Open git-rev-list-failed");
- my @revlist = map { chomp; $_ } <$fd>;
- close $fd
- or die_error(undef, "Reading git-rev-list failed");
+ my @commitlist = parse_commits($hash_base, 101, (100 * $page), "--full-history", $file_name);
my $paging_nav = '';
if ($page > 0) {
$paging_nav .= "first";
$paging_nav .= " ⋅ prev";
}
- if ($#revlist >= (100 * ($page+1)-1)) {
+ if ($#commitlist >= 100) {
$paging_nav .= " ⋅ " .
$cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
file_name=>$file_name, page=>$page+1),
$paging_nav .= " ⋅ next";
}
my $next_link = '';
- if ($#revlist >= (100 * ($page+1)-1)) {
+ if ($#commitlist >= 100) {
$next_link =
$cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
file_name=>$file_name, page=>$page+1),
- -title => "Alt-n"}, "next");
+ -accesskey => "n", -title => "Alt-n"}, "next");
}
git_header_html();
git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
git_print_page_path($file_name, $ftype, $hash_base);
- git_history_body(\@revlist, ($page * 100), $#revlist,
+ git_history_body(\@commitlist, 0, 99,
$refs, $hash_base, $ftype, $next_link);
git_footer_html();
} elsif ($searchtype eq 'committer') {
$greptype = "--committer=";
}
- open my $fd, "-|", git_cmd(), "rev-list",
- ("--max-count=" . (100 * ($page+1))),
- ($greptype . $searchtext),
- $hash, "--"
- or next;
- my @revlist = map { chomp; $_ } <$fd>;
- close $fd;
+ $greptype .= $searchtext;
+ my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype);
my $paging_nav = '';
if ($page > 0) {
$paging_nav .= "first";
$paging_nav .= " ⋅ prev";
}
- if ($#revlist >= (100 * ($page+1)-1)) {
+ if ($#commitlist >= 100) {
$paging_nav .= " ⋅ " .
$cgi->a({-href => href(action=>"search", hash=>$hash,
searchtext=>$searchtext, searchtype=>$searchtype,
$paging_nav .= " ⋅ next";
}
my $next_link = '';
- if ($#revlist >= (100 * ($page+1)-1)) {
+ if ($#commitlist >= 100) {
$next_link =
$cgi->a({-href => href(action=>"search", hash=>$hash,
searchtext=>$searchtext, searchtype=>$searchtype,
git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
git_print_header_div('commit', esc_html($co{'title'}), $hash);
- git_search_grep_body(\@revlist, ($page * 100), $#revlist, $next_link);
+ git_search_grep_body(\@commitlist, 0, 99, $next_link);
}
if ($searchtype eq 'pickaxe') {
}
my $refs = git_get_references();
- my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
- open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
- or die_error(undef, "Open git-rev-list failed");
- my @revlist = map { chomp; $_ } <$fd>;
- close $fd;
+ my @commitlist = parse_commits($hash, 101, (100 * $page));
- my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist);
+ my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1)));
my $next_link = '';
- if ($#revlist >= (100 * ($page+1)-1)) {
+ if ($#commitlist >= 100) {
$next_link =
$cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1),
- -title => "Alt-n"}, "next");
+ -accesskey => "n", -title => "Alt-n"}, "next");
}
-
git_header_html();
git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
git_print_header_div('summary', $project);
- git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);
+ git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
git_footer_html();
}
# log/feed of current (HEAD) branch, log of given branch, history of file/directory
my $head = $hash || 'HEAD';
- open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
- $head, "--", (defined $file_name ? $file_name : ())
- or die_error(undef, "Open git-rev-list failed");
- my @revlist = map { chomp; $_ } <$fd>;
- close $fd or die_error(undef, "Reading git-rev-list failed");
+ my @commitlist = parse_commits($head, 150);
my %latest_commit;
my %latest_date;
# browser (feed reader) prefers text/xml
$content_type = 'text/xml';
}
- if (defined($revlist[0])) {
- %latest_commit = parse_commit($revlist[0]);
+ if (defined($commitlist[0])) {
+ %latest_commit = %{$commitlist[0]};
%latest_date = parse_date($latest_commit{'author_epoch'});
print $cgi->header(
-type => $content_type,
}
# contents
- for (my $i = 0; $i <= $#revlist; $i++) {
- my $commit = $revlist[$i];
- my %co = parse_commit($commit);
+ for (my $i = 0; $i <= $#commitlist; $i++) {
+ my %co = %{$commitlist[$i]};
+ my $commit = $co{'id'};
# we read 150, we always show 30 and the ones more recent than 48 hours
if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
last;
my %cd = parse_date($co{'author_epoch'});
# get list of changed files
- open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+ open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
$co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
or next;
my @difftree = map { chomp; $_ } <$fd>;