提交 af6feeb2 编写于 作者: J Jakub Narebski 提交者: Junio C Hamano

gitweb: Refactor feed generation, make output prettier, add Atom feed

Add support for more modern Atom web feed format. Both RSS and Atom
feeds are generated by git_feed subroutine to avoid code duplication;
git_rss and git_atom are thin wrappers around git_feed. Add links to
Atom feed in HTML header and in page footer (but not in OPML; we
should use APP, Atom Publishing Proptocol instead).

Allow for feed generation for branches other than current (HEAD)
branch, and for generation of feeds for file or directory history.

Do not use "pre ${\sub_returning_scalar(...)} post" trick, but join
strings instead: "pre " . sub_returning_scalar(...) . " post".
Use href(-full=>1, ...) instead of hand-crafting gitweb urls.

Make output prettier:
* Use title similar to the title of web page
* Use project description (if exists) for description/subtitle
* Do not add anything (committer name, commit date) to feed entry title
* Wrap the commit message in <pre>
* Make file names into an unordered list
* Add links (diff, conditional blame, history) to the file list.

In addition to the above points, the attached patch emits a
Last-Changed: HTTP response header field, and doesn't compute the feed
body if the HTTP request type was HEAD. This helps keep the web server
load down for well-behaved feed readers that check if the feed needs
updating.

If browser (feed reader) sent Accept: header, and it prefers 'text/xml' type
to 'application/rss+xml' (in the case of RSS feed) or 'application/atom+xml'
(in the case of Atom feed), then use 'text/xml' as content type.

Both RSS and Atom feeds validate at http://feedvalidator.org
and at http://validator.w3.org/feed/Signed-off-by: NJakub Narebski <jnareb@gmail.com>
Signed-off-by: NAndreas Fuchs <asf@boinkor.net>
Signed-off-by: NJunio C Hamano <junkio@cox.net>
上级 bd5d1e42
...@@ -425,6 +425,7 @@ sub evaluate_path_info { ...@@ -425,6 +425,7 @@ sub evaluate_path_info {
"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,
...@@ -1202,6 +1203,8 @@ sub parse_date { ...@@ -1202,6 +1203,8 @@ sub parse_date {
$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);
...@@ -1209,7 +1212,7 @@ sub parse_date { ...@@ -1209,7 +1212,7 @@ 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;
...@@ -1672,14 +1675,17 @@ sub git_header_html { ...@@ -1672,14 +1675,17 @@ sub git_header_html {
} }
} }
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"));
} }
...@@ -1745,7 +1751,9 @@ sub git_footer_html { ...@@ -1745,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") . " ";
...@@ -4150,26 +4158,125 @@ sub git_shortlog { ...@@ -4150,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');
my %latest_commit;
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; print <<XML;
<?xml version="1.0" encoding="utf-8"?>
<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);
...@@ -4178,42 +4285,100 @@ sub git_rss { ...@@ -4178,42 +4285,100 @@ sub git_rss {
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 element (entry, item)
my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
if ($format eq 'rss') {
print "<item>\n" . print "<item>\n" .
"<title>" . "<title>" . esc_html($co{'title'}) . "</title>\n" .
sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
"</title>\n" .
"<author>" . esc_html($co{'author'}) . "</author>\n" . "<author>" . esc_html($co{'author'}) . "</author>\n" .
"<pubDate>$cd{'rfc2822'}</pubDate>\n" . "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
"<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . "<guid isPermaLink=\"true\">$co_url</guid>\n" .
"<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . "<link>$co_url</link>\n" .
"<description>" . esc_html($co{'title'}) . "</description>\n" . "<description>" . esc_html($co{'title'}) . "</description>\n" .
"<content:encoded>" . "<content:encoded>" .
"<![CDATA[\n"; "<![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";
foreach my $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(.*)$/)) {
next;
}
my $file = esc_path(unquote($7));
$file = to_utf8($file);
print "$file<br/>\n";
} }
print "]]>\n" . print "</pre><ul>\n";
foreach my $difftree_line (@difftree) {
my %difftree = parse_difftree_raw_line($difftree_line);
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');
}
# if this is not a feed of a file history
if (!defined $file_name || $file_name ne $file) {
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" . "</content:encoded>\n" .
"</item>\n"; "</item>\n";
} elsif ($format eq 'atom') {
print "</ul>\n</div>\n" .
"</content>\n" .
"</entry>\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 {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册