From 04408c3578300ac7cc1b1530bbaec54de299b200 Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sat, 18 Nov 2006 23:35:38 +0100
Subject: [PATCH 1/7] gitweb: Protect against possible warning in
 git_commitdiff

We may read an undef from <$fd> and unconditionally chomping it
would result in a warning.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 gitweb/gitweb.perl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 758759576cb..b2482febfc5 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -3731,7 +3731,8 @@ sub git_commitdiff {
 			$hash_parent, $hash, "--"
 			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
 			last unless $line;
 			push @difftree, $line;

From 6d55f05576851aedc7c53f7438c1d505c22ee78f Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sat, 18 Nov 2006 23:35:39 +0100
Subject: [PATCH 2/7] gitweb: Buffer diff header to deal with split patches +
 git_patchset_body refactoring

There are some cases when one line from "raw" git-diff output (raw format)
corresponds to more than one patch in the patchset git-diff output. To deal
with this buffer git diff header and extended diff header (everything up to
actual patch) to check from information from "index <hash>..<hash>" extended
header line if the patch corresponds to the same or next difftree raw line.

This could also be used to gather information needed for hyperlinking, and
used for printing gitweb quoted filenames, from extended diff header instead
of raw git-diff output.

While at it, refactor git_patchset_body subroutine from the event-driven,
AWK-like state-machine parsing to sequential parsing: for each patch
parse (and output) git diff header, parse extended diff header, parse two-line
from-file/to-file diff header, parse patch itself; patch ends with the end
of input [file] or the line matching m/^diff /.

For better understanding the code, there were added assertions in the
comments a la Carp::Assert module. Just in case there is commented out code
dealing with unexpected end of input (should not happen, hence commented
out).

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 gitweb/gitweb.perl | 235 ++++++++++++++++++++++++++-------------------
 1 file changed, 136 insertions(+), 99 deletions(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index b2482febfc5..bf58c3bac8f 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -2202,31 +2202,56 @@ sub git_patchset_body {
 	my ($fd, $difftree, $hash, $hash_parent) = @_;
 
 	my $patch_idx = 0;
-	my $in_header = 0;
-	my $patch_found = 0;
+	my $patch_line;
 	my $diffinfo;
 	my (%from, %to);
+	my ($from_id, $to_id);
 
 	print "<div class=\"patchset\">\n";
 
-	LINE:
-	while (my $patch_line = <$fd>) {
+	# skip to first patch
+	while ($patch_line = <$fd>) {
 		chomp $patch_line;
 
-		if ($patch_line =~ m/^diff /) { # "git diff" header
-			# beginning of patch (in patchset)
-			if ($patch_found) {
-				# close extended header for previous empty patch
-				if ($in_header) {
-					print "</div>\n" # class="diff extended_header"
-				}
-				# close previous patch
-				print "</div>\n"; # class="patch"
-			} else {
-				# first patch in patchset
-				$patch_found = 1;
+		last if ($patch_line =~ m/^diff /);
+	}
+
+ PATCH:
+	while ($patch_line) {
+		my @diff_header;
+
+		# git diff header
+		#assert($patch_line =~ m/^diff /) if DEBUG;
+		#assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+		push @diff_header, $patch_line;
+
+		# 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
 			if (ref($difftree->[$patch_idx]) eq "HASH") {
@@ -2247,100 +2272,112 @@ sub git_patchset_body {
 				                   hash=>$diffinfo->{'to_id'},
 				                   file_name=>$to{'file'});
 			}
-			$patch_idx++;
-
-			# print "git diff" header
-			$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;
+			# this is first patch for raw difftree line with $patch_idx index
+			# we index @$difftree array from 0, but number patches from 1
+			print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
 		}
 
-		if ($in_header) {
-			if ($patch_line !~ m/^---/) {
-				# 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'}) {
-						$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 "git diff" header
+		$patch_line = shift @diff_header;
+		$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 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'}) {
-					$patch_line = '--- a/' .
-					              $cgi->a({-href=>$from{'href'}, -class=>"path"},
-					                      esc_path($from{'file'}));
+					$from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
+					                     substr($diffinfo->{'from_id'},0,7));
+				} 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'}) {
-					$patch_line = '+++ b/' .
-					              $cgi->a({-href=>$to{'href'}, -class=>"path"},
-					                      esc_path($to{'file'}));
+					$to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
+					                   substr($diffinfo->{'to_id'},0,7));
+				} 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);
 		}
 
-		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"
 }

From 9954f772eb73593d9e660e3b2c3f90341b03a087 Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sat, 18 Nov 2006 23:35:41 +0100
Subject: [PATCH 3/7] gitweb: Default to $hash_base or HEAD for $hash in
 "commit" and "commitdiff"

Set $hash parameter to $hash_base || "HEAD" if it is not set (if it is
not true to be more exact). This allows [hand-edited] URLs with 'action'
"commit" or "commitdiff" but without 'hash' parameter.

If there is 'h' (hash) parameter provided, then gitweb tries
to use this. HEAD is used _only_ if nether hash, nor hash_base
are provided, i.e. for URL like below
  URL?p=project.git;a=commit
i.e. without neither 'h' nor 'hb'.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 gitweb/gitweb.perl | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index bf58c3bac8f..19b3d36d159 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -3429,6 +3429,7 @@ sub git_log {
 }
 
 sub git_commit {
+	$hash ||= $hash_base || "HEAD";
 	my %co = parse_commit($hash);
 	if (!%co) {
 		die_error(undef, "Unknown commit object");
@@ -3706,6 +3707,7 @@ sub git_blobdiff_plain {
 
 sub git_commitdiff {
 	my $format = shift || 'html';
+	$hash ||= $hash_base || "HEAD";
 	my %co = parse_commit($hash);
 	if (!%co) {
 		die_error(undef, "Unknown commit object");

From 59e3b14e08bf309baa692bf251b2cedcde131308 Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sat, 18 Nov 2006 23:35:40 +0100
Subject: [PATCH 4/7] gitweb: New improved formatting of chunk header in diff

If we have provided enough info, and diff is not combined diff,
and if provided diff line is chunk header, then:
* split chunk header into .chunk_info and .section span elements,
  first containing proper chunk header, second section heading
  (aka. which function), for separate styling: the proper chunk
  header is on non-white background, section heading part uses
  slightly lighter color.
* hyperlink from-file-range to starting line of from-file, if file
  was not created.
* hyperlink to-file-range to starting line of to-file, if file
  was not deleted.
Links are of invisible variety (and "list" class).

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 gitweb/gitweb.css  | 13 +++++++++++++
 gitweb/gitweb.perl | 23 ++++++++++++++++++++++-
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
index 974b47f19ca..7177c6e86b8 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -334,11 +334,13 @@ div.diff.extended_header {
 	padding: 2px 0px 2px 0px;
 }
 
+div.diff a.list,
 div.diff a.path,
 div.diff a.hash {
 	text-decoration: none;
 }
 
+div.diff a.list:hover,
 div.diff a.path:hover,
 div.diff a.hash:hover {
 	text-decoration: underline;
@@ -362,14 +364,25 @@ div.diff.rem {
 	color: #cc0000;
 }
 
+div.diff.chunk_header a,
 div.diff.chunk_header {
 	color: #990099;
+}
 
+div.diff.chunk_header {
 	border: dotted #ffe0ff;
 	border-width: 1px 0px 0px 0px;
 	margin-top: 2px;
 }
 
+div.diff.chunk_header span.chunk_info {
+	background-color: #ffeeff;
+}
+
+div.diff.chunk_header span.section {
+	color: #aa22aa;
+}
+
 div.diff.incomplete {
 	color: #cccccc;
 }
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 19b3d36d159..5875ba0846a 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -874,8 +874,10 @@ sub format_subject_html {
 	}
 }
 
+# format patch (diff) line (rather not to be used for diff headers)
 sub format_diff_line {
 	my $line = shift;
+	my ($from, $to) = @_;
 	my $char = substr($line, 0, 1);
 	my $diff_class = "";
 
@@ -891,6 +893,25 @@ sub format_diff_line {
 		$diff_class = " incomplete";
 	}
 	$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";
 }
 
@@ -2372,7 +2393,7 @@ sub git_patchset_body {
 
 			next PATCH if ($patch_line =~ m/^diff /);
 
-			print format_diff_line($patch_line);
+			print format_diff_line($patch_line, \%from, \%to);
 		}
 
 	} continue {

From bd5d1e42fb8edffa4e35d1257f648d586b2d054c Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sun, 19 Nov 2006 15:05:21 +0100
Subject: [PATCH 5/7] gitweb: Add an option to href() to return full URL

href subroutine by default generates absolute URL (generated using
CGI::url(-absolute=>1), and saved in $my_uri) using $my_uri as base;
add an option to generate full URL using $my_url as base.

New feature usage: href(..., -full=>1)

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 gitweb/gitweb.perl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 5875ba0846a..873950126a8 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -459,7 +459,8 @@ exit;
 
 sub href(%) {
 	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,
 	# too.

From af6feeb229110a0fa77a179c2655fca08e72f879 Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sun, 19 Nov 2006 15:05:22 +0100
Subject: [PATCH 6/7] 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>
---
 gitweb/gitweb.perl | 255 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 210 insertions(+), 45 deletions(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 873950126a8..a32a6b79c6f 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -425,6 +425,7 @@ my %actions = (
 	"history" => \&git_history,
 	"log" => \&git_log,
 	"rss" => \&git_rss,
+	"atom" => \&git_atom,
 	"search" => \&git_search,
 	"search_help" => \&git_search_help,
 	"shortlog" => \&git_shortlog,
@@ -1198,10 +1199,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 +1212,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;
 }
 
@@ -1672,14 +1675,17 @@ EOF
 		}
 	}
 	if (defined $project) {
-		printf('<link rel="alternate" title="%s log" '.
-		       'href="%s" type="application/rss+xml"/>'."\n",
+		printf('<link rel="alternate" title="%s log RSS feed" '.
+		       'href="%s" type="application/rss+xml" />'."\n",
 		       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 {
 		printf('<link rel="alternate" title="%s projects list" '.
 		       'href="%s" type="text/plain; charset=utf-8"/>'."\n",
 		       $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",
 		       $site_name, href(project=>undef, action=>"opml"));
 	}
@@ -1745,7 +1751,9 @@ sub git_footer_html {
 			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\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") . " ";
@@ -4150,26 +4158,125 @@ 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 <<XML;
-<?xml version="1.0" encoding="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;
 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
 <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
+		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++) {
 		my $commit = $revlist[$i];
 		my %co = parse_commit($commit);
@@ -4178,42 +4285,100 @@ XML
 			last;
 		}
 		my %cd = parse_date($co{'committer_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 "<item>\n" .
-		      "<title>" .
-		      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" .
-		      "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
-		      "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" .
-		      "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" .
-		      "<description>" . esc_html($co{'title'}) . "</description>\n" .
-		      "<content:encoded>" .
-		      "<![CDATA[\n";
+
+		# print element (entry, item)
+		my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
+		if ($format eq 'rss') {
+			print "<item>\n" .
+			      "<title>" . esc_html($co{'title'}) . "</title>\n" .
+			      "<author>" . esc_html($co{'author'}) . "</author>\n" .
+			      "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
+			      "<guid isPermaLink=\"true\">$co_url</guid>\n" .
+			      "<link>$co_url</link>\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'};
+		print "<pre>\n";
 		foreach my $line (@$comment) {
-			$line = to_utf8($line);
-			print "$line<br/>\n";
+			$line = esc_html($line);
+			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;
+		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');
 			}
-			my $file = esc_path(unquote($7));
-			$file = to_utf8($file);
-			print "$file<br/>\n";
+			# 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" .
+			      "</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 {

From 897d1d2e2a36d433c49d6246cd38133a540adddc Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sun, 19 Nov 2006 22:51:39 +0100
Subject: [PATCH 7/7] gitweb: Finish restoring "blob" links in
 git_difftree_body

This finishes work started by commit 4777b0141a4812177390da4b6ebc9d40ac3da4b5
  "gitweb: Restore object-named links in item lists"
by Petr Baudis. It brings back rest of "blob" links in difftree-raw
like part of "commit" and "commitdiff" views, namely in
git_difftree_body subroutine.

Now the td.link table cell has the following links:
 * link to diff ("blobdiff" view) in "commit" view, if applicable
   (there is no link to uninteresting creation/deletion diff), or
   link to patch anchor in "commitdiff" view.
 * link to current version of file ("blob" view), with the obvious
   exception of file deletion, where it is link to the parent
   version.
 * link to "blame" view, if it is enabled, and file was not just
   created (i.e. it has any history).
 * link to history of the file ("history" view), again with sole
   exception of the case of new file.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
 gitweb/gitweb.perl | 42 +++++++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index a32a6b79c6f..ce185d9037e 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -2092,7 +2092,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 "</td>\n";
 
 		} elsif ($diff{'status'} eq "D") { # deleted
@@ -2112,13 +2116,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'})},
@@ -2163,13 +2165,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'})},
@@ -2208,17 +2209,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 "</td>\n";