if $type
or return;
while (my $line = <$fd>) {
chomp $line;
- if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) {
+ if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
if (defined $refs{$1}) {
push @{$refs{$1}}, $2;
} else {
@@ -1198,10 +1207,12 @@ sub parse_date {
$date{'mday'} = $mday;
$date{'day'} = $days[$wday];
$date{'month'} = $months[$mon];
- $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
- $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
+ $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
+ $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
$date{'mday-time'} = sprintf "%d %s %02d:%02d",
$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])$/;
my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@@ -1209,9 +1220,9 @@ sub parse_date {
$date{'hour_local'} = $hour;
$date{'minute_local'} = $min;
$date{'tz_local'} = $tz;
- $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
- 1900+$year, $mon+1, $mday,
- $hour, $min, $sec, $tz);
+ $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
+ 1900+$year, $mon+1, $mday,
+ $hour, $min, $sec, $tz);
return %date;
}
@@ -1284,8 +1295,9 @@ sub parse_commit {
$co{'author'} = $1;
$co{'author_epoch'} = $2;
$co{'author_tz'} = $3;
- if ($co{'author'} =~ m/^([^<]+) ) {
- $co{'author_name'} = $1;
+ if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+ $co{'author_name'} = $1;
+ $co{'author_email'} = $2;
} else {
$co{'author_name'} = $co{'author'};
}
@@ -1294,7 +1306,12 @@ sub parse_commit {
$co{'committer_epoch'} = $2;
$co{'committer_tz'} = $3;
$co{'committer_name'} = $co{'committer'};
- $co{'committer_name'} =~ s/ <.*//;
+ if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
+ $co{'committer_name'} = $1;
+ $co{'committer_email'} = $2;
+ } else {
+ $co{'committer_name'} = $co{'committer'};
+ }
}
}
if (!defined $co{'tree'}) {
@@ -1672,14 +1689,17 @@ EOF
}
}
if (defined $project) {
- printf(''."\n",
+ printf(''."\n",
esc_param($project), href(action=>"rss"));
+ printf(''."\n",
+ esc_param($project), href(action=>"atom"));
} else {
printf(''."\n",
$site_name, href(project=>undef, action=>"project_index"));
- printf(''."\n",
$site_name, href(project=>undef, action=>"opml"));
}
@@ -1745,7 +1765,9 @@ sub git_footer_html {
print "\n";
}
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 {
print $cgi->a({-href => href(project=>undef, action=>"opml"),
-class => "rss_logo"}, "OPML") . " ";
@@ -2084,7 +2106,11 @@ sub git_difftree_body {
# link to patch
$patchno++;
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 "\n";
} elsif ($diff{'status'} eq "D") { # deleted
@@ -2104,13 +2130,11 @@ sub git_difftree_body {
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
hash_base=>$parent, file_name=>$diff{'file'})},
- "blob") . " | ";
+ "blob") . " | ";
if ($have_blame) {
- print $cgi->a({-href =>
- href(action=>"blame",
- hash_base=>$parent,
- file_name=>$diff{'file'})},
- "blame") . " | ";
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
}
print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
file_name=>$diff{'file'})},
@@ -2155,13 +2179,12 @@ sub git_difftree_body {
" | ";
}
print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
- hash_base=>$hash, file_name=>$diff{'file'})},
- "blob") . " | ";
+ hash_base=>$hash, file_name=>$diff{'file'})},
+ "blob") . " | ";
if ($have_blame) {
- print $cgi->a({-href => href(action=>"blame",
- hash_base=>$hash,
- file_name=>$diff{'file'})},
- "blame") . " | ";
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
}
print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
file_name=>$diff{'file'})},
@@ -2200,17 +2223,16 @@ sub git_difftree_body {
"diff") .
" | ";
}
- print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
- hash_base=>$parent, file_name=>$diff{'from_file'})},
- "blob") . " | ";
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$parent, file_name=>$diff{'to_file'})},
+ "blob") . " | ";
if ($have_blame) {
- print $cgi->a({-href => href(action=>"blame",
- hash_base=>$hash,
- file_name=>$diff{'to_file'})},
- "blame") . " | ";
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+ file_name=>$diff{'to_file'})},
+ "blame") . " | ";
}
- print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
- file_name=>$diff{'from_file'})},
+ print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
+ file_name=>$diff{'to_file'})},
"history");
print "\n";
@@ -2420,6 +2442,7 @@ sub git_project_list_body {
($pr->{'age'}, $pr->{'age_string'}) = @aa;
if (!defined $pr->{'descr'}) {
my $descr = git_get_project_description($pr->{'path'}) || "";
+ $pr->{'descr_long'} = to_utf8($descr);
$pr->{'descr'} = chop_str($descr, 25, 5);
}
if (!defined $pr->{'owner'}) {
@@ -2455,7 +2478,7 @@ sub git_project_list_body {
} else {
print "| " .
$cgi->a({-href => href(project=>undef, order=>'project'),
- -class => "header"}, "Project") .
+ -class => "header"}, "Project") .
" | \n";
}
if ($order eq "descr") {
@@ -2464,7 +2487,7 @@ sub git_project_list_body {
} else {
print "" .
$cgi->a({-href => href(project=>undef, order=>'descr'),
- -class => "header"}, "Description") .
+ -class => "header"}, "Description") .
" | \n";
}
if ($order eq "owner") {
@@ -2473,7 +2496,7 @@ sub git_project_list_body {
} else {
print "" .
$cgi->a({-href => href(project=>undef, order=>'owner'),
- -class => "header"}, "Owner") .
+ -class => "header"}, "Owner") .
" | \n";
}
if ($order eq "age") {
@@ -2482,7 +2505,7 @@ sub git_project_list_body {
} else {
print "" .
$cgi->a({-href => href(project=>undef, order=>'age'),
- -class => "header"}, "Last Change") .
+ -class => "header"}, "Last Change") .
" | \n";
}
print " | \n" .
@@ -2507,7 +2530,9 @@ sub git_project_list_body {
}
print "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
-class => "list"}, esc_html($pr->{'path'})) . " | \n" .
- "" . esc_html($pr->{'descr'}) . " | \n" .
+ "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+ -class => "list", -title => $pr->{'descr_long'}},
+ esc_html($pr->{'descr'})) . " | \n" .
"" . chop_str($pr->{'owner'}, 15) . " | \n";
print "{'age'}) . "\">" .
$pr->{'age_string'} . " | \n" .
@@ -2910,8 +2935,8 @@ sub git_tag {
print "";
my $comment = $tag{'comment'};
foreach my $line (@$comment) {
- chomp($line);
- print esc_html($line) . "
\n";
+ chomp $line;
+ print esc_html($line, -nbsp=>1) . "
\n";
}
print "
\n";
git_footer_html();
@@ -2980,7 +3005,7 @@ HTML
}
}
my $data = $_;
- chomp($data);
+ chomp $data;
my $rev = substr($full_rev, 0, 8);
my $author = $meta->{'author'};
my %date = parse_date($meta->{'author-time'},
@@ -4150,70 +4175,237 @@ sub git_shortlog {
}
## ......................................................................
-## feeds (RSS, OPML)
+## feeds (RSS, Atom; OPML)
-sub git_rss {
- # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+sub git_feed {
+ 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",
- git_get_head_hash($project), "--"
+ $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");
- print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
- print <
+
+ 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{'author_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!\n!;
+ if ($format eq 'rss') {
+ print <
-$project $my_uri $my_url
-${\esc_html("$my_url?p=$project;a=summary")}
-$project log
-en
XML
+ print "$title\n" .
+ "$alt_url\n" .
+ "$descr\n" .
+ "en\n";
+ } elsif ($format eq 'atom') {
+ print <
+XML
+ print "$title\n" .
+ "$descr\n" .
+ '' . "\n" .
+ '' . "\n" .
+ "" . href(-full=>1) . "\n" .
+ # use project owner for feed author
+ "$owner\n";
+ if (defined $favicon) {
+ print "" . esc_url($favicon) . "\n";
+ }
+ if (defined $logo_url) {
+ # not twice as wide as tall: 72 x 27 pixels
+ print "" . esc_url($logo_url) . "\n";
+ }
+ if (! %latest_date) {
+ # dummy date to keep the feed valid until commits trickle in:
+ print "1970-01-01T00:00:00Z\n";
+ } else {
+ print "$latest_date{'iso-8601'}\n";
+ }
+ }
+ # contents
for (my $i = 0; $i <= $#revlist; $i++) {
my $commit = $revlist[$i];
my %co = parse_commit($commit);
# we read 150, we always show 30 and the ones more recent than 48 hours
- if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) {
+ if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
last;
}
- my %cd = parse_date($co{'committer_epoch'});
+ my %cd = parse_date($co{'author_epoch'});
+
+ # get list of changed files
open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
- $co{'parent'}, $co{'id'}, "--"
+ $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
or next;
my @difftree = map { chomp; $_ } <$fd>;
close $fd
or next;
- print "- \n" .
- "" .
- sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
- "\n" .
- "" . esc_html($co{'author'}) . "\n" .
- "$cd{'rfc2822'}\n" .
- "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" .
- "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" .
- "" . esc_html($co{'title'}) . "\n" .
- "" .
- "1, action=>"commit", hash=>$commit);
+ if ($format eq 'rss') {
+ print "
- \n" .
+ "" . esc_html($co{'title'}) . "\n" .
+ "" . esc_html($co{'author'}) . "\n" .
+ "$cd{'rfc2822'}\n" .
+ "$co_url\n" .
+ "$co_url\n" .
+ "" . esc_html($co{'title'}) . "\n" .
+ "" .
+ "\n" .
+ "" . esc_html($co{'title'}) . "\n" .
+ "$cd{'iso-8601'}\n" .
+ "\n" .
+ " " . esc_html($co{'author_name'}) . "\n";
+ if ($co{'author_email'}) {
+ print " " . esc_html($co{'author_email'}) . "\n";
+ }
+ print "\n" .
+ # use committer for contributor
+ "\n" .
+ " " . esc_html($co{'committer_name'}) . "\n";
+ if ($co{'committer_email'}) {
+ print " " . esc_html($co{'committer_email'}) . "\n";
+ }
+ print "\n" .
+ "$cd{'iso-8601'}\n" .
+ "\n" .
+ "$co_url\n" .
+ "\n" .
+ "
\n";
+ }
my $comment = $co{'comment'};
+ print "
\n";
foreach my $line (@$comment) {
- $line = to_utf8($line);
- print "$line
\n";
+ $line = esc_html($line);
+ print "$line\n";
}
- print "
\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;
+ print "\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 "- " .
+ "[" .
+ $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');
}
- my $file = esc_path(unquote($7));
- $file = to_utf8($file);
- print "$file
\n";
+ $file = esc_path($file);
+ print "] ".
+ "$file \n";
+ }
+ if ($format eq 'rss') {
+ print "
]]>\n" .
+ "\n" .
+ "\n";
+ } elsif ($format eq 'atom') {
+ print "\n
\n" .
+ "\n" .
+ "\n";
}
- print "]]>\n" .
- "\n" .
- " \n";
}
- print " ";
+
+ # end of feed
+ if ($format eq 'rss') {
+ print "\n\n";
+ } elsif ($format eq 'atom') {
+ print "\n";
+ }
+}
+
+sub git_rss {
+ git_feed('rss');
+}
+
+sub git_atom {
+ git_feed('atom');
}
sub git_opml {