Merge branch 'jn/web'

* jn/web:
  gitweb: Finish restoring "blob" links in git_difftree_body
  gitweb: Refactor feed generation, make output prettier, add Atom feed
  gitweb: Add an option to href() to return full URL
  gitweb: New improved formatting of chunk header in diff
  gitweb: Default to $hash_base or HEAD for $hash in "commit" and "commitdiff"
  gitweb: Buffer diff header to deal with split patches + git_patchset_body refactoring
  gitweb: Protect against possible warning in git_commitdiff
This commit is contained in:
Junio C Hamano 2006-11-24 03:54:57 -08:00
commit 634b8d0514
2 changed files with 407 additions and 167 deletions

View File

@ -334,11 +334,13 @@ div.diff.extended_header {
padding: 2px 0px 2px 0px; padding: 2px 0px 2px 0px;
} }
div.diff a.list,
div.diff a.path, div.diff a.path,
div.diff a.hash { div.diff a.hash {
text-decoration: none; text-decoration: none;
} }
div.diff a.list:hover,
div.diff a.path:hover, div.diff a.path:hover,
div.diff a.hash:hover { div.diff a.hash:hover {
text-decoration: underline; text-decoration: underline;
@ -362,14 +364,25 @@ div.diff.rem {
color: #cc0000; color: #cc0000;
} }
div.diff.chunk_header a,
div.diff.chunk_header { div.diff.chunk_header {
color: #990099; color: #990099;
}
div.diff.chunk_header {
border: dotted #ffe0ff; border: dotted #ffe0ff;
border-width: 1px 0px 0px 0px; border-width: 1px 0px 0px 0px;
margin-top: 2px; margin-top: 2px;
} }
div.diff.chunk_header span.chunk_info {
background-color: #ffeeff;
}
div.diff.chunk_header span.section {
color: #aa22aa;
}
div.diff.incomplete { div.diff.incomplete {
color: #cccccc; color: #cccccc;
} }

View File

@ -425,6 +425,7 @@ my %actions = (
"history" => \&git_history, "history" => \&git_history,
"log" => \&git_log, "log" => \&git_log,
"rss" => \&git_rss, "rss" => \&git_rss,
"atom" => \&git_atom,
"search" => \&git_search, "search" => \&git_search,
"search_help" => \&git_search_help, "search_help" => \&git_search_help,
"shortlog" => \&git_shortlog, "shortlog" => \&git_shortlog,
@ -459,7 +460,8 @@ exit;
sub href(%) { sub href(%) {
my %params = @_; my %params = @_;
my $href = $my_uri; # default is to use -absolute url() i.e. $my_uri
my $href = $params{-full} ? $my_url : $my_uri;
# XXX: Warning: If you touch this, check the search form for updating, # XXX: Warning: If you touch this, check the search form for updating,
# too. # too.
@ -874,8 +876,10 @@ sub format_subject_html {
} }
} }
# format patch (diff) line (rather not to be used for diff headers)
sub format_diff_line { sub format_diff_line {
my $line = shift; my $line = shift;
my ($from, $to) = @_;
my $char = substr($line, 0, 1); my $char = substr($line, 0, 1);
my $diff_class = ""; my $diff_class = "";
@ -891,6 +895,25 @@ sub format_diff_line {
$diff_class = " incomplete"; $diff_class = " incomplete";
} }
$line = untabify($line); $line = untabify($line);
if ($from && $to && $line =~ m/^\@{2} /) {
my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
$line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
$from_lines = 0 unless defined $from_lines;
$to_lines = 0 unless defined $to_lines;
if ($from->{'href'}) {
$from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
-class=>"list"}, $from_text);
}
if ($to->{'href'}) {
$to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start",
-class=>"list"}, $to_text);
}
$line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
"<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
return "<div class=\"diff$diff_class\">$line</div>\n";
}
return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n"; return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
} }
@ -1176,10 +1199,12 @@ sub parse_date {
$date{'mday'} = $mday; $date{'mday'} = $mday;
$date{'day'} = $days[$wday]; $date{'day'} = $days[$wday];
$date{'month'} = $months[$mon]; $date{'month'} = $months[$mon];
$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
$days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
$date{'mday-time'} = sprintf "%d %s %02d:%02d", $date{'mday-time'} = sprintf "%d %s %02d:%02d",
$mday, $months[$mon], $hour ,$min; $mday, $months[$mon], $hour ,$min;
$date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
1900+$year, $mon, $mday, $hour ,$min, $sec;
$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
my $local = $epoch + ((int $1 + ($2/60)) * 3600); my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@ -1187,9 +1212,9 @@ sub parse_date {
$date{'hour_local'} = $hour; $date{'hour_local'} = $hour;
$date{'minute_local'} = $min; $date{'minute_local'} = $min;
$date{'tz_local'} = $tz; $date{'tz_local'} = $tz;
$date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s", $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
1900+$year, $mon+1, $mday, 1900+$year, $mon+1, $mday,
$hour, $min, $sec, $tz); $hour, $min, $sec, $tz);
return %date; return %date;
} }
@ -1650,14 +1675,17 @@ EOF
} }
} }
if (defined $project) { if (defined $project) {
printf('<link rel="alternate" title="%s log" '. printf('<link rel="alternate" title="%s log RSS feed" '.
'href="%s" type="application/rss+xml"/>'."\n", 'href="%s" type="application/rss+xml" />'."\n",
esc_param($project), href(action=>"rss")); esc_param($project), href(action=>"rss"));
printf('<link rel="alternate" title="%s log Atom feed" '.
'href="%s" type="application/atom+xml" />'."\n",
esc_param($project), href(action=>"atom"));
} else { } else {
printf('<link rel="alternate" title="%s projects list" '. printf('<link rel="alternate" title="%s projects list" '.
'href="%s" type="text/plain; charset=utf-8"/>'."\n", 'href="%s" type="text/plain; charset=utf-8"/>'."\n",
$site_name, href(project=>undef, action=>"project_index")); $site_name, href(project=>undef, action=>"project_index"));
printf('<link rel="alternate" title="%s projects logs" '. printf('<link rel="alternate" title="%s projects feeds" '.
'href="%s" type="text/x-opml"/>'."\n", 'href="%s" type="text/x-opml"/>'."\n",
$site_name, href(project=>undef, action=>"opml")); $site_name, href(project=>undef, action=>"opml"));
} }
@ -1723,7 +1751,9 @@ sub git_footer_html {
print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
} }
print $cgi->a({-href => href(action=>"rss"), print $cgi->a({-href => href(action=>"rss"),
-class => "rss_logo"}, "RSS") . "\n"; -class => "rss_logo"}, "RSS") . " ";
print $cgi->a({-href => href(action=>"atom"),
-class => "rss_logo"}, "Atom") . "\n";
} else { } else {
print $cgi->a({-href => href(project=>undef, action=>"opml"), print $cgi->a({-href => href(project=>undef, action=>"opml"),
-class => "rss_logo"}, "OPML") . " "; -class => "rss_logo"}, "OPML") . " ";
@ -2062,7 +2092,11 @@ sub git_difftree_body {
# link to patch # link to patch
$patchno++; $patchno++;
print $cgi->a({-href => "#patch$patchno"}, "patch"); print $cgi->a({-href => "#patch$patchno"}, "patch");
print " | ";
} }
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'})},
"blob") . " | ";
print "</td>\n"; print "</td>\n";
} elsif ($diff{'status'} eq "D") { # deleted } elsif ($diff{'status'} eq "D") { # deleted
@ -2082,13 +2116,11 @@ sub git_difftree_body {
} }
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
hash_base=>$parent, file_name=>$diff{'file'})}, hash_base=>$parent, file_name=>$diff{'file'})},
"blob") . " | "; "blob") . " | ";
if ($have_blame) { if ($have_blame) {
print $cgi->a({-href => print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
href(action=>"blame", file_name=>$diff{'file'})},
hash_base=>$parent, "blame") . " | ";
file_name=>$diff{'file'})},
"blame") . " | ";
} }
print $cgi->a({-href => href(action=>"history", hash_base=>$parent, print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
file_name=>$diff{'file'})}, file_name=>$diff{'file'})},
@ -2133,13 +2165,12 @@ sub git_difftree_body {
" | "; " | ";
} }
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'})}, hash_base=>$hash, file_name=>$diff{'file'})},
"blob") . " | "; "blob") . " | ";
if ($have_blame) { if ($have_blame) {
print $cgi->a({-href => href(action=>"blame", print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
hash_base=>$hash, file_name=>$diff{'file'})},
file_name=>$diff{'file'})}, "blame") . " | ";
"blame") . " | ";
} }
print $cgi->a({-href => href(action=>"history", hash_base=>$hash, print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
file_name=>$diff{'file'})}, file_name=>$diff{'file'})},
@ -2178,17 +2209,16 @@ sub git_difftree_body {
"diff") . "diff") .
" | "; " | ";
} }
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$parent, file_name=>$diff{'from_file'})}, hash_base=>$parent, file_name=>$diff{'to_file'})},
"blob") . " | "; "blob") . " | ";
if ($have_blame) { if ($have_blame) {
print $cgi->a({-href => href(action=>"blame", print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
hash_base=>$hash, file_name=>$diff{'to_file'})},
file_name=>$diff{'to_file'})}, "blame") . " | ";
"blame") . " | ";
} }
print $cgi->a({-href => href(action=>"history", hash_base=>$parent, print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
file_name=>$diff{'from_file'})}, file_name=>$diff{'to_file'})},
"history"); "history");
print "</td>\n"; print "</td>\n";
@ -2202,31 +2232,56 @@ sub git_patchset_body {
my ($fd, $difftree, $hash, $hash_parent) = @_; my ($fd, $difftree, $hash, $hash_parent) = @_;
my $patch_idx = 0; my $patch_idx = 0;
my $in_header = 0; my $patch_line;
my $patch_found = 0;
my $diffinfo; my $diffinfo;
my (%from, %to); my (%from, %to);
my ($from_id, $to_id);
print "<div class=\"patchset\">\n"; print "<div class=\"patchset\">\n";
LINE: # skip to first patch
while (my $patch_line = <$fd>) { while ($patch_line = <$fd>) {
chomp $patch_line; chomp $patch_line;
if ($patch_line =~ m/^diff /) { # "git diff" header last if ($patch_line =~ m/^diff /);
# beginning of patch (in patchset) }
if ($patch_found) {
# close extended header for previous empty patch PATCH:
if ($in_header) { while ($patch_line) {
print "</div>\n" # class="diff extended_header" my @diff_header;
}
# close previous patch # git diff header
print "</div>\n"; # class="patch" #assert($patch_line =~ m/^diff /) if DEBUG;
} else { #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
# first patch in patchset push @diff_header, $patch_line;
$patch_found = 1;
# extended diff header
EXTENDED_HEADER:
while ($patch_line = <$fd>) {
chomp $patch_line;
last EXTENDED_HEADER if ($patch_line =~ m/^--- /);
if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
$from_id = $1;
$to_id = $2;
} }
print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
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
# and parse raw git-diff line if needed
if (defined $diffinfo &&
$diffinfo->{'from_id'} eq $from_id &&
$diffinfo->{'to_id'} eq $to_id) {
# this is split patch
print "<div class=\"patch cont\">\n";
} else {
# advance raw git-diff output if needed
$patch_idx++ if defined $diffinfo;
# read and prepare patch information # read and prepare patch information
if (ref($difftree->[$patch_idx]) eq "HASH") { if (ref($difftree->[$patch_idx]) eq "HASH") {
@ -2247,100 +2302,112 @@ sub git_patchset_body {
hash=>$diffinfo->{'to_id'}, hash=>$diffinfo->{'to_id'},
file_name=>$to{'file'}); file_name=>$to{'file'});
} }
$patch_idx++; # this is first patch for raw difftree line with $patch_idx index
# we index @$difftree array from 0, but number patches from 1
# print "git diff" header print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
$patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
if ($from{'href'}) {
$patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
'a/' . esc_path($from{'file'}));
} else { # file was added
$patch_line .= 'a/' . esc_path($from{'file'});
}
$patch_line .= ' ';
if ($to{'href'}) {
$patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
'b/' . esc_path($to{'file'}));
} else { # file was deleted
$patch_line .= 'b/' . esc_path($to{'file'});
}
print "<div class=\"diff header\">$patch_line</div>\n";
print "<div class=\"diff extended_header\">\n";
$in_header = 1;
next LINE;
} }
if ($in_header) { # print "git diff" header
if ($patch_line !~ m/^---/) { $patch_line = shift @diff_header;
# match <path> $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { if ($from{'href'}) {
$patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
esc_path($from{'file'})); 'a/' . esc_path($from{'file'}));
} } else { # file was added
if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { $patch_line .= 'a/' . esc_path($from{'file'});
$patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, }
esc_path($to{'file'})); $patch_line .= ' ';
} if ($to{'href'}) {
# match <mode> $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
if ($patch_line =~ m/\s(\d{6})$/) { 'b/' . esc_path($to{'file'}));
$patch_line .= '<span class="info"> (' . } else { # file was deleted
file_type_long($1) . $patch_line .= 'b/' . esc_path($to{'file'});
')</span>'; }
} print "<div class=\"diff header\">$patch_line</div>\n";
# match <hash>
if ($patch_line =~ m/^index/) {
my ($from_link, $to_link);
if ($from{'href'}) {
$from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
substr($diffinfo->{'from_id'},0,7));
} else {
$from_link = '0' x 7;
}
if ($to{'href'}) {
$to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
substr($diffinfo->{'to_id'},0,7));
} else {
$to_link = '0' x 7;
}
my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
$patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
}
print $patch_line . "<br/>\n";
} else {
#$in_header && $patch_line =~ m/^---/;
print "</div>\n"; # class="diff extended_header"
$in_header = 0;
# print extended diff header
print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
EXTENDED_HEADER:
foreach $patch_line (@diff_header) {
# 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'}));
}
if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
$patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"},
esc_path($to{'file'}));
}
# match <mode>
if ($patch_line =~ m/\s(\d{6})$/) {
$patch_line .= '<span class="info"> (' .
file_type_long($1) .
')</span>';
}
# match <hash>
if ($patch_line =~ m/^index/) {
my ($from_link, $to_link);
if ($from{'href'}) { if ($from{'href'}) {
$patch_line = '--- a/' . $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
$cgi->a({-href=>$from{'href'}, -class=>"path"}, substr($diffinfo->{'from_id'},0,7));
esc_path($from{'file'})); } else {
$from_link = '0' x 7;
} }
print "<div class=\"diff from_file\">$patch_line</div>\n";
$patch_line = <$fd>;
chomp $patch_line;
#$patch_line =~ m/^+++/;
if ($to{'href'}) { if ($to{'href'}) {
$patch_line = '+++ b/' . $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
$cgi->a({-href=>$to{'href'}, -class=>"path"}, substr($diffinfo->{'to_id'},0,7));
esc_path($to{'file'})); } else {
$to_link = '0' x 7;
} }
print "<div class=\"diff to_file\">$patch_line</div>\n"; #affirm {
# my ($from_hash, $to_hash) =
# ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/);
# my ($from_id, $to_id) =
# ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
# ($from_hash eq $from_id) && ($to_hash eq $to_id);
#} if DEBUG;
my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
$patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
} }
print $patch_line . "<br/>\n";
}
print "</div>\n" if (@diff_header > 0); # class="diff extended_header"
next LINE; # from-file/to-file diff header
$patch_line = $last_patch_line;
#assert($patch_line =~ m/^---/) if DEBUG;
if ($from{'href'}) {
$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'}) {
$patch_line = '+++ b/' .
$cgi->a({-href=>$to{'href'}, -class=>"path"},
esc_path($to{'file'}));
}
print "<div class=\"diff to_file\">$patch_line</div>\n";
# the patch itself
LINE:
while ($patch_line = <$fd>) {
chomp $patch_line;
next PATCH if ($patch_line =~ m/^diff /);
print format_diff_line($patch_line, \%from, \%to);
} }
print format_diff_line($patch_line); } continue {
print "</div>\n"; # class="patch"
} }
print "</div>\n" if $in_header; # extended header
print "</div>\n" if $patch_found; # class="patch"
print "</div>\n"; # class="patchset" print "</div>\n"; # class="patchset"
} }
@ -3392,6 +3459,7 @@ sub git_log {
} }
sub git_commit { sub git_commit {
$hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash); my %co = parse_commit($hash);
if (!%co) { if (!%co) {
die_error(undef, "Unknown commit object"); die_error(undef, "Unknown commit object");
@ -3669,6 +3737,7 @@ sub git_blobdiff_plain {
sub git_commitdiff { sub git_commitdiff {
my $format = shift || 'html'; my $format = shift || 'html';
$hash ||= $hash_base || "HEAD";
my %co = parse_commit($hash); my %co = parse_commit($hash);
if (!%co) { if (!%co) {
die_error(undef, "Unknown commit object"); die_error(undef, "Unknown commit object");
@ -3731,7 +3800,8 @@ sub git_commitdiff {
$hash_parent, $hash, "--" $hash_parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed"); or die_error(undef, "Open git-diff-tree failed");
while (chomp(my $line = <$fd>)) { while (my $line = <$fd>) {
chomp $line;
# empty line ends raw part of diff-tree output # empty line ends raw part of diff-tree output
last unless $line; last unless $line;
push @difftree, $line; push @difftree, $line;
@ -4088,26 +4158,125 @@ sub git_shortlog {
} }
## ...................................................................... ## ......................................................................
## feeds (RSS, OPML) ## feeds (RSS, Atom; OPML)
sub git_rss { sub git_feed {
# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ my $format = shift || 'atom';
my ($have_blame) = gitweb_check_feature('blame');
# Atom: http://www.atomenabled.org/developers/syndication/
# RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
if ($format ne 'rss' && $format ne 'atom') {
die_error(undef, "Unknown web feed format");
}
# 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", open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
git_get_head_hash($project), "--" $head, "--", (defined $file_name ? $file_name : ())
or die_error(undef, "Open git-rev-list failed"); or die_error(undef, "Open git-rev-list failed");
my @revlist = map { chomp; $_ } <$fd>; my @revlist = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-rev-list failed"); close $fd or die_error(undef, "Reading git-rev-list failed");
print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
print <<XML; my %latest_commit;
<?xml version="1.0" encoding="utf-8"?> my %latest_date;
my $content_type = "application/$format+xml";
if (defined $cgi->http('HTTP_ACCEPT') &&
$cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
# browser (feed reader) prefers text/xml
$content_type = 'text/xml';
}
if (defined($revlist[0])) {
%latest_commit = parse_commit($revlist[0]);
%latest_date = parse_date($latest_commit{'committer_epoch'});
print $cgi->header(
-type => $content_type,
-charset => 'utf-8',
-last_modified => $latest_date{'rfc2822'});
} else {
print $cgi->header(
-type => $content_type,
-charset => 'utf-8');
}
# Optimization: skip generating the body if client asks only
# for Last-Modified date.
return if ($cgi->request_method() eq 'HEAD');
# header variables
my $title = "$site_name - $project/$action";
my $feed_type = 'log';
if (defined $hash) {
$title .= " - '$hash'";
$feed_type = 'branch log';
if (defined $file_name) {
$title .= " :: $file_name";
$feed_type = 'history';
}
} elsif (defined $file_name) {
$title .= " - $file_name";
$feed_type = 'history';
}
$title .= " $feed_type";
my $descr = git_get_project_description($project);
if (defined $descr) {
$descr = esc_html($descr);
} else {
$descr = "$project " .
($format eq 'rss' ? 'RSS' : 'Atom') .
" feed";
}
my $owner = git_get_project_owner($project);
$owner = esc_html($owner);
#header
my $alt_url;
if (defined $file_name) {
$alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
} elsif (defined $hash) {
$alt_url = href(-full=>1, action=>"log", hash=>$hash);
} else {
$alt_url = href(-full=>1, action=>"summary");
}
print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
if ($format eq 'rss') {
print <<XML;
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"> <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel> <channel>
<title>$project $my_uri $my_url</title>
<link>${\esc_html("$my_url?p=$project;a=summary")}</link>
<description>$project log</description>
<language>en</language>
XML XML
print "<title>$title</title>\n" .
"<link>$alt_url</link>\n" .
"<description>$descr</description>\n" .
"<language>en</language>\n";
} elsif ($format eq 'atom') {
print <<XML;
<feed xmlns="http://www.w3.org/2005/Atom">
XML
print "<title>$title</title>\n" .
"<subtitle>$descr</subtitle>\n" .
'<link rel="alternate" type="text/html" href="' .
$alt_url . '" />' . "\n" .
'<link rel="self" type="' . $content_type . '" href="' .
$cgi->self_url() . '" />' . "\n" .
"<id>" . href(-full=>1) . "</id>\n" .
# use project owner for feed author
"<author><name>$owner</name></author>\n";
if (defined $favicon) {
print "<icon>" . esc_url($favicon) . "</icon>\n";
}
if (defined $logo_url) {
# not twice as wide as tall: 72 x 27 pixels
print "<logo>" . esc_url($logo_url) . "</logo>\n";
}
if (! %latest_date) {
# dummy date to keep the feed valid until commits trickle in:
print "<updated>1970-01-01T00:00:00Z</updated>\n";
} else {
print "<updated>$latest_date{'iso-8601'}</updated>\n";
}
}
# contents
for (my $i = 0; $i <= $#revlist; $i++) { for (my $i = 0; $i <= $#revlist; $i++) {
my $commit = $revlist[$i]; my $commit = $revlist[$i];
my %co = parse_commit($commit); my %co = parse_commit($commit);
@ -4116,42 +4285,100 @@ XML
last; last;
} }
my %cd = parse_date($co{'committer_epoch'}); my %cd = parse_date($co{'committer_epoch'});
# get list of changed files
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
$co{'parent'}, $co{'id'}, "--" $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
or next; or next;
my @difftree = map { chomp; $_ } <$fd>; my @difftree = map { chomp; $_ } <$fd>;
close $fd close $fd
or next; or next;
print "<item>\n" .
"<title>" . # print element (entry, item)
sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
"</title>\n" . if ($format eq 'rss') {
"<author>" . esc_html($co{'author'}) . "</author>\n" . print "<item>\n" .
"<pubDate>$cd{'rfc2822'}</pubDate>\n" . "<title>" . esc_html($co{'title'}) . "</title>\n" .
"<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . "<author>" . esc_html($co{'author'}) . "</author>\n" .
"<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
"<description>" . esc_html($co{'title'}) . "</description>\n" . "<guid isPermaLink=\"true\">$co_url</guid>\n" .
"<content:encoded>" . "<link>$co_url</link>\n" .
"<![CDATA[\n"; "<description>" . esc_html($co{'title'}) . "</description>\n" .
"<content:encoded>" .
"<![CDATA[\n";
} elsif ($format eq 'atom') {
print "<entry>\n" .
"<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
"<updated>$cd{'iso-8601'}</updated>\n" .
"<author><name>" . esc_html($co{'author_name'}) . "</name></author>\n" .
# use committer for contributor
"<contributor><name>" . esc_html($co{'committer_name'}) . "</name></contributor>\n" .
"<published>$cd{'iso-8601'}</published>\n" .
"<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
"<id>$co_url</id>\n" .
"<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
"<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
}
my $comment = $co{'comment'}; my $comment = $co{'comment'};
print "<pre>\n";
foreach my $line (@$comment) { foreach my $line (@$comment) {
$line = to_utf8($line); $line = esc_html($line);
print "$line<br/>\n"; print "$line\n";
} }
print "<br/>\n"; print "</pre><ul>\n";
foreach my $line (@difftree) { foreach my $difftree_line (@difftree) {
if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { my %difftree = parse_difftree_raw_line($difftree_line);
next; next if !$difftree{'from_id'};
my $file = $difftree{'file'} || $difftree{'to_file'};
print "<li>" .
"[" .
$cgi->a({-href => href(-full=>1, action=>"blobdiff",
hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
file_name=>$file, file_parent=>$difftree{'from_file'}),
-title => "diff"}, 'D');
if ($have_blame) {
print $cgi->a({-href => href(-full=>1, action=>"blame",
file_name=>$file, hash_base=>$commit),
-title => "blame"}, 'B');
} }
my $file = esc_path(unquote($7)); # if this is not a feed of a file history
$file = to_utf8($file); if (!defined $file_name || $file_name ne $file) {
print "$file<br/>\n"; print $cgi->a({-href => href(-full=>1, action=>"history",
file_name=>$file, hash=>$commit),
-title => "history"}, 'H');
}
$file = esc_path($file);
print "] ".
"$file</li>\n";
}
if ($format eq 'rss') {
print "</ul>]]>\n" .
"</content:encoded>\n" .
"</item>\n";
} elsif ($format eq 'atom') {
print "</ul>\n</div>\n" .
"</content>\n" .
"</entry>\n";
} }
print "]]>\n" .
"</content:encoded>\n" .
"</item>\n";
} }
print "</channel></rss>";
# end of feed
if ($format eq 'rss') {
print "</channel>\n</rss>\n";
} elsif ($format eq 'atom') {
print "</feed>\n";
}
}
sub git_rss {
git_feed('rss');
}
sub git_atom {
git_feed('atom');
} }
sub git_opml { sub git_opml {