Skip to content

Commit af6feeb

Browse files
jnarebJunio C Hamano
authored andcommitted
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: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Andreas Fuchs <asf@boinkor.net> Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent bd5d1e4 commit af6feeb

File tree

1 file changed

+210
-45
lines changed

1 file changed

+210
-45
lines changed

gitweb/gitweb.perl

Lines changed: 210 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ sub evaluate_path_info {
425425
"history" => \&git_history,
426426
"log" => \&git_log,
427427
"rss" => \&git_rss,
428+
"atom" => \&git_atom,
428429
"search" => \&git_search,
429430
"search_help" => \&git_search_help,
430431
"shortlog" => \&git_shortlog,
@@ -1198,20 +1199,22 @@ sub parse_date {
11981199
$date{'mday'} = $mday;
11991200
$date{'day'} = $days[$wday];
12001201
$date{'month'} = $months[$mon];
1201-
$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
1202-
$days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
1202+
$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
1203+
$days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
12031204
$date{'mday-time'} = sprintf "%d %s %02d:%02d",
12041205
$mday, $months[$mon], $hour ,$min;
1206+
$date{'iso-8601'} = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
1207+
1900+$year, $mon, $mday, $hour ,$min, $sec;
12051208

12061209
$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
12071210
my $local = $epoch + ((int $1 + ($2/60)) * 3600);
12081211
($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
12091212
$date{'hour_local'} = $hour;
12101213
$date{'minute_local'} = $min;
12111214
$date{'tz_local'} = $tz;
1212-
$date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
1213-
1900+$year, $mon+1, $mday,
1214-
$hour, $min, $sec, $tz);
1215+
$date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
1216+
1900+$year, $mon+1, $mday,
1217+
$hour, $min, $sec, $tz);
12151218
return %date;
12161219
}
12171220

@@ -1672,14 +1675,17 @@ sub git_header_html {
16721675
}
16731676
}
16741677
if (defined $project) {
1675-
printf('<link rel="alternate" title="%s log" '.
1676-
'href="%s" type="application/rss+xml"/>'."\n",
1678+
printf('<link rel="alternate" title="%s log RSS feed" '.
1679+
'href="%s" type="application/rss+xml" />'."\n",
16771680
esc_param($project), href(action=>"rss"));
1681+
printf('<link rel="alternate" title="%s log Atom feed" '.
1682+
'href="%s" type="application/atom+xml" />'."\n",
1683+
esc_param($project), href(action=>"atom"));
16781684
} else {
16791685
printf('<link rel="alternate" title="%s projects list" '.
16801686
'href="%s" type="text/plain; charset=utf-8"/>'."\n",
16811687
$site_name, href(project=>undef, action=>"project_index"));
1682-
printf('<link rel="alternate" title="%s projects logs" '.
1688+
printf('<link rel="alternate" title="%s projects feeds" '.
16831689
'href="%s" type="text/x-opml"/>'."\n",
16841690
$site_name, href(project=>undef, action=>"opml"));
16851691
}
@@ -1745,7 +1751,9 @@ sub git_footer_html {
17451751
print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
17461752
}
17471753
print $cgi->a({-href => href(action=>"rss"),
1748-
-class => "rss_logo"}, "RSS") . "\n";
1754+
-class => "rss_logo"}, "RSS") . " ";
1755+
print $cgi->a({-href => href(action=>"atom"),
1756+
-class => "rss_logo"}, "Atom") . "\n";
17491757
} else {
17501758
print $cgi->a({-href => href(project=>undef, action=>"opml"),
17511759
-class => "rss_logo"}, "OPML") . " ";
@@ -4150,26 +4158,125 @@ sub git_shortlog {
41504158
}
41514159

41524160
## ......................................................................
4153-
## feeds (RSS, OPML)
4161+
## feeds (RSS, Atom; OPML)
41544162

4155-
sub git_rss {
4156-
# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
4163+
sub git_feed {
4164+
my $format = shift || 'atom';
4165+
my ($have_blame) = gitweb_check_feature('blame');
4166+
4167+
# Atom: http://www.atomenabled.org/developers/syndication/
4168+
# RSS: http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
4169+
if ($format ne 'rss' && $format ne 'atom') {
4170+
die_error(undef, "Unknown web feed format");
4171+
}
4172+
4173+
# log/feed of current (HEAD) branch, log of given branch, history of file/directory
4174+
my $head = $hash || 'HEAD';
41574175
open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
4158-
git_get_head_hash($project), "--"
4176+
$head, "--", (defined $file_name ? $file_name : ())
41594177
or die_error(undef, "Open git-rev-list failed");
41604178
my @revlist = map { chomp; $_ } <$fd>;
41614179
close $fd or die_error(undef, "Reading git-rev-list failed");
4162-
print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
4163-
print <<XML;
4164-
<?xml version="1.0" encoding="utf-8"?>
4180+
4181+
my %latest_commit;
4182+
my %latest_date;
4183+
my $content_type = "application/$format+xml";
4184+
if (defined $cgi->http('HTTP_ACCEPT') &&
4185+
$cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
4186+
# browser (feed reader) prefers text/xml
4187+
$content_type = 'text/xml';
4188+
}
4189+
if (defined($revlist[0])) {
4190+
%latest_commit = parse_commit($revlist[0]);
4191+
%latest_date = parse_date($latest_commit{'committer_epoch'});
4192+
print $cgi->header(
4193+
-type => $content_type,
4194+
-charset => 'utf-8',
4195+
-last_modified => $latest_date{'rfc2822'});
4196+
} else {
4197+
print $cgi->header(
4198+
-type => $content_type,
4199+
-charset => 'utf-8');
4200+
}
4201+
4202+
# Optimization: skip generating the body if client asks only
4203+
# for Last-Modified date.
4204+
return if ($cgi->request_method() eq 'HEAD');
4205+
4206+
# header variables
4207+
my $title = "$site_name - $project/$action";
4208+
my $feed_type = 'log';
4209+
if (defined $hash) {
4210+
$title .= " - '$hash'";
4211+
$feed_type = 'branch log';
4212+
if (defined $file_name) {
4213+
$title .= " :: $file_name";
4214+
$feed_type = 'history';
4215+
}
4216+
} elsif (defined $file_name) {
4217+
$title .= " - $file_name";
4218+
$feed_type = 'history';
4219+
}
4220+
$title .= " $feed_type";
4221+
my $descr = git_get_project_description($project);
4222+
if (defined $descr) {
4223+
$descr = esc_html($descr);
4224+
} else {
4225+
$descr = "$project " .
4226+
($format eq 'rss' ? 'RSS' : 'Atom') .
4227+
" feed";
4228+
}
4229+
my $owner = git_get_project_owner($project);
4230+
$owner = esc_html($owner);
4231+
4232+
#header
4233+
my $alt_url;
4234+
if (defined $file_name) {
4235+
$alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
4236+
} elsif (defined $hash) {
4237+
$alt_url = href(-full=>1, action=>"log", hash=>$hash);
4238+
} else {
4239+
$alt_url = href(-full=>1, action=>"summary");
4240+
}
4241+
print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
4242+
if ($format eq 'rss') {
4243+
print <<XML;
41654244
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
41664245
<channel>
4167-
<title>$project $my_uri $my_url</title>
4168-
<link>${\esc_html("$my_url?p=$project;a=summary")}</link>
4169-
<description>$project log</description>
4170-
<language>en</language>
41714246
XML
4247+
print "<title>$title</title>\n" .
4248+
"<link>$alt_url</link>\n" .
4249+
"<description>$descr</description>\n" .
4250+
"<language>en</language>\n";
4251+
} elsif ($format eq 'atom') {
4252+
print <<XML;
4253+
<feed xmlns="http://www.w3.org/2005/Atom">
4254+
XML
4255+
print "<title>$title</title>\n" .
4256+
"<subtitle>$descr</subtitle>\n" .
4257+
'<link rel="alternate" type="text/html" href="' .
4258+
$alt_url . '" />' . "\n" .
4259+
'<link rel="self" type="' . $content_type . '" href="' .
4260+
$cgi->self_url() . '" />' . "\n" .
4261+
"<id>" . href(-full=>1) . "</id>\n" .
4262+
# use project owner for feed author
4263+
"<author><name>$owner</name></author>\n";
4264+
if (defined $favicon) {
4265+
print "<icon>" . esc_url($favicon) . "</icon>\n";
4266+
}
4267+
if (defined $logo_url) {
4268+
# not twice as wide as tall: 72 x 27 pixels
4269+
print "<logo>" . esc_url($logo_url) . "</logo>\n";
4270+
}
4271+
if (! %latest_date) {
4272+
# dummy date to keep the feed valid until commits trickle in:
4273+
print "<updated>1970-01-01T00:00:00Z</updated>\n";
4274+
} else {
4275+
print "<updated>$latest_date{'iso-8601'}</updated>\n";
4276+
}
4277+
}
41724278

4279+
# contents
41734280
for (my $i = 0; $i <= $#revlist; $i++) {
41744281
my $commit = $revlist[$i];
41754282
my %co = parse_commit($commit);
@@ -4178,42 +4285,100 @@ sub git_rss {
41784285
last;
41794286
}
41804287
my %cd = parse_date($co{'committer_epoch'});
4288+
4289+
# get list of changed files
41814290
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
4182-
$co{'parent'}, $co{'id'}, "--"
4291+
$co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
41834292
or next;
41844293
my @difftree = map { chomp; $_ } <$fd>;
41854294
close $fd
41864295
or next;
4187-
print "<item>\n" .
4188-
"<title>" .
4189-
sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
4190-
"</title>\n" .
4191-
"<author>" . esc_html($co{'author'}) . "</author>\n" .
4192-
"<pubDate>$cd{'rfc2822'}</pubDate>\n" .
4193-
"<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" .
4194-
"<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" .
4195-
"<description>" . esc_html($co{'title'}) . "</description>\n" .
4196-
"<content:encoded>" .
4197-
"<![CDATA[\n";
4296+
4297+
# print element (entry, item)
4298+
my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
4299+
if ($format eq 'rss') {
4300+
print "<item>\n" .
4301+
"<title>" . esc_html($co{'title'}) . "</title>\n" .
4302+
"<author>" . esc_html($co{'author'}) . "</author>\n" .
4303+
"<pubDate>$cd{'rfc2822'}</pubDate>\n" .
4304+
"<guid isPermaLink=\"true\">$co_url</guid>\n" .
4305+
"<link>$co_url</link>\n" .
4306+
"<description>" . esc_html($co{'title'}) . "</description>\n" .
4307+
"<content:encoded>" .
4308+
"<![CDATA[\n";
4309+
} elsif ($format eq 'atom') {
4310+
print "<entry>\n" .
4311+
"<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
4312+
"<updated>$cd{'iso-8601'}</updated>\n" .
4313+
"<author><name>" . esc_html($co{'author_name'}) . "</name></author>\n" .
4314+
# use committer for contributor
4315+
"<contributor><name>" . esc_html($co{'committer_name'}) . "</name></contributor>\n" .
4316+
"<published>$cd{'iso-8601'}</published>\n" .
4317+
"<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
4318+
"<id>$co_url</id>\n" .
4319+
"<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
4320+
"<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
4321+
}
41984322
my $comment = $co{'comment'};
4323+
print "<pre>\n";
41994324
foreach my $line (@$comment) {
4200-
$line = to_utf8($line);
4201-
print "$line<br/>\n";
4325+
$line = esc_html($line);
4326+
print "$line\n";
42024327
}
4203-
print "<br/>\n";
4204-
foreach my $line (@difftree) {
4205-
if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
4206-
next;
4328+
print "</pre><ul>\n";
4329+
foreach my $difftree_line (@difftree) {
4330+
my %difftree = parse_difftree_raw_line($difftree_line);
4331+
next if !$difftree{'from_id'};
4332+
4333+
my $file = $difftree{'file'} || $difftree{'to_file'};
4334+
4335+
print "<li>" .
4336+
"[" .
4337+
$cgi->a({-href => href(-full=>1, action=>"blobdiff",
4338+
hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
4339+
hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
4340+
file_name=>$file, file_parent=>$difftree{'from_file'}),
4341+
-title => "diff"}, 'D');
4342+
if ($have_blame) {
4343+
print $cgi->a({-href => href(-full=>1, action=>"blame",
4344+
file_name=>$file, hash_base=>$commit),
4345+
-title => "blame"}, 'B');
42074346
}
4208-
my $file = esc_path(unquote($7));
4209-
$file = to_utf8($file);
4210-
print "$file<br/>\n";
4347+
# if this is not a feed of a file history
4348+
if (!defined $file_name || $file_name ne $file) {
4349+
print $cgi->a({-href => href(-full=>1, action=>"history",
4350+
file_name=>$file, hash=>$commit),
4351+
-title => "history"}, 'H');
4352+
}
4353+
$file = esc_path($file);
4354+
print "] ".
4355+
"$file</li>\n";
4356+
}
4357+
if ($format eq 'rss') {
4358+
print "</ul>]]>\n" .
4359+
"</content:encoded>\n" .
4360+
"</item>\n";
4361+
} elsif ($format eq 'atom') {
4362+
print "</ul>\n</div>\n" .
4363+
"</content>\n" .
4364+
"</entry>\n";
42114365
}
4212-
print "]]>\n" .
4213-
"</content:encoded>\n" .
4214-
"</item>\n";
42154366
}
4216-
print "</channel></rss>";
4367+
4368+
# end of feed
4369+
if ($format eq 'rss') {
4370+
print "</channel>\n</rss>\n";
4371+
} elsif ($format eq 'atom') {
4372+
print "</feed>\n";
4373+
}
4374+
}
4375+
4376+
sub git_rss {
4377+
git_feed('rss');
4378+
}
4379+
4380+
sub git_atom {
4381+
git_feed('atom');
42174382
}
42184383

42194384
sub git_opml {

0 commit comments

Comments
 (0)