threads / patch / 5038

patchSome gitweb patches

Subject: [PATCH 0] Some gitweb patches

## tl;dr

54 messages between Jul 29, 2006 and Aug 1, 2006. Diffs are folded; open one to read it.

replies: 53people: 4as markdown or json

Jakub Narebski· Jul 29, 2006, 20:39 UTC · lore

This series of patches, based on 'next' branch (v1.4.2-rc2-g688a750 or 688a75071490101dbc660e3304aafb7a13e28807) is the beginning of gitweb cleanup. It would hopefully introduce no new features.

Open-ended series...
-- 
Jakub Narebski
Poland
Jakub Narebski· Jul 29, 2006, 20:43 UTC · re: Jakub Narebski · lore

[PATCH 1] gitweb: whitespace cleanup

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Use tab for indenting commands, spaces for align
(because the fact that some elements are aligned 
should not depend on the tab width).
 gitweb/gitweb.cgi |   58 +++++++++++++++++++++++++++--------------------------
 1 files changed, 30 insertions(+), 28 deletions(-)
Show changes to gitweb/gitweb.cgi +30 −28
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index e5fca63..f92eeab 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -36,7 +36,7 @@ our $git_version = qx($GIT --version) =~
 # location for temporary files needed for diffs
 our $git_temp = "/tmp/gitweb";
 if (! -d $git_temp) {
-    mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
+	mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
 }
 
 # target of the home link on top of all pages
@@ -104,7 +104,7 @@ if (defined $project) {
 		die_error(undef, "No such project.");
 	}
 	$rss_link = "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" .
-		    "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>";
+	            "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>";
 	$ENV{'GIT_DIR'} = "$projectroot/$project";
 } else {
 	git_project_list();
@@ -303,7 +303,7 @@ sub git_header_html {
 	} else {
 		$content_type = 'text/html';
 	}
-	print $cgi->header(-type=>$content_type,  -charset => 'utf-8', -status=> $status, -expires => $expires);
+	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
 	print <<EOF;
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
@@ -1138,17 +1138,17 @@ sub git_summary {
 				      "</td>\n" .
 				      "<td>";
 				if (defined($comment)) {
-				      print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment));
+					print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment));
 				}
 				print "</td>\n" .
 				      "<td class=\"link\">";
 				if ($tag{'type'} eq "tag") {
-				      print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
+					print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
 				}
 				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
 				if ($tag{'reftype'} eq "commit") {
-				      print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
-					    " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
+					print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
+					      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
 				}
 				print "</td>\n" .
 				      "</tr>";
@@ -1457,17 +1457,17 @@ sub git_tags {
 			      "</td>\n" .
 			      "<td>";
 			if (defined($comment)) {
-			      print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment);
+				print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment);
 			}
 			print "</td>\n" .
 			      "<td class=\"link\">";
 			if ($tag{'type'} eq "tag") {
-			      print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
+				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
 			}
 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
 			if ($tag{'reftype'} eq "commit") {
-			      print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
-				    " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
+				print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
+				      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
 			}
 			print "</td>\n" .
 			      "</tr>";
@@ -1613,13 +1613,14 @@ sub git_blob_plain_mimetype {
 
 sub git_blob_plain {
 	if (!defined $hash) {
-                if (defined $file_name) {
-                        my $base = $hash_base || git_read_head($project);
-                        $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file.");
-                } else {
-                        die_error(undef, "No file name defined.");
-                }
-        }
+		if (defined $file_name) {
+			my $base = $hash_base || git_read_head($project);
+			$hash = git_get_hash_by_path($base, $file_name, "blob") 
+				or die_error(undef, "Error lookup file.");
+		} else {
+			die_error(undef, "No file name defined.");
+		}
+	}
 	my $type = shift;
 	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash");
 
@@ -1644,13 +1645,14 @@ sub git_blob_plain {
 
 sub git_blob {
 	if (!defined $hash) {
-                if (defined $file_name) {
-                        my $base = $hash_base || git_read_head($project);
-                        $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file.");
-                } else {
-                        die_error(undef, "No file name defined.");
-                }
-        }
+		if (defined $file_name) {
+			my $base = $hash_base || git_read_head($project);
+			$hash = git_get_hash_by_path($base, $file_name, "blob") 
+				or die_error(undef, "Error lookup file.");
+		} else {
+			die_error(undef, "No file name defined.");
+		}
+	}
 	my $have_blame = git_get_project_config_bool ('blame');
 	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed.");
 	my $mimetype = git_blob_plain_mimetype($fd, $file_name);
@@ -1677,7 +1679,7 @@ sub git_blob {
 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n";
 		}
 		print "</div>\n".
-		       "<div>" .
+		      "<div>" .
 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
 		      "</div>\n";
 	} else {
@@ -2305,7 +2307,7 @@ sub git_commitdiff {
 		my $status = $5;
 		my $file = validate_input(unquote($6));
 		if ($status eq "A") {
-			print "<div class=\"diff_info\">" .  file_type($to_mode) . ":" .
+			print "<div class=\"diff_info\">" . file_type($to_mode) . ":" .
 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" .
 			      "</div>\n";
 			git_diff_print(undef, "/dev/null", $to_id, "b/$file");
@@ -2359,7 +2361,7 @@ sub git_commitdiff_plain {
 	      "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
 	      "Subject: $co{'title'}\n";
 	if (defined $tagname) {
-	      print "X-Git-Tag: $tagname\n";
+		print "X-Git-Tag: $tagname\n";
 	}
 	print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
 	      "\n";
-- 
1.4.0
Jakub Narebski· Jul 29, 2006, 20:51 UTC · re: Jakub Narebski · lore

[PATCH 2] gitweb: Use list for of open for running git commands, thorougly.

Use list form of open for running git commands and reading their
output through pipe, for example
  open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash
instead of
  open my $fd, "-|", "$GIT rev-list --header --parents $hash"
Single letter options use ' instead of " as quotes, according to style
used in list form of magic "-|" open.
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
The correct solution would be to use safe_pipe_capture or, 
what's better, use Git.pm, when it will be in released version.

Should have no impact on speed. Using ' instead of " could bring insignificant speedup.

 gitweb/gitweb.cgi |   45 +++++++++++++++++++++++++--------------------
 1 files changed, 25 insertions(+), 20 deletions(-)
Show changes to gitweb/gitweb.cgi +25 −20
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index f92eeab..9a17f87 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -388,7 +388,7 @@ sub die_error {
 sub git_get_type {
 	my $hash = shift;
 
-	open my $fd, "-|", "$GIT cat-file -t $hash" or return;
+	open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return;
 	my $type = <$fd>;
 	close $fd or return;
 	chomp $type;
@@ -440,7 +440,7 @@ sub git_read_tag {
 	my %tag;
 	my @comment;
 
-	open my $fd, "-|", "$GIT cat-file tag $tag_id" or return;
+	open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return;
 	$tag{'id'} = $tag_id;
 	while (my $line = <$fd>) {
 		chomp $line;
@@ -512,7 +512,7 @@ sub git_read_commit {
 		@commit_lines = @$commit_text;
 	} else {
 		$/ = "\0";
-		open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return;
+		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return;
 		@commit_lines = split '\n', <$fd>;
 		close $fd or return;
 		$/ = "\n";
@@ -610,7 +610,7 @@ sub git_diff_print {
 	if (defined $from) {
 		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";
 		open my $fd2, "> $from_tmp";
-		open my $fd, "-|", "$GIT cat-file blob $from";
+		open my $fd, "-|", $GIT, "cat-file", "blob", $from;
 		my @file = <$fd>;
 		print $fd2 @file;
 		close $fd2;
@@ -621,7 +621,7 @@ sub git_diff_print {
 	if (defined $to) {
 		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";
 		open my $fd2, "> $to_tmp";
-		open my $fd, "-|", "$GIT cat-file blob $to";
+		open my $fd, "-|", $GIT, "cat-file", "blob", $to;
 		my @file = <$fd>;
 		print $fd2 @file;
 		close $fd2;
@@ -1062,7 +1062,8 @@ sub git_summary {
 	      "<tr><td>owner</td><td>$owner</td></tr>\n" .
 	      "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
 	      "</table>\n";
-	open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) 
+		or die_error(undef, "Open failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd;
 	print "<div>\n" .
@@ -1526,7 +1527,7 @@ sub git_get_hash_by_path {
 	my $tree = $base;
 	my @parts = split '/', $path;
 	while (my $part = shift @parts) {
-		open my $fd, "-|", "$GIT ls-tree $tree" or die_error(undef, "Open git-ls-tree failed.");
+		open my $fd, "-|", $GIT, "ls-tree", $tree or die_error(undef, "Open git-ls-tree failed.");
 		my (@entries) = map { chomp; $_ } <$fd>;
 		close $fd or return undef;
 		foreach my $line (@entries) {
@@ -1622,7 +1623,7 @@ sub git_blob_plain {
 		}
 	}
 	my $type = shift;
-	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash");
+	open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error("Couldn't cat $file_name, $hash");
 
 	$type ||= git_blob_plain_mimetype($fd, $file_name);
 
@@ -1654,7 +1655,7 @@ sub git_blob {
 		}
 	}
 	my $have_blame = git_get_project_config_bool ('blame');
-	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error(undef, "Open failed.");
 	my $mimetype = git_blob_plain_mimetype($fd, $file_name);
 	if ($mimetype !~ m/^text\//) {
 		close $fd;
@@ -1718,7 +1719,7 @@ sub git_tree {
 		}
 	}
 	$/ = "\0";
-	open my $fd, "-|", "$GIT ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed.");
+	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash or die_error(undef, "Open git-ls-tree failed.");
 	chomp (my (@entries) = <$fd>);
 	close $fd or die_error(undef, "Reading tree failed.");
 	$/ = "\n";
@@ -1799,7 +1800,8 @@ #			      " | " . $cgi->a({-href => "$my
 
 sub git_rss {
 	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
-	open my $fd, "-|", "$GIT rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) 
+		or die_error(undef, "Open failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading rev-list failed.");
 	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
@@ -1819,7 +1821,7 @@ sub git_rss {
 			last;
 		}
 		my %cd = date_str($co{'committer_epoch'});
-		open $fd, "-|", "$GIT diff-tree -r $co{'parent'} $co{'id'}" or next;
+		open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next;
 		my @difftree = map { chomp; $_ } <$fd>;
 		close $fd or next;
 		print "<item>\n" .
@@ -1907,7 +1909,7 @@ sub git_log {
 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-	open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd;
 
@@ -1998,7 +2000,8 @@ sub git_commit {
 		$root = " --root";
 		$parent = "";
 	}
-	open my $fd, "-|", "$GIT diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $root, $parent, $hash 
+		or die_error(undef, "Open failed.");
 	@difftree = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
@@ -2244,7 +2247,8 @@ sub git_commitdiff {
 	if (!defined $hash_parent) {
 		$hash_parent = $co{'parent'};
 	}
-	open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
+		or die_error(undef, "Open failed.");
 	my (@difftree) = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
@@ -2334,14 +2338,15 @@ sub git_commitdiff {
 
 sub git_commitdiff_plain {
 	mkdir($git_temp, 0700);
-	open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash 
+		or die_error(undef, "Open failed.");
 	my (@difftree) = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
 	# try to figure out the next tag after this commit
 	my $tagname;
 	my $refs = read_info_ref("tags");
-	open $fd, "-|", "$GIT rev-list HEAD";
+	open $fd, "-|", $GIT, "rev-list", "HEAD";
 	chomp (my (@commits) = <$fd>);
 	close $fd;
 	foreach my $commit (@commits) {
@@ -2419,7 +2424,7 @@ sub git_history {
 	git_print_page_path($file_name, $ftype);
 
 	open my $fd, "-|",
-		"$GIT rev-list --full-history $hash_base -- \'$file_name\'";
+		$GIT, "rev-list", "--full-history", $hash_base, "--", "\'$file_name\'";
 	print "<table cellspacing=\"0\">\n";
 	my $alternate = 0;
 	while (my $line = <$fd>) {
@@ -2506,7 +2511,7 @@ sub git_search {
 	my $alternate = 0;
 	if ($commit_search) {
 		$/ = "\0";
-		open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next;
+		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next;
 		while (my $commit_text = <$fd>) {
 			if (!grep m/$searchtext/i, $commit_text) {
 				next;
@@ -2627,7 +2632,7 @@ sub git_shortlog {
 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-	open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd;
 
-- 
1.4.0
Junio C Hamano· Jul 31, 2006, 10:53 UTC · re: Jakub Narebski · lore

Re: [PATCH 2] gitweb: Use list for of open for running git commands, thorougly.

This, together with PATCH 6, seems to break "history" link. Visit a repository (summary page), click on "tree" on the second line, and click on "history" (on any blob or tree).

I might have made a merge mistake while squashing PATCH 2 and PATCH 6 into a single commit.

BTW, please be careful on trailing whitespaces.

I've wasted quite a lot of time yesterday just fixing up trailing whitespaces from your patches (although the first in your series was "whitespace cleanup" X-<).

I usually apply patches with whitespace=strip, unless there is a compelling reason not to (e.g. when the patch is to add a testcase of diff output which would inevitably have trailing whitespaces when a context contains an empty line), which means if you have a series of patches whose later patch updates lines with trailing whitespaces an earlier patch introduced the patch would not apply.

Jakub Narebski· Jul 31, 2006, 11:42 UTC · re: Junio C Hamano · lore

Re: [PATCH 2] gitweb: Use list for of open for running git commands, thorougly.

Junio C Hamano wrote:
> This, together with PATCH 6, seems to break "history" link.
> Visit a repository (summary page), click on "tree" on the second
> line, and click on "history" (on any blob or tree).
Strange... returning this part of git_history to previous version, i.e.
  open my $fd, "-|",
    "$GIT rev-list --full-history $hash_base -- \'$file_name\'";
still gives no history. Curious. Will examine that...
> BTW, please be careful on trailing whitespaces.

I'm sorry for that. I have forgot to turn-on default git pre-commit it clone of git repository I'm not working on...

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Jakub Narebski· Jul 31, 2006, 12:10 UTC · re: Jakub Narebski · lore

Re: [PATCH 2] gitweb: Use list for of open for running git commands, thorougly.

Jakub Narebski wrote:
Show 12 quoted lines
> Junio C Hamano wrote:
> 
>> This, together with PATCH 6, seems to break "history" link.
>> Visit a repository (summary page), click on "tree" on the second
>> line, and click on "history" (on any blob or tree).
> 
> Strange... returning this part of git_history to previous version, i.e.
> 
>   open my $fd, "-|",
>     "$GIT rev-list --full-history $hash_base -- \'$file_name\'";
> 
> still gives no history. Curious. Will examine that...

Curiouser and curiouser. It seems that somehow --full-history option (which was never documented by the way) got lost somewhere.

  ~/git> git merge-base --all next HEAD
  688a75071490101dbc660e3304aafb7a13e28807
  ~/git> git log --full-history HEAD -- gitweb/test/file+plus+sign
  fatal: unrecognized argument: --full-history

(same with 'git rev-list --full-history HEAD -- gitweb/test/file+plus+sign', but it shows help instead of simply croaking like git-log).

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Jakub Narebski· Jul 31, 2006, 12:38 UTC · re: Jakub Narebski · lore

Re: [PATCH 2] gitweb: Use list for of open for running git commands, thorougly.

Jakub Narebski wrote:
Show 17 quoted lines
> Jakub Narebski wrote:
> 
>> Junio C Hamano wrote:
>> 
>>> This, together with PATCH 6, seems to break "history" link.
>>> Visit a repository (summary page), click on "tree" on the second
>>> line, and click on "history" (on any blob or tree).
>> 
>> Strange... returning this part of git_history to previous version, i.e.
>> 
>>   open my $fd, "-|",
>>     "$GIT rev-list --full-history $hash_base -- \'$file_name\'";
>> 
>> still gives no history. Curious. Will examine that...
> 
> Curiouser and curiouser. It seems that somehow --full-history option 
> (which was never documented by the way) got lost somewhere.

Ooops. I've forgot that '--full-history' option was introduced after v1.4.0 (which I use).

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Jakub Narebski· Jul 31, 2006, 12:59 UTC · re: Junio C Hamano · lore

Re: [PATCH 2] gitweb: Use list for of open for running git commands, thorougly.

Junio C Hamano wrote:
> This, together with PATCH 6, seems to break "history" link.
> Visit a repository (summary page), click on "tree" on the second
> line, and click on "history" (on any blob or tree).

It works for me (both --full-history and gitweb) for git 1.4.1.1, although --full-history doesn't seem to add anything for this version of git:

  ~/git> time git rev-list HEAD -- gitweb/test/file+plus+sign
  0a8f4f0020cb35095005852c0797f0b90e9ebb74
  real    0m2.562s
  user    0m2.412s
  sys     0m0.024s
  ~/git> time git rev-list --full-history HEAD -- gitweb/test/file+plus+sign
  0a8f4f0020cb35095005852c0797f0b90e9ebb74
  real    0m8.564s
  user    0m7.212s
  sys     0m0.068s
while correct result should be
  ~/git> time git rev-list HEAD -- gitweb/test/file+plus+sign \
                                   test/file+plus+sign
  0a8f4f0020cb35095005852c0797f0b90e9ebb74
  85852d44e48c1d1c6d815cc5fccf1b580f2f2cad
  cc3245b6512a01d74c0fd460d762ba8a1e8b968a
  real    0m2.565s
  user    0m2.452s
  sys     0m0.008s
git version 1.4.1.1
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Jakub Narebski· Jul 29, 2006, 20:55 UTC · re: Jakub Narebski · lore

[PATCH 3] gitweb: simplify git_get_hash_by_path

Simplify git_get_hash_by_path by using git-ls-tree to do path limiting, instead of finding correct ttree and parsing unconstrained git-ls-tree output.

_Should_ be slightly faster.
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Testing speed before and after this patch using ApacheBench 
on git.git repository has shown speedup withing error. Perhaps
sample size was too small...

Speedup should be larger for deeper nested projects with larger number of files in a directory.

Not extensively tested!
 gitweb/gitweb.cgi |   32 +++++++++-----------------------
 1 files changed, 9 insertions(+), 23 deletions(-)
Show changes to gitweb/gitweb.cgi +9 −23
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 9a17f87..e1a817b 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1525,29 +1525,15 @@ sub git_get_hash_by_path {
 	my $path = shift || return undef;
 
 	my $tree = $base;
-	my @parts = split '/', $path;
-	while (my $part = shift @parts) {
-		open my $fd, "-|", $GIT, "ls-tree", $tree or die_error(undef, "Open git-ls-tree failed.");
-		my (@entries) = map { chomp; $_ } <$fd>;
-		close $fd or return undef;
-		foreach my $line (@entries) {
-			#'100644	blob	0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
-			$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
-			my $t_mode = $1;
-			my $t_type = $2;
-			my $t_hash = $3;
-			my $t_name = validate_input(unquote($4));
-			if ($t_name eq $part) {
-				if (!(@parts)) {
-					return $t_hash;
-				}
-				if ($t_type eq "tree") {
-					$tree = $t_hash;
-				}
-				last;
-			}
-		}
-	}
+
+	open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
+		or die_error(undef, "Open git-ls-tree failed.");
+	my $line = <$fd>;
+	close $fd or return undef;
+
+	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
+	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+	return $3;
 }
 
 sub mimetype_guess_file {
-- 
1.4.0
Jakub Narebski· Jul 29, 2006, 21:01 UTC · re: Jakub Narebski · lore

[PATCH 4] gitweb: More explicit error messages for open "-|"

Use more explicit error messages when failing magical "-|" open, stating at least the name of the git command that failed.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Should not affect web security.
 gitweb/gitweb.cgi |   29 +++++++++++++++++------------
 1 files changed, 17 insertions(+), 12 deletions(-)
Show changes to gitweb/gitweb.cgi +17 −12
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index e1a817b..2c67df0 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1063,7 +1063,7 @@ sub git_summary {
 	      "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
 	      "</table>\n";
 	open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) 
-		or die_error(undef, "Open failed.");
+		or die_error(undef, "Open git-rev-list failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd;
 	print "<div>\n" .
@@ -1271,7 +1271,7 @@ sub git_blame2 {
 		die_error("400 Bad Request", "object is not a blob");
 	}
 	open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base)
-		or die_error(undef, "Open failed");
+		or die_error(undef, "Open git-blame failed.");
 	git_header_html();
 	print "<div class=\"page_nav\">\n" .
 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
@@ -1333,7 +1333,7 @@ sub git_blame {
 			or die_error(undef, "Error lookup file.");
 	}
 	open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
-		or die_error(undef, "Open failed.");
+		or die_error(undef, "Open git-annotate failed.");
 	git_header_html();
 	print "<div class=\"page_nav\">\n" .
 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
@@ -1609,7 +1609,8 @@ sub git_blob_plain {
 		}
 	}
 	my $type = shift;
-	open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error("Couldn't cat $file_name, $hash");
+	open my $fd, "-|", $GIT, "cat-file", "blob", $hash 
+		or die_error("Couldn't cat $file_name, $hash");
 
 	$type ||= git_blob_plain_mimetype($fd, $file_name);
 
@@ -1641,7 +1642,8 @@ sub git_blob {
 		}
 	}
 	my $have_blame = git_get_project_config_bool ('blame');
-	open my $fd, "-|", $GIT, "cat-file", "blob", $hash or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "cat-file", "blob", $hash 
+		or die_error(undef, "Couldn't cat $file_name, $hash.");
 	my $mimetype = git_blob_plain_mimetype($fd, $file_name);
 	if ($mimetype !~ m/^text\//) {
 		close $fd;
@@ -1705,7 +1707,8 @@ sub git_tree {
 		}
 	}
 	$/ = "\0";
-	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash or die_error(undef, "Open git-ls-tree failed.");
+	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash 
+		or die_error(undef, "Open git-ls-tree failed.");
 	chomp (my (@entries) = <$fd>);
 	close $fd or die_error(undef, "Reading tree failed.");
 	$/ = "\n";
@@ -1787,7 +1790,7 @@ #			      " | " . $cgi->a({-href => "$my
 sub git_rss {
 	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
 	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) 
-		or die_error(undef, "Open failed.");
+		or die_error(undef, "Open git-rev-list failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading rev-list failed.");
 	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
@@ -1895,7 +1898,8 @@ sub git_log {
 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-	open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "rev-list", $limit, $hash 
+		or die_error(undef, "Open git-rev-list failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd;
 
@@ -1987,7 +1991,7 @@ sub git_commit {
 		$parent = "";
 	}
 	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $root, $parent, $hash 
-		or die_error(undef, "Open failed.");
+		or die_error(undef, "Open git-diff-tree failed.");
 	@difftree = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
@@ -2234,7 +2238,7 @@ sub git_commitdiff {
 		$hash_parent = $co{'parent'};
 	}
 	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
-		or die_error(undef, "Open failed.");
+		or die_error(undef, "Open git-diff-tree failed.");
 	my (@difftree) = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
@@ -2325,7 +2329,7 @@ sub git_commitdiff {
 sub git_commitdiff_plain {
 	mkdir($git_temp, 0700);
 	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash 
-		or die_error(undef, "Open failed.");
+		or die_error(undef, "Open git-diff-tree failed.");
 	my (@difftree) = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
@@ -2618,7 +2622,8 @@ sub git_shortlog {
 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-	open my $fd, "-|", $GIT, "rev-list", $limit, $hash or die_error(undef, "Open failed.");
+	open my $fd, "-|", $GIT, "rev-list", $limit, $hash 
+		or die_error(undef, "Open git-rev-list failed.");
 	my (@revlist) = map { chomp; $_ } <$fd>;
 	close $fd;
 
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 02:08 UTC · re: Jakub Narebski · lore

[PATCH 5] gitweb: Cleanup - chomp $line in consistent style

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
Show changes to gitweb/gitweb.cgi +2 −2
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 2c67df0..e1f0e26 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -635,7 +635,7 @@ sub git_diff_print {
 		$/ = "\n";
 	} else {
 		while (my $line = <$fd>) {
-			chomp($line);
+			chomp $line;
 			my $char = substr($line, 0, 1);
 			my $diff_class = "";
 			if ($char eq '+') {
@@ -944,7 +944,7 @@ sub read_info_ref {
 	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{}
 	open my $fd, "$projectroot/$project/info/refs" or return;
 	while (my $line = <$fd>) {
-		chomp($line);
+		chomp $line;
 		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
 			if (defined $refs{$1}) {
 				$refs{$1} .= " / $2";
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 02:11 UTC · re: Jakub Narebski · lore

[PATCH 6] gitweb: Correct error from changing "-|" open to list form in git_commit

Correct error in moving to list form of open for running git commands and reading their output through pipe (PATCH 2 in series) in git_commit subroutine.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
It also simplifies git_commit a tiny bit.
 gitweb/gitweb.cgi |    8 +++-----
 1 files changed, 3 insertions(+), 5 deletions(-)
Show changes to gitweb/gitweb.cgi +3 −5
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index e1f0e26..86b9547 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1984,16 +1984,14 @@ sub git_commit {
 	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
 
 	my @difftree;
-	my $root = "";
 	my $parent = $co{'parent'};
 	if (!defined $parent) {
-		$root = " --root";
-		$parent = "";
+		$parent = "--root";
 	}
-	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $root, $parent, $hash 
+	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash 
 		or die_error(undef, "Open git-diff-tree failed.");
 	@difftree = map { chomp; $_ } <$fd>;
-	close $fd or die_error(undef, "Reading diff-tree failed.");
+	close $fd or die_error(undef, "Reading git-diff-tree failed.");
 
 	# non-textual hash id's can be cached
 	my $expires;
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 12:58 UTC · re: Jakub Narebski · lore

[PATCH 7] gitweb: Cleanup - chomp @lines in consistent style

Use 'my @lines = map { chomp; $_ } <$fd>;' form to read all lines of git command output into array without trailing newlines.

It has advantage over 'chomp (my (@lines) = <$fd>);' in that it does not modify array.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |   19 +++++++++----------
 1 files changed, 9 insertions(+), 10 deletions(-)
Show changes to gitweb/gitweb.cgi +9 −10
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 86b9547..d5248fe 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1064,7 +1064,7 @@ sub git_summary {
 	      "</table>\n";
 	open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) 
 		or die_error(undef, "Open git-rev-list failed.");
-	my (@revlist) = map { chomp; $_ } <$fd>;
+	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") .
@@ -1709,7 +1709,7 @@ sub git_tree {
 	$/ = "\0";
 	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash 
 		or die_error(undef, "Open git-ls-tree failed.");
-	chomp (my (@entries) = <$fd>);
+	my @entries = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading tree failed.");
 	$/ = "\n";
 
@@ -1791,7 +1791,7 @@ sub git_rss {
 	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
 	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) 
 		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 rev-list failed.");
 	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
 	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
@@ -1900,7 +1900,7 @@ sub git_log {
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 	open my $fd, "-|", $GIT, "rev-list", $limit, $hash 
 		or die_error(undef, "Open git-rev-list failed.");
-	my (@revlist) = map { chomp; $_ } <$fd>;
+	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
 	if ($hash ne $head || $page) {
@@ -1983,14 +1983,13 @@ sub git_commit {
 	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
 	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
 
-	my @difftree;
 	my $parent = $co{'parent'};
 	if (!defined $parent) {
 		$parent = "--root";
 	}
 	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash 
 		or die_error(undef, "Open git-diff-tree failed.");
-	@difftree = map { chomp; $_ } <$fd>;
+	my @difftree = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading git-diff-tree failed.");
 
 	# non-textual hash id's can be cached
@@ -2237,7 +2236,7 @@ sub git_commitdiff {
 	}
 	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
 		or die_error(undef, "Open git-diff-tree failed.");
-	my (@difftree) = map { chomp; $_ } <$fd>;
+	my @difftree = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
 	# non-textual hash id's can be cached
@@ -2328,14 +2327,14 @@ sub git_commitdiff_plain {
 	mkdir($git_temp, 0700);
 	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash 
 		or die_error(undef, "Open git-diff-tree failed.");
-	my (@difftree) = map { chomp; $_ } <$fd>;
+	my @difftree = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading diff-tree failed.");
 
 	# try to figure out the next tag after this commit
 	my $tagname;
 	my $refs = read_info_ref("tags");
 	open $fd, "-|", $GIT, "rev-list", "HEAD";
-	chomp (my (@commits) = <$fd>);
+	my @commits = map { chomp; $_ } <$fd>;
 	close $fd;
 	foreach my $commit (@commits) {
 		if (defined $refs->{$commit}) {
@@ -2622,7 +2621,7 @@ sub git_shortlog {
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 	open my $fd, "-|", $GIT, "rev-list", $limit, $hash 
 		or die_error(undef, "Open git-rev-list failed.");
-	my (@revlist) = map { chomp; $_ } <$fd>;
+	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
 	if ($hash ne $head || $page) {
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 12:59 UTC · re: Jakub Narebski · lore

[PATCH 8] gitweb: Add git_page_nav for later use

Adds git_page_nav subroutine to factors out the generation of the
navigation bar.  Based on Sven Verdoolaege code
  Message-Id: <20050618113121.GA13122@pc117b.liacs.nl>
  http://marc.theaimsgroup.com/?l=git&m=111909432415478&w=2
I tried for the refactored navbar generate the same result.
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |   34 ++++++++++++++++++++++++++++++++++
 1 files changed, 34 insertions(+), 0 deletions(-)
Show changes to gitweb/gitweb.cgi +34 −0
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index d5248fe..2bc3445 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -385,6 +385,40 @@ sub die_error {
 	exit;
 }
 
+sub git_page_nav {
+	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
+	$extra = '' if !defined $extra; # pager or formats
+
+	my @navs = qw(summary shortlog log commit commitdiff tree);
+	if ($suppress) {
+		@navs = grep { $_ ne $suppress } @navs;
+	}
+
+	my %arg = map { $_, ''} @navs;
+	if (defined $head) {
+		for (qw(commit commitdiff)) {
+			$arg{$_} = ";h=$head";
+		}
+		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
+			for (qw(shortlog log)) {
+				$arg{$_} = ";h=$head";
+			}
+		}
+	}
+	$arg{tree} .= ";h=$treehead" if defined $treehead;
+	$arg{tree} .= ";hb=$treebase" if defined $treebase;
+
+	print "<div class=\"page_nav\">\n" .
+		(join " | ",
+		 map { $_ eq $current
+					 ? $_
+					 : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_")
+				 }
+		 @navs);
+	print "<br/>$extra<br/>\n" .
+	      "</div>\n";
+}
+
 sub git_get_type {
 	my $hash = shift;
 
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 13:01 UTC · re: Jakub Narebski · lore

[PATCH 9] gitweb: Navbar refactoring - use git_page_nav to generate navigation bar

Use git_page_nav subroutine to generate navigation bar. Additional navigation (either formats or pager/pagination) is put into variables.

Corrects error in git_search where hash parameter was added to "summary" link instead of to "log" link. Might differ from previous version by additional "<br/>" in navigation bar.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |  235 +++++++++++++++--------------------------------------
 1 files changed, 66 insertions(+), 169 deletions(-)
Show changes to gitweb/gitweb.cgi +66 −169
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 2bc3445..6c72d33 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1081,15 +1081,7 @@ sub git_summary {
 
 	my $refs = read_info_ref();
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-	      "summary".
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree")}, "tree") .
-	      "<br/><br/>\n" .
-	      "</div>\n";
+	git_page_nav('summary','', $head);
 	print "<div class=\"title\">&nbsp;</div>\n";
 	print "<table cellspacing=\"0\">\n" .
 	      "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
@@ -1251,15 +1243,7 @@ sub git_print_page_path {
 sub git_tag {
 	my $head = git_read_head($project);
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" .
-	      "<br/>\n" .
-	      "</div>\n";
+	git_page_nav('','', $head,undef,$head);
 	my %tag = git_read_tag($hash);
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" .
@@ -1307,19 +1291,13 @@ sub git_blame2 {
 	open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base)
 		or die_error(undef, "Open git-blame failed.");
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
-	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n";
-	print "</div>\n".
-		"<div>" .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
-		"</div>\n";
+	my $formats_nav = 
+		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
+		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
+	git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+	print "<div>" .
+	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
+	      "</div>\n";
 	git_print_page_path($file_name, $ftype);
 	my @rev_color = (qw(light dark));
 	my $num_colors = scalar(@rev_color);
@@ -1369,19 +1347,13 @@ sub git_blame {
 	open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
 		or die_error(undef, "Open git-annotate failed.");
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
-	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
-		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n";
-	print "</div>\n".
-		"<div>" .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
-		"</div>\n";
+	my $formats_nav = 
+		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
+		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
+	git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+	print "<div>" .
+	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
+	      "</div>\n";
 	git_print_page_path($file_name);
 	print "<div class=\"page_body\">\n";
 	print <<HTML;
@@ -1456,15 +1428,7 @@ HTML
 sub git_tags {
 	my $head = git_read_head($project);
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" .
-	      "<br/>\n" .
-	      "</div>\n";
+	git_page_nav('','', $head,undef,$head);
 	my $taglist = git_read_refs("refs/tags");
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
@@ -1515,15 +1479,7 @@ sub git_tags {
 sub git_heads {
 	my $head = git_read_head($project);
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" .
-	      "<br/>\n" .
-	      "</div>\n";
+	git_page_nav('','', $head,undef,$head);
 	my $taglist = git_read_refs("refs/heads");
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
@@ -1684,25 +1640,20 @@ sub git_blob {
 		return git_blob_plain($mimetype);
 	}
 	git_header_html();
+	my $formats_nav = '';
 	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
-		print "<div class=\"page_nav\">\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
 		if (defined $file_name) {
 			if ($have_blame) {
-				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") .  " | ";
+				$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | ";
 			}
-			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
-			" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "<br/>\n";
+			$formats_nav .=
+				$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
+				" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head");
 		} else {
-			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n";
+			$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");
 		}
-		print "</div>\n".
-		      "<div>" .
+		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+		print "<div>" .
 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
 		      "</div>\n";
 	} else {
@@ -1757,15 +1708,7 @@ sub git_tree {
 	my $base = "";
 	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
 		$base_key = ";hb=$hash_base";
-		print "<div class=\"page_nav\">\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash_base")}, "shortlog") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash_base")}, "log") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
-		      " | tree" .
-		      "<br/><br/>\n" .
-		      "</div>\n";
+		git_page_nav('tree','', $hash_base);
 		print "<div>\n" .
 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
 		      "</div>\n";
@@ -1922,14 +1865,6 @@ sub git_log {
 		$page = 0;
 	}
 	my $refs = read_info_ref();
-	git_header_html();
-	print "<div class=\"page_nav\">\n";
-	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") .
-	      " | log" .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 	open my $fd, "-|", $GIT, "rev-list", $limit, $hash 
@@ -1937,25 +1872,28 @@ sub git_log {
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
+	my $paging_nav = '';
 	if ($hash ne $head || $page) {
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD");
+		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD");
 	} else {
-		print "HEAD";
+		$paging_nav .= "HEAD";
 	}
 	if ($page > 0) {
-		print " &sdot; " .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
 	} else {
-		print " &sdot; prev";
+		$paging_nav .= " &sdot; prev";
 	}
 	if ($#revlist >= (100 * ($page+1)-1)) {
-		print " &sdot; " .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
 	} else {
-		print " &sdot; next";
+		$paging_nav .= " &sdot; next";
 	}
-	print "<br/>\n" .
-	      "</div>\n";
+
+	git_header_html();
+	git_page_nav('log','', $hash,undef,undef, $paging_nav);
+
 	if (!@revlist) {
 		print "<div>\n" .
 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
@@ -2037,21 +1975,14 @@ sub git_commit {
 		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>";
 	}
 	git_header_html(undef, $expires);
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
-	      " | commit";
-	if (defined $co{'parent'}) {
-		print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff");
-	}
-	print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "\n" .
-		"<br/>\n";
+	my $formats_nav = '';
 	if (defined $file_name && defined $co{'parent'}) {
 		my $parent = $co{'parent'};
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame") . "\n";
+		$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame");
 	}
-	print "<br/></div>\n";
+	git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', 
+							 $hash, $co{'tree'}, $hash,
+							 $formats_nav);
 
 	if (defined $co{'parent'}) {
 		print "<div>\n" .
@@ -2223,16 +2154,9 @@ sub git_blobdiff {
 	mkdir($git_temp, 0700);
 	git_header_html();
 	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
-		print "<div class=\"page_nav\">\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") .
-		      "<br/>\n";
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain") .
-		      "</div>\n";
+		my $formats_nav = 
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
+		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
 		print "<div>\n" .
 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" .
 		      "</div>\n";
@@ -2284,15 +2208,9 @@ sub git_commitdiff {
 		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>";
 	}
 	git_header_html(undef, $expires);
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
-	      " | commitdiff" .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "<br/>\n";
-	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . "\n" .
-	      "</div>\n";
+	my $formats_nav =
+		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
+	git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
 	      "</div>\n";
@@ -2424,15 +2342,7 @@ sub git_history {
 	}
 	my $refs = read_info_ref();
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") .
-	      "<br/><br/>\n" .
-	      "</div>\n";
+	git_page_nav('','', $hash_base,$co{'tree'},$hash_base);
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" .
 	      "</div>\n";
@@ -2515,15 +2425,7 @@ sub git_search {
 		$pickaxe_search = 1;
 	}
 	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary;h=$hash")}, "summary") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") .
-	      "<br/><br/>\n" .
-	      "</div>\n";
+	git_page_nav('','', $hash,$co{'tree'},$hash);
 
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" .
@@ -2643,14 +2545,6 @@ sub git_shortlog {
 		$page = 0;
 	}
 	my $refs = read_info_ref();
-	git_header_html();
-	print "<div class=\"page_nav\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
-	      " | shortlog" .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") .
-	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 
 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 	open my $fd, "-|", $GIT, "rev-list", $limit, $hash 
@@ -2658,25 +2552,28 @@ sub git_shortlog {
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
+	my $paging_nav = '';
 	if ($hash ne $head || $page) {
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD");
+		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD");
 	} else {
-		print "HEAD";
+		$paging_nav .= "HEAD";
 	}
 	if ($page > 0) {
-		print " &sdot; " .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
 	} else {
-		print " &sdot; prev";
+		$paging_nav .= " &sdot; prev";
 	}
 	if ($#revlist >= (100 * ($page+1)-1)) {
-		print " &sdot; " .
-		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
 	} else {
-		print " &sdot; next";
+		$paging_nav .= " &sdot; next";
 	}
-	print "<br/>\n" .
-	      "</div>\n";
+
+	git_header_html();
+	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
+
 	print "<div>\n" .
 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
 	      "</div>\n";
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 13:02 UTC · re: Jakub Narebski · lore

[PATCH 10] gitweb: Replace form-feed character by ^L

>From 2be5cab10486cba804ccae063e93b146288054fe Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Sun, 30 Jul 2006 13:11:56 +0200
Subject: [PATCH] 

Replace FORM FEED (FF) character (014, 12, 0xc) by it's textual representation '^L'. This character is used for example in GNU GPL 'COPYING' file. With this patch "blob" output for COPYING passes XHTML validation.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
Show changes to gitweb/gitweb.cgi +1 −0
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 6c72d33..d39af82 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -250,6 +250,7 @@ sub esc_html {
 	my $str = shift;
 	$str = decode("utf8", $str, Encode::FB_DEFAULT);
 	$str = escapeHTML($str);
+	$str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
 	return $str;
 }
 
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 14:13 UTC · re: Jakub Narebski · lore

[PATCH 11] gitweb: Read project description using utf-8 encoding

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
Show changes to gitweb/gitweb.cgi +1 −1
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index d39af82..7d52a2c 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -463,7 +463,7 @@ sub git_read_hash {
 sub git_read_description {
 	my $path = shift;
 
-	open my $fd, "$projectroot/$path/description" or return undef;
+	open (my $fd, "<:utf8", "$projectroot/$path/description") or return undef;
 	my $descr = <$fd>;
 	close $fd;
 	chomp $descr;
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 15:20 UTC · re: Jakub Narebski · lore

Re: [PATCH 11] gitweb: Read project description using utf-8 encoding

Disregard this patch for now. It corrects error in project_list page, but introduces errors on other pages:

  Software error: Cannot decode string with wide characters 
  at Encode.pm line 166.
Corrected patch to follow.
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Jakub Narebski· Jul 30, 2006, 15:47 UTC · re: Jakub Narebski · lore

[PATCH 11] gitweb: Show project descriptions with utf-8 characters in project list correctly

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
This supersedes previous patch.
 gitweb/gitweb.cgi |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
Show changes to gitweb/gitweb.cgi +1 −1
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 3884ffd..1befa7d 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -988,7 +988,7 @@ sub git_project_list {
 		}
 		$alternate ^= 1;
 		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-		      "<td>$pr->{'descr'}</td>\n" .
+		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
 		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
 		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" .
 		      "<td class=\"link\">" .
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 14:14 UTC · re: Jakub Narebski · lore

[PATCH 12] gitweb: Add "\n" after <br/> in git_page_nav

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
Show changes to gitweb/gitweb.cgi +1 −1
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 7d52a2c..df152c3 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -416,7 +416,7 @@ sub git_page_nav {
 					 : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_")
 				 }
 		 @navs);
-	print "<br/>$extra<br/>\n" .
+	print "<br/>\n$extra<br/>\n" .
 	      "</div>\n";
 }
 
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 15:49 UTC · re: Jakub Narebski · lore

[PATCH 13] gitweb: Pager refactoring - use git_get_paging_nav for pagination

Add git_get_paging_nav subroutine which returns string with pager (paging nav) for shortlog and log actions.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
It would make no sense to split this patch into two, I think.
 gitweb/gitweb.cgi |   68 +++++++++++++++++++++++++----------------------------
 1 files changed, 32 insertions(+), 36 deletions(-)
Show changes to gitweb/gitweb.cgi +32 −36
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index df152c3..0ed3ad2 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -420,6 +420,36 @@ sub git_page_nav {
 	      "</div>\n";
 }
 
+sub git_get_paging_nav {
+	my ($project, $action, $hash, $head, $page, $nrevs) = @_;
+	my $paging_nav;
+
+
+	if ($hash ne $head || $page) {
+		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
+	} else {
+		$paging_nav .= "HEAD";
+	}
+
+	if ($page > 0) {
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)),
+							 -accesskey => "p", -title => "Alt-p"}, "prev");
+	} else {
+		$paging_nav .= " &sdot; prev";
+	}
+
+	if ($nrevs >= (100 * ($page+1)-1)) {
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)),
+							 -accesskey => "n", -title => "Alt-n"}, "next");
+	} else {
+		$paging_nav .= " &sdot; next";
+	}
+
+	return $paging_nav;
+}
+
 sub git_get_type {
 	my $hash = shift;
 
@@ -1873,24 +1903,7 @@ sub git_log {
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
-	my $paging_nav = '';
-	if ($hash ne $head || $page) {
-		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD");
-	} else {
-		$paging_nav .= "HEAD";
-	}
-	if ($page > 0) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
-	} else {
-		$paging_nav .= " &sdot; prev";
-	}
-	if ($#revlist >= (100 * ($page+1)-1)) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
-	} else {
-		$paging_nav .= " &sdot; next";
-	}
+	my $paging_nav = git_get_paging_nav($project, 'log', $hash, $head, $page, $#revlist);
 
 	git_header_html();
 	git_page_nav('log','', $hash,undef,undef, $paging_nav);
@@ -2553,24 +2566,7 @@ sub git_shortlog {
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
-	my $paging_nav = '';
-	if ($hash ne $head || $page) {
-		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD");
-	} else {
-		$paging_nav .= "HEAD";
-	}
-	if ($page > 0) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
-	} else {
-		$paging_nav .= " &sdot; prev";
-	}
-	if ($#revlist >= (100 * ($page+1)-1)) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
-	} else {
-		$paging_nav .= " &sdot; next";
-	}
+	my $paging_nav = git_get_paging_nav($project, 'shortlog', $hash, $head, $page, $#revlist);
 
 	git_header_html();
 	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 18:31 UTC · re: Jakub Narebski · lore

[PATCH 14] gitweb: Remove $project from git_get_paging_nav arguments

Remove $project from arguments passed to git_get_paging_nav subroutine: it did not depend only on arguments, using $my_uri global variable (and now $project global variable).

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)
Show changes to gitweb/gitweb.cgi +3 −3
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 1befa7d..bfb01de 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -421,7 +421,7 @@ sub git_page_nav {
 }
 
 sub git_get_paging_nav {
-	my ($project, $action, $hash, $head, $page, $nrevs) = @_;
+	my ($action, $hash, $head, $page, $nrevs) = @_;
 	my $paging_nav;
 
 
@@ -1903,7 +1903,7 @@ sub git_log {
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
-	my $paging_nav = git_get_paging_nav($project, 'log', $hash, $head, $page, $#revlist);
+	my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist);
 
 	git_header_html();
 	git_page_nav('log','', $hash,undef,undef, $paging_nav);
@@ -2566,7 +2566,7 @@ sub git_shortlog {
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 
-	my $paging_nav = git_get_paging_nav($project, 'shortlog', $hash, $head, $page, $#revlist);
+	my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist);
 
 	git_header_html();
 	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 18:32 UTC · re: Jakub Narebski · lore

[PATCH 15] gitweb: Headers refactoring - use git_header_div for header divs

Add git_header_div subroutine which prints "header" divs, now with class "header" (class "title" is taken, and has set CSS style, changing appereance and maing layout wrong), and use it thorough gitweb.cgi. Change header linking to project summary from empty (&nbsp; as a contents of link) to having $project as contents/name of link. Sometimes a little reordering.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |  100 +++++++++++++++++++++--------------------------------
 1 files changed, 40 insertions(+), 60 deletions(-)
Show changes to gitweb/gitweb.cgi +40 −60
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index bfb01de..06a6930 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -420,6 +420,19 @@ sub git_page_nav {
 	      "</div>\n";
 }
 
+sub git_header_div {
+	my ($action, $title, $hash, $hash_base) = @_;
+	my $rest = '';
+
+	$rest .= ";h=$hash" if $hash;
+	$rest .= ";hb=$hash_base" if $hash_base;
+
+	print "<div class=\"header\">\n" .
+	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"),
+	               -class => "title"}, $title ? $title : $action) . "\n" .
+	      "</div>\n";
+}
+
 sub git_get_paging_nav {
 	my ($action, $hash, $head, $page, $nrevs) = @_;
 	my $paging_nav;
@@ -1123,9 +1136,7 @@ sub git_summary {
 		or die_error(undef, "Open git-rev-list failed.");
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") .
-	      "</div>\n";
+	git_header_div('shortlog');
 	my $i = 16;
 	print "<table cellspacing=\"0\">\n";
 	my $alternate = 0;
@@ -1169,9 +1180,7 @@ sub git_summary {
 
 	my $taglist = git_read_refs("refs/tags");
 	if (defined @$taglist) {
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags"), -class => "title"}, "tags") .
-		      "</div>\n";
+		git_header_div('tags');
 		my $i = 16;
 		print "<table cellspacing=\"0\">\n";
 		my $alternate = 0;
@@ -1221,9 +1230,7 @@ sub git_summary {
 
 	my $headlist = git_read_refs("refs/heads");
 	if (defined @$headlist) {
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads"), -class => "title"}, "heads") .
-		      "</div>\n";
+		git_header_div('heads');
 		my $i = 16;
 		print "<table cellspacing=\"0\">\n";
 		my $alternate = 0;
@@ -1276,9 +1283,7 @@ sub git_tag {
 	git_header_html();
 	git_page_nav('','', $head,undef,$head);
 	my %tag = git_read_tag($hash);
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" .
-	      "</div>\n";
+	git_header_div('commit', esc_html($tag{'name'}), $hash);
 	print "<div class=\"title_text\">\n" .
 	      "<table cellspacing=\"0\">\n" .
 	      "<tr>\n" .
@@ -1326,9 +1331,7 @@ sub git_blame2 {
 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
 	git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
-	print "<div>" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
-	      "</div>\n";
+	git_header_div('commit', esc_html($co{'title'}), $hash_base);
 	git_print_page_path($file_name, $ftype);
 	my @rev_color = (qw(light dark));
 	my $num_colors = scalar(@rev_color);
@@ -1382,9 +1385,7 @@ sub git_blame {
 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
 	git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
-	print "<div>" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
-	      "</div>\n";
+	git_header_div('commit', esc_html($co{'title'}), $hash_base);
 	git_print_page_path($file_name);
 	print "<div class=\"page_body\">\n";
 	print <<HTML;
@@ -1460,11 +1461,10 @@ sub git_tags {
 	my $head = git_read_head($project);
 	git_header_html();
 	git_page_nav('','', $head,undef,$head);
-	my $taglist = git_read_refs("refs/tags");
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
-	      "</div>\n";
+	git_header_div('summary', $project);
 	print "<table cellspacing=\"0\">\n";
+
+	my $taglist = git_read_refs("refs/tags");
 	my $alternate = 0;
 	if (defined @$taglist) {
 		foreach my $entry (@$taglist) {
@@ -1511,11 +1511,10 @@ sub git_heads {
 	my $head = git_read_head($project);
 	git_header_html();
 	git_page_nav('','', $head,undef,$head);
-	my $taglist = git_read_refs("refs/heads");
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
-	      "</div>\n";
+	hit_header_div('summary', $project);
 	print "<table cellspacing=\"0\">\n";
+
+	my $taglist = git_read_refs("refs/heads");
 	my $alternate = 0;
 	if (defined @$taglist) {
 		foreach my $entry (@$taglist) {
@@ -1684,9 +1683,7 @@ sub git_blob {
 			$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");
 		}
 		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
-		print "<div>" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
-		      "</div>\n";
+		git_header_div('commit', esc_html($co{'title'}), $hash_base);
 	} else {
 		print "<div class=\"page_nav\">\n" .
 		      "<br/><br/></div>\n" .
@@ -1740,9 +1737,7 @@ sub git_tree {
 	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
 		$base_key = ";hb=$hash_base";
 		git_page_nav('tree','', $hash_base);
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
-		      "</div>\n";
+		git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
 	} else {
 		print "<div class=\"page_nav\">\n";
 		print "<br/><br/></div>\n";
@@ -1909,10 +1904,9 @@ sub git_log {
 	git_page_nav('log','', $hash,undef,undef, $paging_nav);
 
 	if (!@revlist) {
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
-		      "</div>\n";
 		my %co = git_read_commit($hash);
+
+		git_header_div('summary', $project);
 		print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
 	}
 	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
@@ -1924,10 +1918,10 @@ sub git_log {
 		my %co = git_read_commit($commit);
 		next if !%co;
 		my %ad = date_str($co{'author_epoch'});
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "title"},
-		      "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref) . "\n";
-		print "</div>\n";
+		git_header_div('commit',
+									 "<span class=\"age\">$co{'age_string'}</span>" .
+									 esc_html($co{'title'}) . $ref,
+									 $commit);
 		print "<div class=\"title_text\">\n" .
 		      "<div class=\"log_link\">\n" .
 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
@@ -1999,13 +1993,9 @@ sub git_commit {
 							 $formats_nav);
 
 	if (defined $co{'parent'}) {
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
-		      "</div>\n";
+		git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
 	} else {
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" .
-		      "</div>\n";
+		git_header_div('tree', esc_html($co{'title'}), $co{'tree'}, $hash);
 	}
 	print "<div class=\"title_text\">\n" .
 	      "<table cellspacing=\"0\">\n";
@@ -2171,9 +2161,7 @@ sub git_blobdiff {
 		my $formats_nav = 
 			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
 		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
-		print "<div>\n" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" .
-		      "</div>\n";
+		git_header_div('commit', esc_html($co{'title'}), $hash_base);
 	} else {
 		print "<div class=\"page_nav\">\n" .
 		      "<br/><br/></div>\n" .
@@ -2225,9 +2213,7 @@ sub git_commitdiff {
 	my $formats_nav =
 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
 	git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
-	      "</div>\n";
+	git_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
 	print "<div class=\"page_body\">\n";
 	my $comment = $co{'comment'};
 	my $empty = 0;
@@ -2357,9 +2343,7 @@ sub git_history {
 	my $refs = read_info_ref();
 	git_header_html();
 	git_page_nav('','', $hash_base,$co{'tree'},$hash_base);
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" .
-	      "</div>\n";
+	git_header_div('commit', esc_html($co{'title'}), $hash_base);
 	if (!defined $hash && defined $file_name) {
 		$hash = git_get_hash_by_path($hash_base, $file_name);
 	}
@@ -2440,10 +2424,8 @@ sub git_search {
 	}
 	git_header_html();
 	git_page_nav('','', $hash,$co{'tree'},$hash);
+	git_header_div('commit', esc_html($co{'title'}), $hash);
 
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" .
-	      "</div>\n";
 	print "<table cellspacing=\"0\">\n";
 	my $alternate = 0;
 	if ($commit_search) {
@@ -2570,10 +2552,8 @@ sub git_shortlog {
 
 	git_header_html();
 	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
+	git_header_div('summary', $project);
 
-	print "<div>\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
-	      "</div>\n";
 	print "<table cellspacing=\"0\">\n";
 	my $alternate = 0;
 	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
-- 
1.4.0
Jakub Narebski· Jul 30, 2006, 20:36 UTC · re: Jakub Narebski · lore

[PATCH 16] gitweb: Remove characters entities entirely when shortening string

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Yet another error noticed due to strict validation 
of application/xhtml+xml by Mozilla.
 gitweb/gitweb.cgi |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
Show changes to gitweb/gitweb.cgi +1 −0
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 06a6930..83ea97a 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -776,6 +776,7 @@ sub chop_str {
 	my $tail = $2;
 	if (length($tail) > 4) {
 		$tail = " ...";
+		$body =~ s/&[^;]$//; # remove chopped character entities
 	}
 	return "$body$tail";
 }
-- 
1.4.0
Jakub Narebski· Jul 31, 2006, 16:59 UTC · re: Jakub Narebski · lore

Re: [PATCH 16] gitweb: Remove characters entities entirely when shortening string

Jakub Narebski wrote:
Show 10 quoted lines
> --- a/gitweb/gitweb.cgi
> +++ b/gitweb/gitweb.cgi
> @@ -776,6 +776,7 @@ sub chop_str {
>       my $tail = $2;
>       if (length($tail) > 4) {
>               $tail = " ...";
> +             $body =~ s/&[^;]$//; # remove chopped character entities
>       }
>       return "$body$tail";
>  }
Of course it should be
+               $body =~ s/&[^;]*$//; # remove chopped character entities
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Jakub Narebski· Jul 31, 2006, 18:58 UTC · re: Jakub Narebski · lore

Re: [PATCH 16b] gitweb: Remove characters entities entirely when shortening string -- correction

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Apply it on top of [PATCH 16] to correct it;
this patch was generated after [PATCH 19].
 gitweb/gitweb.cgi |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
Show changes to gitweb/gitweb.cgi +1 −1
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index f7fe28a..cb6af39 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -776,7 +776,7 @@ sub chop_str {
 	my $tail = $2;
 	if (length($tail) > 4) {
 		$tail = " ...";
-		$body =~ s/&[^;]$//; # remove chopped character entities
+		$body =~ s/&[^;]*$//; # remove chopped character entities
 	}
 	return "$body$tail";
 }
-- 
1.4.1.1
Jakub Narebski· Jul 31, 2006, 00:21 UTC · re: Jakub Narebski · lore

[PATCH 17] gitweb: Ref refactoring - use git_get_referencing for marking tagged/head commits

Use git_get_referencing to get HTML code for markers showing which refs (tags and heads) point to current commit. It would be much easier to change format of markers in one or two places than thorough the gitweb.cgi file.

Added comment about read_info_ref subroutine: for $type == "" (empty argument) it saves only last path part of ref name e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'.

Some reordering.  Added $ref in one place.
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |   53 ++++++++++++++++++++++-------------------------------
 1 files changed, 22 insertions(+), 31 deletions(-)
Show changes to gitweb/gitweb.cgi +22 −31
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 83ea97a..dab6068 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1024,6 +1024,8 @@ sub read_info_ref {
 	open my $fd, "$projectroot/$project/info/refs" or return;
 	while (my $line = <$fd>) {
 		chomp $line;
+		# attention: for $type == "" it saves only last path part of ref name
+		# e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
 		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
 			if (defined $refs{$1}) {
 				$refs{$1} .= " / $2";
@@ -1036,6 +1038,16 @@ sub read_info_ref {
 	return \%refs;
 }
 
+sub git_get_referencing {
+	my ($refs, $id) = @_;
+
+	if (defined $refs->{$id}) {
+		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
+	} else {
+		return "";
+	}
+}
+
 sub git_read_refs {
 	my $ref_dir = shift;
 	my @reflist;
@@ -1151,10 +1163,7 @@ sub git_summary {
 		}
 		$alternate ^= 1;
 		if ($i-- > 0) {
-			my $ref = "";
-			if (defined $refs->{$commit}) {
-				$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
-			}
+			my $ref = git_get_referencing($refs, $commit);
 			print "<td><i>$co{'age_string'}</i></td>\n" .
 			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
 			      "<td>";
@@ -1728,10 +1737,7 @@ sub git_tree {
 	$/ = "\n";
 
 	my $refs = read_info_ref();
-	my $ref = "";
-	if (defined $refs->{$hash_base}) {
-		$ref = " <span class=\"tag\">" . esc_html($refs->{$hash_base}) . "</span>";
-	}
+	my $ref = git_get_referencing($refs, $hash_base);
 	git_header_html();
 	my $base_key = "";
 	my $base = "";
@@ -1912,10 +1918,7 @@ sub git_log {
 	}
 	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
 		my $commit = $revlist[$i];
-		my $ref = "";
-		if (defined $refs->{$commit}) {
-			$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
-		}
+		my $ref = git_get_referencing($refs, $commit);
 		my %co = git_read_commit($commit);
 		next if !%co;
 		my %ad = date_str($co{'author_epoch'});
@@ -1979,16 +1982,13 @@ sub git_commit {
 		$expires = "+1d";
 	}
 	my $refs = read_info_ref();
-	my $ref = "";
-	if (defined $refs->{$co{'id'}}) {
-		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>";
-	}
-	git_header_html(undef, $expires);
+	my $ref = git_get_referencing($refs, $co{'id'});
 	my $formats_nav = '';
 	if (defined $file_name && defined $co{'parent'}) {
 		my $parent = $co{'parent'};
 		$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame");
 	}
+	git_header_html(undef, $expires);
 	git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', 
 							 $hash, $co{'tree'}, $hash,
 							 $formats_nav);
@@ -1996,7 +1996,7 @@ sub git_commit {
 	if (defined $co{'parent'}) {
 		git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
 	} else {
-		git_header_div('tree', esc_html($co{'title'}), $co{'tree'}, $hash);
+		git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
 	}
 	print "<div class=\"title_text\">\n" .
 	      "<table cellspacing=\"0\">\n";
@@ -2206,13 +2206,10 @@ sub git_commitdiff {
 		$expires = "+1d";
 	}
 	my $refs = read_info_ref();
-	my $ref = "";
-	if (defined $refs->{$co{'id'}}) {
-		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>";
-	}
-	git_header_html(undef, $expires);
+	my $ref = git_get_referencing($refs, $co{'id'});
 	my $formats_nav =
 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
+	git_header_html(undef, $expires);
 	git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
 	git_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
 	print "<div class=\"page_body\">\n";
@@ -2364,10 +2361,7 @@ sub git_history {
 			if (!%co) {
 				next;
 			}
-			my $ref = "";
-			if (defined $refs->{$commit}) {
-				$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
-			}
+			my $ref = git_get_referencing($refs, $commit);
 			if ($alternate) {
 				print "<tr class=\"dark\">\n";
 			} else {
@@ -2559,10 +2553,7 @@ sub git_shortlog {
 	my $alternate = 0;
 	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
 		my $commit = $revlist[$i];
-		my $ref = "";
-		if (defined $refs->{$commit}) {
-			$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
-		}
+		my $ref = git_get_referencing($refs, $commit);
 		my %co = git_read_commit($commit);
 		my %ad = date_str($co{'author_epoch'});
 		if ($alternate) {
-- 
1.4.0
Jakub Narebski· Jul 31, 2006, 09:22 UTC · re: Jakub Narebski · lore

[PATCH 18] gitweb: Refactor generation of shortlog, tags and heads body

Add git_shortlog_body, git_tags_body and git_heads_body to generate table with shortlog, tags and heads respectively in git_summary and git_shortlog, git_tags, git_heads respectively.

Better support for lightweight tags in git_read_refs; currently only lightweight tag pointing to tag object is not resolved fully.

Shortlog, tags and heads body tables have proper class now (we could use id instead of class).

Add support for showing full comment on mouseover to tags list when comment is shortened, similar to how full title of commit was/is shown on mouseover when title was shortened. Changed layout of tags table to better show lightweight tags.

Add showing which branch (head) is current branch (current head), using "current_head" class (we could use id instead).

Corrected "</table\n>" and hit_header_div instead of git_header_div.
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Largest patch in the series. Refactoring and unification.
New features (table classes, current head) are fairly unintrusive.
 gitweb/gitweb.cgi |  383 ++++++++++++++++++++++++-----------------------------
 gitweb/gitweb.css |   10 +
 2 files changed, 184 insertions(+), 209 deletions(-)
Show changes to 2 files +184 −209

gitweb/gitweb.cgi, gitweb/gitweb.css

diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index dab6068..d209af0 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1102,6 +1102,10 @@ sub git_read_refs {
 			$ref_item{'refid'} = $ref_id;
 			$ref_item{'epoch'} = $co{'committer_epoch'};
 			$ref_item{'age'} = $co{'age_string'};
+		} else {
+			$ref_item{'reftype'} = $type;
+			$ref_item{'name'} = $ref_file;
+			$ref_item{'refid'} = $ref_id;
 		}
 
 		push @reflist, \%ref_item;
@@ -1111,6 +1115,156 @@ sub git_read_refs {
 	return \@reflist;
 }
 
+sub git_shortlog_body {
+	# uses global variable $project
+	my ($revlist, $from, $to, $refs, $extra) = @_;
+	$from = 0 unless defined $from;
+	$to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
+
+	print "<table class=\"shortlog\" cellspacing=\"0\">\n";
+	my $alternate = 0;
+	for (my $i = $from; $i <= $to; $i++) {
+		my $commit = $revlist->[$i];
+		#my $ref = defined $refs ? git_get_referencing($refs, $commit) : '';
+		my $ref = git_get_referencing($refs, $commit);
+		my %co = git_read_commit($commit);
+		my %ad = date_str($co{'author_epoch'});
+		if ($alternate) {
+			print "<tr class=\"dark\">\n";
+		} else {
+			print "<tr class=\"light\">\n";
+		}
+		$alternate ^= 1;
+		# git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
+		print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+		      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
+		      "<td>";
+		if (length($co{'title_short'}) < length($co{'title'})) {
+			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
+			               -class => "list", -title => "$co{'title'}"},
+			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
+		} else {
+			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
+			               -class => "list"},
+			      "<b>" . esc_html($co{'title'}) . "$ref</b>");
+		}
+		print "</td>\n" .
+		      "<td class=\"link\">" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
+		      "</td>\n" .
+		      "</tr>\n";
+	}
+	if (defined $extra) {
+		print "<tr>\n" .
+		      "<td colspan=\"4\">$extra</td>\n" .
+		      "</tr>\n";
+	}
+	print "</table>\n";
+}
+
+sub git_tags_body {
+	# uses global variable $project
+	my ($taglist, $from, $to, $extra) = @_;
+	$from = 0 unless defined $from;
+	$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
+
+	print "<table class=\"tags\" cellspacing=\"0\">\n";
+	my $alternate = 0;
+	for (my $i = $from; $i <= $to; $i++) {
+		my $entry = $taglist->[$i];
+		my %tag = %$entry;
+		my $comment_lines = $tag{'comment'};
+		my $comment = shift @$comment_lines;
+		my $comment_short;
+		if (defined $comment) {
+			$comment_short = chop_str($comment, 30, 5);
+		}
+		if ($alternate) {
+			print "<tr class=\"dark\">\n";
+		} else {
+			print "<tr class=\"light\">\n";
+		}
+		$alternate ^= 1;
+		print "<td><i>$tag{'age'}</i></td>\n" .
+		      "<td>" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"),
+		               -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
+		      "</td>\n" .
+		      "<td>";
+		if (defined $comment) {
+			if (length($comment_short) < length($comment)) {
+				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
+				               -class => "list", -title => $comment}, $comment_short);
+			} else {
+				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
+				               -class => "list"}, $comment);
+			}
+		}
+		print "</td>\n" .
+		      "<td class=\"selflink\">";
+		if ($tag{'type'} eq "tag") {
+			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag");
+		} else {
+			print "&nbsp;";
+		}
+		print "</td>\n" .
+		      "<td class=\"link\">" . " | " .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
+		if ($tag{'reftype'} eq "commit") {
+			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
+			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
+		} elsif ($tag{'reftype'} eq "blob") {
+			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw");
+		}
+		print "</td>\n" .
+		      "</tr>";
+	}
+	if (defined $extra) {
+		print "<tr>\n" .
+		      "<td colspan=\"5\">$extra</td>\n" .
+		      "</tr>\n";
+	}
+	print "</table>\n";
+}
+
+sub git_heads_body {
+	# uses global variable $project
+	my ($taglist, $head, $from, $to, $extra) = @_;
+	$from = 0 unless defined $from;
+	$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
+
+	print "<table class=\"heads\" cellspacing=\"0\">\n";
+	my $alternate = 0;
+	for (my $i = $from; $i <= $to; $i++) {
+		my $entry = $taglist->[$i];
+		my %tag = %$entry;
+		my $curr = $tag{'id'} eq $head;
+		if ($alternate) {
+			print "<tr class=\"dark\">\n";
+		} else {
+			print "<tr class=\"light\">\n";
+		}
+		$alternate ^= 1;
+		print "<td><i>$tag{'age'}</i></td>\n" .
+		      ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"),
+		               -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
+		      "</td>\n" .
+		      "<td class=\"link\">" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
+		      "</td>\n" .
+		      "</tr>";
+	}
+	if (defined $extra) {
+		print "<tr>\n" .
+		      "<td colspan=\"3\">$extra</td>\n" .
+		      "</tr>\n";
+	}
+	print "</table>\n";
+}
+
 sub git_summary {
 	my $descr = git_read_description($project) || "none";
 	my $head = git_read_head($project);
@@ -1139,138 +1293,36 @@ sub git_summary {
 	my $refs = read_info_ref();
 	git_header_html();
 	git_page_nav('summary','', $head);
+
 	print "<div class=\"title\">&nbsp;</div>\n";
 	print "<table cellspacing=\"0\">\n" .
 	      "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
 	      "<tr><td>owner</td><td>$owner</td></tr>\n" .
 	      "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
 	      "</table>\n";
+
 	open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) 
 		or die_error(undef, "Open git-rev-list failed.");
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd;
 	git_header_div('shortlog');
-	my $i = 16;
-	print "<table cellspacing=\"0\">\n";
-	my $alternate = 0;
-	foreach my $commit (@revlist) {
-		my %co = git_read_commit($commit);
-		my %ad = date_str($co{'author_epoch'});
-		if ($alternate) {
-			print "<tr class=\"dark\">\n";
-		} else {
-			print "<tr class=\"light\">\n";
-		}
-		$alternate ^= 1;
-		if ($i-- > 0) {
-			my $ref = git_get_referencing($refs, $commit);
-			print "<td><i>$co{'age_string'}</i></td>\n" .
-			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
-			      "<td>";
-			if (length($co{'title_short'}) < length($co{'title'})) {
-				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"},
-				      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
-			} else {
-				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"},
-				      "<b>" . esc_html($co{'title'}) . "$ref</b>");
-			}
-			print "</td>\n" .
-			      "<td class=\"link\">" .
-			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
-			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
-			      "</td>\n" .
-			      "</tr>";
-		} else {
-			print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...") . "</td>\n" .
-			"</tr>";
-			last;
-		}
-	}
-	print "</table\n>";
+	git_shortlog_body(\@revlist, 0, 15, $refs,
+	                  $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "..."));
 
 	my $taglist = git_read_refs("refs/tags");
 	if (defined @$taglist) {
 		git_header_div('tags');
-		my $i = 16;
-		print "<table cellspacing=\"0\">\n";
-		my $alternate = 0;
-		foreach my $entry (@$taglist) {
-			my %tag = %$entry;
-			my $comment_lines = $tag{'comment'};
-			my $comment = shift @$comment_lines;
-			if (defined($comment)) {
-				$comment = chop_str($comment, 30, 5);
-			}
-			if ($alternate) {
-				print "<tr class=\"dark\">\n";
-			} else {
-				print "<tr class=\"light\">\n";
-			}
-			$alternate ^= 1;
-			if ($i-- > 0) {
-				print "<td><i>$tag{'age'}</i></td>\n" .
-				      "<td>" .
-				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"},
-				      "<b>" . esc_html($tag{'name'}) . "</b>") .
-				      "</td>\n" .
-				      "<td>";
-				if (defined($comment)) {
-					print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment));
-				}
-				print "</td>\n" .
-				      "<td class=\"link\">";
-				if ($tag{'type'} eq "tag") {
-					print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
-				}
-				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
-				if ($tag{'reftype'} eq "commit") {
-					print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
-					      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
-				}
-				print "</td>\n" .
-				      "</tr>";
-			} else {
-				print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...") . "</td>\n" .
-				"</tr>";
-				last;
-			}
-		}
-		print "</table\n>";
+		git_tags_body($taglist, 0, 15,
+		              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "..."));
 	}
 
 	my $headlist = git_read_refs("refs/heads");
 	if (defined @$headlist) {
 		git_header_div('heads');
-		my $i = 16;
-		print "<table cellspacing=\"0\">\n";
-		my $alternate = 0;
-		foreach my $entry (@$headlist) {
-			my %tag = %$entry;
-			if ($alternate) {
-				print "<tr class=\"dark\">\n";
-			} else {
-				print "<tr class=\"light\">\n";
-			}
-			$alternate ^= 1;
-			if ($i-- > 0) {
-				print "<td><i>$tag{'age'}</i></td>\n" .
-				      "<td>" .
-				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"},
-				      "<b>" . esc_html($tag{'name'}) . "</b>") .
-				      "</td>\n" .
-				      "<td class=\"link\">" .
-				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
-				      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
-				      "</td>\n" .
-				      "</tr>";
-			} else {
-				print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...") . "</td>\n" .
-				"</tr>";
-				last;
-			}
-		}
-		print "</table\n>";
+		git_heads_body($taglist, $head, 0, 15,
+		               $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
 	}
+
 	git_footer_html();
 }
 
@@ -1472,48 +1524,11 @@ sub git_tags {
 	git_header_html();
 	git_page_nav('','', $head,undef,$head);
 	git_header_div('summary', $project);
-	print "<table cellspacing=\"0\">\n";
 
 	my $taglist = git_read_refs("refs/tags");
-	my $alternate = 0;
 	if (defined @$taglist) {
-		foreach my $entry (@$taglist) {
-			my %tag = %$entry;
-			my $comment_lines = $tag{'comment'};
-			my $comment = shift @$comment_lines;
-			if (defined($comment)) {
-				$comment = chop_str($comment, 30, 5);
-			}
-			if ($alternate) {
-				print "<tr class=\"dark\">\n";
-			} else {
-				print "<tr class=\"light\">\n";
-			}
-			$alternate ^= 1;
-			print "<td><i>$tag{'age'}</i></td>\n" .
-			      "<td>" .
-			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"},
-			      "<b>" . esc_html($tag{'name'}) . "</b>") .
-			      "</td>\n" .
-			      "<td>";
-			if (defined($comment)) {
-				print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment);
-			}
-			print "</td>\n" .
-			      "<td class=\"link\">";
-			if ($tag{'type'} eq "tag") {
-				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
-			}
-			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
-			if ($tag{'reftype'} eq "commit") {
-				print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
-				      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
-			}
-			print "</td>\n" .
-			      "</tr>";
-		}
+		git_tags_body($taglist);
 	}
-	print "</table\n>";
 	git_footer_html();
 }
 
@@ -1521,32 +1536,13 @@ sub git_heads {
 	my $head = git_read_head($project);
 	git_header_html();
 	git_page_nav('','', $head,undef,$head);
-	hit_header_div('summary', $project);
-	print "<table cellspacing=\"0\">\n";
+	git_header_div('summary', $project);
 
 	my $taglist = git_read_refs("refs/heads");
 	my $alternate = 0;
 	if (defined @$taglist) {
-		foreach my $entry (@$taglist) {
-			my %tag = %$entry;
-			if ($alternate) {
-				print "<tr class=\"dark\">\n";
-			} else {
-				print "<tr class=\"light\">\n";
-			}
-			$alternate ^= 1;
-			print "<td><i>$tag{'age'}</i></td>\n" .
-			      "<td>" .
-			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
-			      "</td>\n" .
-			      "<td class=\"link\">" .
-			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
-			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
-			      "</td>\n" .
-			      "</tr>";
-		}
+		git_heads_body($taglist, $head);
 	}
-	print "</table\n>";
 	git_footer_html();
 }
 
@@ -2544,48 +2540,19 @@ sub git_shortlog {
 	close $fd;
 
 	my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist);
+	my $next_link = '';
+	if ($#revlist >= (100 * ($page+1)-1)) {
+		$next_link =
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)),
+			         -title => "Alt-n"}, "next");
+	}
+
 
 	git_header_html();
 	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
 	git_header_div('summary', $project);
 
-	print "<table cellspacing=\"0\">\n";
-	my $alternate = 0;
-	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
-		my $commit = $revlist[$i];
-		my $ref = git_get_referencing($refs, $commit);
-		my %co = git_read_commit($commit);
-		my %ad = date_str($co{'author_epoch'});
-		if ($alternate) {
-			print "<tr class=\"dark\">\n";
-		} else {
-			print "<tr class=\"light\">\n";
-		}
-		$alternate ^= 1;
-		print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-		      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
-		      "<td>";
-		if (length($co{'title_short'}) < length($co{'title'})) {
-			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"},
-			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
-		} else {
-			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"},
-			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
-		}
-		print "</td>\n" .
-		      "<td class=\"link\">" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
-		      "</td>\n" .
-		      "</tr>";
-	}
-	if ($#revlist >= (100 * ($page+1)-1)) {
-		print "<tr>\n" .
-		      "<td>" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -title => "Alt-n"}, "next") .
-		      "</td>\n" .
-		      "</tr>\n";
-	}
-	print "</table\n>";
+	git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);
+
 	git_footer_html();
 }
diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css
index fffdb13..460e728 100644
--- a/gitweb/gitweb.css
+++ b/gitweb/gitweb.css
@@ -181,12 +181,16 @@ td {
 	vertical-align: top;
 }
 
-td.link {
+td.link, td.selflink {
 	padding: 2px 5px;
 	font-family: sans-serif;
 	font-size: 10px;
 }
 
+td.selflink {
+	padding-right: 0px;
+}
+
 td.sha1 {
 	font-family: monospace;
 }
@@ -196,6 +200,10 @@ td.error {
 	background-color: yellow;
 }
 
+td.current_head {
+	text-decoration: underline;
+}
+
 table.diff_tree span.file_status.new {
 	color: #008000;
 }
-- 
1.4.0
Jakub Narebski· Jul 31, 2006, 16:33 UTC · re: Jakub Narebski · lore

[PATCH 19] gitweb: No need to quote path for list version of open "-|"

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
Show changes to gitweb/gitweb.cgi +1 −1
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index d209af0..f7fe28a 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -2347,7 +2347,7 @@ sub git_history {
 	git_print_page_path($file_name, $ftype);
 
 	open my $fd, "-|",
-		$GIT, "rev-list", "--full-history", $hash_base, "--", "\'$file_name\'";
+		$GIT, "rev-list", "--full-history", $hash_base, "--", $file_name;
 	print "<table cellspacing=\"0\">\n";
 	my $alternate = 0;
 	while (my $line = <$fd>) {
-- 
1.4.1.1
Junio C Hamano· Jul 31, 2006, 18:55 UTC · re: Jakub Narebski · lore

Re: [PATCH 19] gitweb: No need to quote path for list version of open "-|"

I suspect the patch title is wrong -- it is not "no need to" but "quoting path breaks so do not do it" ;-).

I guess this fixes the problem I saw last night?
Jakub Narebski· Jul 31, 2006, 19:00 UTC · re: Junio C Hamano · lore

Re: [PATCH 19] gitweb: No need to quote path for list version of open "-|"

<opublikowany i wysłany>
Junio C Hamano wrote:
> I suspect the patch title is wrong -- it is not "no need to" but
> "quoting path breaks so do not do it" ;-).
> 
> I guess this fixes the problem I saw last night?

If I remember correctly it worked without this patch, for git 1.4.1.1 (i.e. with --full-history option, although not working as advertised: see my comment earlier in thread).

How to do sane fallback to version without "--full-history", if git binaries used do not support "--full-history"?

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Junio C Hamano· Aug 1, 2006, 02:17 UTC · re: Jakub Narebski · lore

Re: [PATCH 19] gitweb: No need to quote path for list version of open "-|"

Jakub Narebski <jnareb@gmail.com> writes:
Show 12 quoted lines
> <opublikowany i wysłany>
>
> Junio C Hamano wrote:
>
>> I suspect the patch title is wrong -- it is not "no need to" but
>> "quoting path breaks so do not do it" ;-).
>> 
>> I guess this fixes the problem I saw last night?
>
> If I remember correctly it worked without this patch, for git 1.4.1.1 
> (i.e. with --full-history option, although not working as advertised: see my
> comment earlier in thread).

Couldn't have worked, or you have found a bug in whatever command you invoked. If it is told to look for a path that starts with a single quote it should have done so and produced no history since there is no such file.

In any case, the patch #19 seems to fix the problem.  Thanks.
Junio C Hamano· Aug 1, 2006, 02:18 UTC · re: Jakub Narebski · lore

There can be more than two levels of subdirectories

Earlier code to read .git/refs/{tags,heads} hierarchy had a hardcoded up-to-two-level assumption. Lift it by using File::Find.

Signed-off-by: Junio C Hamano <junkio@cox.net>
---
Show changes to gitweb/gitweb.cgi +8 −16
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 4e79390..902b96a 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -14,6 +14,7 @@ use CGI::Util qw(unescape);
 use CGI::Carp qw(fatalsToBrowser);
 use Encode;
 use Fcntl ':mode';
+use File::Find qw();
 binmode STDOUT, ':utf8';
 
 our $cgi = new CGI;
@@ -697,23 +698,14 @@ sub git_read_refs {
 	my @reflist;
 
 	my @refs;
-	opendir my $dh, "$projectroot/$project/$ref_dir";
-	while (my $dir = readdir($dh)) {
-		if ($dir =~ m/^\./) {
-			next;
-		}
-		if (-d "$projectroot/$project/$ref_dir/$dir") {
-			opendir my $dh2, "$projectroot/$project/$ref_dir/$dir";
-			my @subdirs = grep !m/^\./, readdir $dh2;
-			closedir($dh2);
-			foreach my $subdir (@subdirs) {
-				push @refs, "$dir/$subdir"
-			}
-			next;
+	my $pfxlen = length("$projectroot/$project/$ref_dir");
+	File::Find::find(sub {
+		return if (/^\./);
+		if (-f $_) {
+			push @refs, substr($File::Find::name, $pfxlen + 1);
 		}
-		push @refs, $dir;
-	}
-	closedir($dh);
+	}, "$projectroot/$project/$ref_dir");
+
 	foreach my $ref_file (@refs) {
 		my $ref_id = git_read_hash("$project/$ref_dir/$ref_file");
 		my $type = git_get_type($ref_id) || next;
Jakub Narebski· Jul 31, 2006, 18:48 UTC · re: Jakub Narebski · lore

[PATCH 20] gitweb: Reordering code and dividing it into categories

Reorder gitweb code around, divide it into sections (categories) and add some comments.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
This patch is superficially large, as it only moves code around,
and add only a little.
 gitweb/gitweb.cgi | 1672 ++++++++++++++++++++++++++++-------------------------
 1 files changed, 898 insertions(+), 774 deletions(-)
Show changes to gitweb/gitweb.cgi +898 −774
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index cb6af39..2d710ae 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -160,19 +160,65 @@ if (defined $searchtext) {
 	$searchtext = quotemeta $searchtext;
 }
 
-sub validate_input {
-	my $input = shift;
-
-	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
-		return $input;
-	}
-	if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
-		return undef;
-	}
-	if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
-		return undef;
-	}
-	return $input;
+# dispatch
+if (!defined $action || $action eq "summary") {
+	git_summary();
+	exit;
+} elsif ($action eq "heads") {
+	git_heads();
+	exit;
+} elsif ($action eq "tags") {
+	git_tags();
+	exit;
+} elsif ($action eq "blob") {
+	git_blob();
+	exit;
+} elsif ($action eq "blob_plain") {
+	git_blob_plain();
+	exit;
+} elsif ($action eq "tree") {
+	git_tree();
+	exit;
+} elsif ($action eq "rss") {
+	git_rss();
+	exit;
+} elsif ($action eq "commit") {
+	git_commit();
+	exit;
+} elsif ($action eq "log") {
+	git_log();
+	exit;
+} elsif ($action eq "blobdiff") {
+	git_blobdiff();
+	exit;
+} elsif ($action eq "blobdiff_plain") {
+	git_blobdiff_plain();
+	exit;
+} elsif ($action eq "commitdiff") {
+	git_commitdiff();
+	exit;
+} elsif ($action eq "commitdiff_plain") {
+	git_commitdiff_plain();
+	exit;
+} elsif ($action eq "history") {
+	git_history();
+	exit;
+} elsif ($action eq "search") {
+	git_search();
+	exit;
+} elsif ($action eq "shortlog") {
+	git_shortlog();
+	exit;
+} elsif ($action eq "tag") {
+	git_tag();
+	exit;
+} elsif ($action eq "blame") {
+	git_blame2();
+	exit;
+} else {
+	undef $action;
+	die_error(undef, "Unknown action.");
+	exit;
 }
 
 if (!defined $action || $action eq "summary") {
@@ -235,6 +281,24 @@ if (!defined $action || $action eq "summ
 	exit;
 }
 
+## ======================================================================
+## validation, quoting/unquoting and escaping
+
+sub validate_input {
+	my $input = shift;
+
+	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
+		return $input;
+	}
+	if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
+		return undef;
+	}
+	if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
+		return undef;
+	}
+	return $input;
+}
+
 # quote unsafe chars, but keep the slash, even when it's not
 # correct, but quoted slashes look too horrible in bookmarks
 sub esc_param {
@@ -264,6 +328,29 @@ sub unquote {
 	return $str;
 }
 
+## ----------------------------------------------------------------------
+## HTML aware string manipulation
+
+sub chop_str {
+	my $str = shift;
+	my $len = shift;
+	my $add_len = shift || 10;
+
+	# allow only $len chars, but don't cut a word if it would fit in $add_len
+	# if it doesn't fit, cut it if it's still longer than the dots we would add
+	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
+	my $body = $1;
+	my $tail = $2;
+	if (length($tail) > 4) {
+		$tail = " ...";
+		$body =~ s/&[^;]*$//; # remove chopped character entities
+	}
+	return "$body$tail";
+}
+
+## ----------------------------------------------------------------------
+## functions returning short strings
+
 # CSS class for given age value (in seconds)
 sub age_class {
 	my $age = shift;
@@ -277,202 +364,108 @@ sub age_class {
 	}
 }
 
-sub git_header_html {
-	my $status = shift || "200 OK";
-	my $expires = shift;
+# convert age in seconds to "nn units ago" string
+sub age_string {
+	my $age = shift;
+	my $age_str;
 
-	my $title = "$site_name git";
-	if (defined $project) {
-		$title .= " - $project";
-		if (defined $action) {
-			$title .= "/$action";
-			if (defined $file_name) {
-				$title .= " - $file_name";
-				if ($action eq "tree" && $file_name !~ m|/$|) {
-					$title .= "/";
-				}
-			}
-		}
-	}
-	my $content_type;
-	# require explicit support from the UA if we are to send the page as
-	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
-	# we have to do this because MSIE sometimes globs '*/*', pretending to
-	# support xhtml+xml but choking when it gets what it asked for.
-	if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
-		$content_type = 'application/xhtml+xml';
+	if ($age > 60*60*24*365*2) {
+		$age_str = (int $age/60/60/24/365);
+		$age_str .= " years ago";
+	} elsif ($age > 60*60*24*(365/12)*2) {
+		$age_str = int $age/60/60/24/(365/12);
+		$age_str .= " months ago";
+	} elsif ($age > 60*60*24*7*2) {
+		$age_str = int $age/60/60/24/7;
+		$age_str .= " weeks ago";
+	} elsif ($age > 60*60*24*2) {
+		$age_str = int $age/60/60/24;
+		$age_str .= " days ago";
+	} elsif ($age > 60*60*2) {
+		$age_str = int $age/60/60;
+		$age_str .= " hours ago";
+	} elsif ($age > 60*2) {
+		$age_str = int $age/60;
+		$age_str .= " min ago";
+	} elsif ($age > 2) {
+		$age_str = int $age;
+		$age_str .= " sec ago";
 	} else {
-		$content_type = 'text/html';
-	}
-	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
-	print <<EOF;
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
-<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
-<!-- git core binaries version $git_version -->
-<head>
-<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
-<meta name="robots" content="index, nofollow"/>
-<title>$title</title>
-<link rel="stylesheet" type="text/css" href="$stylesheet"/>
-$rss_link
-</head>
-<body>
-EOF
-	print "<div class=\"page_header\">\n" .
-	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
-	      "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
-	      "</a>\n";
-	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
-	if (defined $project) {
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project));
-		if (defined $action) {
-			print " / $action";
-		}
-		print "\n";
-		if (!defined $searchtext) {
-			$searchtext = "";
-		}
-		my $search_hash;
-		if (defined $hash_base) {
-			$search_hash = $hash_base;
-		} elsif (defined $hash) {
-			$search_hash = $hash;
-		} else {
-			$search_hash = "HEAD";
-		}
-		$cgi->param("a", "search");
-		$cgi->param("h", $search_hash);
-		print $cgi->startform(-method => "get", -action => $my_uri) .
-		      "<div class=\"search\">\n" .
-		      $cgi->hidden(-name => "p") . "\n" .
-		      $cgi->hidden(-name => "a") . "\n" .
-		      $cgi->hidden(-name => "h") . "\n" .
-		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
-		      "</div>" .
-		      $cgi->end_form() . "\n";
+		$age_str .= " right now";
 	}
-	print "</div>\n";
+	return $age_str;
 }
 
-sub git_footer_html {
-	print "<div class=\"page_footer\">\n";
-	if (defined $project) {
-		my $descr = git_read_description($project);
-		if (defined $descr) {
-			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
-		}
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n";
+# convert file mode in octal to symbolic file mode string
+sub mode_str {
+	my $mode = oct shift;
+
+	if (S_ISDIR($mode & S_IFMT)) {
+		return 'drwxr-xr-x';
+	} elsif (S_ISLNK($mode)) {
+		return 'lrwxrwxrwx';
+	} elsif (S_ISREG($mode)) {
+		# git cares only about the executable bit
+		if ($mode & S_IXUSR) {
+			return '-rwxr-xr-x';
+		} else {
+			return '-rw-r--r--';
+		};
 	} else {
-		print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n";
+		return '----------';
 	}
-	print "</div>\n" .
-	      "</body>\n" .
-	      "</html>";
 }
 
-sub die_error {
-	my $status = shift || "403 Forbidden";
-	my $error = shift || "Malformed query, file missing or permission denied";
+# convert file mode in octal to file type string
+sub file_type {
+	my $mode = oct shift;
 
-	git_header_html($status);
-	print "<div class=\"page_body\">\n" .
-	      "<br/><br/>\n" .
-	      "$status - $error\n" .
-	      "<br/>\n" .
-	      "</div>\n";
-	git_footer_html();
-	exit;
+	if (S_ISDIR($mode & S_IFMT)) {
+		return "directory";
+	} elsif (S_ISLNK($mode)) {
+		return "symlink";
+	} elsif (S_ISREG($mode)) {
+		return "file";
+	} else {
+		return "unknown";
+	}
 }
 
-sub git_page_nav {
-	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
-	$extra = '' if !defined $extra; # pager or formats
+## ----------------------------------------------------------------------
+## functions returning short HTML fragments, or transforming HTML fragments
+## which don't beling to other sections
 
-	my @navs = qw(summary shortlog log commit commitdiff tree);
-	if ($suppress) {
-		@navs = grep { $_ ne $suppress } @navs;
-	}
+# format line of commit message or tag comment
+sub format_log_line_html {
+	my $line = shift;
 
-	my %arg = map { $_, ''} @navs;
-	if (defined $head) {
-		for (qw(commit commitdiff)) {
-			$arg{$_} = ";h=$head";
-		}
-		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
-			for (qw(shortlog log)) {
-				$arg{$_} = ";h=$head";
-			}
+	$line = esc_html($line);
+	$line =~ s/ /&nbsp;/g;
+	if ($line =~ m/([0-9a-fA-F]{40})/) {
+		my $hash_text = $1;
+		if (git_get_type($hash_text) eq "commit") {
+			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
+			$line =~ s/$hash_text/$link/;
 		}
 	}
-	$arg{tree} .= ";h=$treehead" if defined $treehead;
-	$arg{tree} .= ";hb=$treebase" if defined $treebase;
-
-	print "<div class=\"page_nav\">\n" .
-		(join " | ",
-		 map { $_ eq $current
-					 ? $_
-					 : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_")
-				 }
-		 @navs);
-	print "<br/>\n$extra<br/>\n" .
-	      "</div>\n";
-}
-
-sub git_header_div {
-	my ($action, $title, $hash, $hash_base) = @_;
-	my $rest = '';
-
-	$rest .= ";h=$hash" if $hash;
-	$rest .= ";hb=$hash_base" if $hash_base;
-
-	print "<div class=\"header\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"),
-	               -class => "title"}, $title ? $title : $action) . "\n" .
-	      "</div>\n";
+	return $line;
 }
 
-sub git_get_paging_nav {
-	my ($action, $hash, $head, $page, $nrevs) = @_;
-	my $paging_nav;
-
-
-	if ($hash ne $head || $page) {
-		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
-	} else {
-		$paging_nav .= "HEAD";
-	}
-
-	if ($page > 0) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)),
-							 -accesskey => "p", -title => "Alt-p"}, "prev");
-	} else {
-		$paging_nav .= " &sdot; prev";
-	}
+# format marker of refs pointing to given object
+sub git_get_referencing {
+	my ($refs, $id) = @_;
 
-	if ($nrevs >= (100 * ($page+1)-1)) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)),
-							 -accesskey => "n", -title => "Alt-n"}, "next");
+	if (defined $refs->{$id}) {
+		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
 	} else {
-		$paging_nav .= " &sdot; next";
+		return "";
 	}
-
-	return $paging_nav;
 }
 
-sub git_get_type {
-	my $hash = shift;
-
-	open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return;
-	my $type = <$fd>;
-	close $fd or return;
-	chomp $type;
-	return $type;
-}
+## ----------------------------------------------------------------------
+## git utility subroutines, invoking git commands
 
+# get HEAD ref of given project as hash
 sub git_read_head {
 	my $project = shift;
 	my $oENV = $ENV{'GIT_DIR'};
@@ -491,6 +484,57 @@ sub git_read_head {
 	return $retval;
 }
 
+# get type of given object
+sub git_get_type {
+	my $hash = shift;
+
+	open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return;
+	my $type = <$fd>;
+	close $fd or return;
+	chomp $type;
+	return $type;
+}
+
+sub git_get_project_config {
+	my $key = shift;
+
+	return unless ($key);
+	$key =~ s/^gitweb\.//;
+	return if ($key =~ m/\W/);
+
+	my $val = qx($GIT repo-config --get gitweb.$key);
+	return ($val);
+}
+
+sub git_get_project_config_bool {
+	my $val = git_get_project_config (@_);
+	if ($val and $val =~ m/true|yes|on/) {
+		return (1);
+	}
+	return; # implicit false
+}
+
+# get hash of given path at given ref
+sub git_get_hash_by_path {
+	my $base = shift;
+	my $path = shift || return undef;
+
+	my $tree = $base;
+
+	open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
+		or die_error(undef, "Open git-ls-tree failed.");
+	my $line = <$fd>;
+	close $fd or return undef;
+
+	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
+	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+	return $3;
+}
+
+## ......................................................................
+## git utility functions, directly accessing git repository
+
+# assumes that PATH is not symref
 sub git_read_hash {
 	my $path = shift;
 
@@ -513,6 +557,100 @@ sub git_read_description {
 	return $descr;
 }
 
+sub git_read_projects {
+	my @list;
+
+	if (-d $projects_list) {
+		# search in directory
+		my $dir = $projects_list;
+		opendir my ($dh), $dir or return undef;
+		while (my $dir = readdir($dh)) {
+			if (-e "$projectroot/$dir/HEAD") {
+				my $pr = {
+					path => $dir,
+				};
+				push @list, $pr
+			}
+		}
+		closedir($dh);
+	} elsif (-f $projects_list) {
+		# read from file(url-encoded):
+		# 'git%2Fgit.git Linus+Torvalds'
+		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
+		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+		open my ($fd), $projects_list or return undef;
+		while (my $line = <$fd>) {
+			chomp $line;
+			my ($path, $owner) = split ' ', $line;
+			$path = unescape($path);
+			$owner = unescape($owner);
+			if (!defined $path) {
+				next;
+			}
+			if (-e "$projectroot/$path/HEAD") {
+				my $pr = {
+					path => $path,
+					owner => decode("utf8", $owner, Encode::FB_DEFAULT),
+				};
+				push @list, $pr
+			}
+		}
+		close $fd;
+	}
+	@list = sort {$a->{'path'} cmp $b->{'path'}} @list;
+	return @list;
+}
+
+sub read_info_ref {
+	my $type = shift || "";
+	my %refs;
+	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11
+	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{}
+	open my $fd, "$projectroot/$project/info/refs" or return;
+	while (my $line = <$fd>) {
+		chomp $line;
+		# attention: for $type == "" it saves only last path part of ref name
+		# e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
+		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
+			if (defined $refs{$1}) {
+				$refs{$1} .= " / $2";
+			} else {
+				$refs{$1} = $2;
+			}
+		}
+	}
+	close $fd or return;
+	return \%refs;
+}
+
+## ----------------------------------------------------------------------
+## parse to hash functions
+
+sub date_str {
+	my $epoch = shift;
+	my $tz = shift || "-0000";
+
+	my %date;
+	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
+	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
+	$date{'hour'} = $hour;
+	$date{'minute'} = $min;
+	$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{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;
+
+	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
+	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
+	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
+	$date{'hour_local'} = $hour;
+	$date{'minute_local'} = $min;
+	$date{'tz_local'} = $tz;
+	return %date;
+}
+
 sub git_read_tag {
 	my $tag_id = shift;
 	my %tag;
@@ -548,37 +686,6 @@ sub git_read_tag {
 	return %tag
 }
 
-sub age_string {
-	my $age = shift;
-	my $age_str;
-
-	if ($age > 60*60*24*365*2) {
-		$age_str = (int $age/60/60/24/365);
-		$age_str .= " years ago";
-	} elsif ($age > 60*60*24*(365/12)*2) {
-		$age_str = int $age/60/60/24/(365/12);
-		$age_str .= " months ago";
-	} elsif ($age > 60*60*24*7*2) {
-		$age_str = int $age/60/60/24/7;
-		$age_str .= " weeks ago";
-	} elsif ($age > 60*60*24*2) {
-		$age_str = int $age/60/60/24;
-		$age_str .= " days ago";
-	} elsif ($age > 60*60*2) {
-		$age_str = int $age/60/60;
-		$age_str .= " hours ago";
-	} elsif ($age > 60*2) {
-		$age_str = int $age/60;
-		$age_str .= " min ago";
-	} elsif ($age > 2) {
-		$age_str = int $age;
-		$age_str .= " sec ago";
-	} else {
-		$age_str .= " right now";
-	}
-	return $age_str;
-}
-
 sub git_read_commit {
 	my $commit_id = shift;
 	my $commit_text = shift;
@@ -673,380 +780,8 @@ sub git_read_commit {
 	return %co;
 }
 
-sub git_diff_print {
-	my $from = shift;
-	my $from_name = shift;
-	my $to = shift;
-	my $to_name = shift;
-	my $format = shift || "html";
-
-	my $from_tmp = "/dev/null";
-	my $to_tmp = "/dev/null";
-	my $pid = $$;
-
-	# create tmp from-file
-	if (defined $from) {
-		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";
-		open my $fd2, "> $from_tmp";
-		open my $fd, "-|", $GIT, "cat-file", "blob", $from;
-		my @file = <$fd>;
-		print $fd2 @file;
-		close $fd2;
-		close $fd;
-	}
-
-	# create tmp to-file
-	if (defined $to) {
-		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";
-		open my $fd2, "> $to_tmp";
-		open my $fd, "-|", $GIT, "cat-file", "blob", $to;
-		my @file = <$fd>;
-		print $fd2 @file;
-		close $fd2;
-		close $fd;
-	}
-
-	open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp";
-	if ($format eq "plain") {
-		undef $/;
-		print <$fd>;
-		$/ = "\n";
-	} else {
-		while (my $line = <$fd>) {
-			chomp $line;
-			my $char = substr($line, 0, 1);
-			my $diff_class = "";
-			if ($char eq '+') {
-				$diff_class = " add";
-			} elsif ($char eq "-") {
-				$diff_class = " rem";
-			} elsif ($char eq "@") {
-				$diff_class = " chunk_header";
-			} elsif ($char eq "\\") {
-				# skip errors
-				next;
-			}
-			while ((my $pos = index($line, "\t")) != -1) {
-				if (my $count = (8 - (($pos-1) % 8))) {
-					my $spaces = ' ' x $count;
-					$line =~ s/\t/$spaces/;
-				}
-			}
-			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
-		}
-	}
-	close $fd;
-
-	if (defined $from) {
-		unlink($from_tmp);
-	}
-	if (defined $to) {
-		unlink($to_tmp);
-	}
-}
-
-sub mode_str {
-	my $mode = oct shift;
-
-	if (S_ISDIR($mode & S_IFMT)) {
-		return 'drwxr-xr-x';
-	} elsif (S_ISLNK($mode)) {
-		return 'lrwxrwxrwx';
-	} elsif (S_ISREG($mode)) {
-		# git cares only about the executable bit
-		if ($mode & S_IXUSR) {
-			return '-rwxr-xr-x';
-		} else {
-			return '-rw-r--r--';
-		};
-	} else {
-		return '----------';
-	}
-}
-
-sub chop_str {
-	my $str = shift;
-	my $len = shift;
-	my $add_len = shift || 10;
-
-	# allow only $len chars, but don't cut a word if it would fit in $add_len
-	# if it doesn't fit, cut it if it's still longer than the dots we would add
-	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
-	my $body = $1;
-	my $tail = $2;
-	if (length($tail) > 4) {
-		$tail = " ...";
-		$body =~ s/&[^;]*$//; # remove chopped character entities
-	}
-	return "$body$tail";
-}
-
-sub file_type {
-	my $mode = oct shift;
-
-	if (S_ISDIR($mode & S_IFMT)) {
-		return "directory";
-	} elsif (S_ISLNK($mode)) {
-		return "symlink";
-	} elsif (S_ISREG($mode)) {
-		return "file";
-	} else {
-		return "unknown";
-	}
-}
-
-sub format_log_line_html {
-	my $line = shift;
-
-	$line = esc_html($line);
-	$line =~ s/ /&nbsp;/g;
-	if ($line =~ m/([0-9a-fA-F]{40})/) {
-		my $hash_text = $1;
-		if (git_get_type($hash_text) eq "commit") {
-			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
-			$line =~ s/$hash_text/$link/;
-		}
-	}
-	return $line;
-}
-
-sub date_str {
-	my $epoch = shift;
-	my $tz = shift || "-0000";
-
-	my %date;
-	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
-	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
-	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
-	$date{'hour'} = $hour;
-	$date{'minute'} = $min;
-	$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{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;
-
-	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
-	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
-	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
-	$date{'hour_local'} = $hour;
-	$date{'minute_local'} = $min;
-	$date{'tz_local'} = $tz;
-	return %date;
-}
-
-# git-logo (cached in browser for one day)
-sub git_logo {
-	binmode STDOUT, ':raw';
-	print $cgi->header(-type => 'image/png', -expires => '+1d');
-	# cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g'
-	print	"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
-		"\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
-		"\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
-		"\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
-		"\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
-		"\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
-		"\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
-		"\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
-		"\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
-		"\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
-		"\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
-		"\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
-		"\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
-}
-
-sub get_file_owner {
-	my $path = shift;
-
-	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
-	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
-	if (!defined $gcos) {
-		return undef;
-	}
-	my $owner = $gcos;
-	$owner =~ s/[,;].*$//;
-	return decode("utf8", $owner, Encode::FB_DEFAULT);
-}
-
-sub git_read_projects {
-	my @list;
-
-	if (-d $projects_list) {
-		# search in directory
-		my $dir = $projects_list;
-		opendir my ($dh), $dir or return undef;
-		while (my $dir = readdir($dh)) {
-			if (-e "$projectroot/$dir/HEAD") {
-				my $pr = {
-					path => $dir,
-				};
-				push @list, $pr
-			}
-		}
-		closedir($dh);
-	} elsif (-f $projects_list) {
-		# read from file(url-encoded):
-		# 'git%2Fgit.git Linus+Torvalds'
-		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
-		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
-		open my ($fd), $projects_list or return undef;
-		while (my $line = <$fd>) {
-			chomp $line;
-			my ($path, $owner) = split ' ', $line;
-			$path = unescape($path);
-			$owner = unescape($owner);
-			if (!defined $path) {
-				next;
-			}
-			if (-e "$projectroot/$path/HEAD") {
-				my $pr = {
-					path => $path,
-					owner => decode("utf8", $owner, Encode::FB_DEFAULT),
-				};
-				push @list, $pr
-			}
-		}
-		close $fd;
-	}
-	@list = sort {$a->{'path'} cmp $b->{'path'}} @list;
-	return @list;
-}
-
-sub git_get_project_config {
-	my $key = shift;
-
-	return unless ($key);
-	$key =~ s/^gitweb\.//;
-	return if ($key =~ m/\W/);
-
-	my $val = qx($GIT repo-config --get gitweb.$key);
-	return ($val);
-}
-
-sub git_get_project_config_bool {
-	my $val = git_get_project_config (@_);
-	if ($val and $val =~ m/true|yes|on/) {
-		return (1);
-	}
-	return; # implicit false
-}
-
-sub git_project_list {
-	my @list = git_read_projects();
-	my @projects;
-	if (!@list) {
-		die_error(undef, "No project found.");
-	}
-	foreach my $pr (@list) {
-		my $head = git_read_head($pr->{'path'});
-		if (!defined $head) {
-			next;
-		}
-		$ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
-		my %co = git_read_commit($head);
-		if (!%co) {
-			next;
-		}
-		$pr->{'commit'} = \%co;
-		if (!defined $pr->{'descr'}) {
-			my $descr = git_read_description($pr->{'path'}) || "";
-			$pr->{'descr'} = chop_str($descr, 25, 5);
-		}
-		if (!defined $pr->{'owner'}) {
-			$pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
-		}
-		push @projects, $pr;
-	}
-	git_header_html();
-	if (-f $home_text) {
-		print "<div class=\"index_include\">\n";
-		open (my $fd, $home_text);
-		print <$fd>;
-		close $fd;
-		print "</div>\n";
-	}
-	print "<table class=\"project_list\">\n" .
-	      "<tr>\n";
-	if (!defined($order) || (defined($order) && ($order eq "project"))) {
-		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
-		print "<th>Project</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n";
-	}
-	if (defined($order) && ($order eq "descr")) {
-		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
-		print "<th>Description</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n";
-	}
-	if (defined($order) && ($order eq "owner")) {
-		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
-		print "<th>Owner</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n";
-	}
-	if (defined($order) && ($order eq "age")) {
-		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
-		print "<th>Last Change</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n";
-	}
-	print "<th></th>\n" .
-	      "</tr>\n";
-	my $alternate = 0;
-	foreach my $pr (@projects) {
-		if ($alternate) {
-			print "<tr class=\"dark\">\n";
-		} else {
-			print "<tr class=\"light\">\n";
-		}
-		$alternate ^= 1;
-		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
-		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
-		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" .
-		      "<td class=\"link\">" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
-		      "</td>\n" .
-		      "</tr>\n";
-	}
-	print "</table>\n";
-	git_footer_html();
-}
-
-sub read_info_ref {
-	my $type = shift || "";
-	my %refs;
-	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11
-	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{}
-	open my $fd, "$projectroot/$project/info/refs" or return;
-	while (my $line = <$fd>) {
-		chomp $line;
-		# attention: for $type == "" it saves only last path part of ref name
-		# e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
-		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
-			if (defined $refs{$1}) {
-				$refs{$1} .= " / $2";
-			} else {
-				$refs{$1} = $2;
-			}
-		}
-	}
-	close $fd or return;
-	return \%refs;
-}
-
-sub git_get_referencing {
-	my ($refs, $id) = @_;
-
-	if (defined $refs->{$id}) {
-		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
-	} else {
-		return "";
-	}
-}
+## ......................................................................
+## parse to array of hashes functions
 
 sub git_read_refs {
 	my $ref_dir = shift;
@@ -1115,6 +850,299 @@ sub git_read_refs {
 	return \@reflist;
 }
 
+## ----------------------------------------------------------------------
+## filesystem-related functions
+
+sub get_file_owner {
+	my $path = shift;
+
+	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
+	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
+	if (!defined $gcos) {
+		return undef;
+	}
+	my $owner = $gcos;
+	$owner =~ s/[,;].*$//;
+	return decode("utf8", $owner, Encode::FB_DEFAULT);
+}
+
+## ......................................................................
+## mimetype related functions
+
+sub mimetype_guess_file {
+	my $filename = shift;
+	my $mimemap = shift;
+	-r $mimemap or return undef;
+
+	my %mimemap;
+	open(MIME, $mimemap) or return undef;
+	while (<MIME>) {
+		my ($mime, $exts) = split(/\t+/);
+		my @exts = split(/\s+/, $exts);
+		foreach my $ext (@exts) {
+			$mimemap{$ext} = $mime;
+		}
+	}
+	close(MIME);
+
+	$filename =~ /\.(.*?)$/;
+	return $mimemap{$1};
+}
+
+sub mimetype_guess {
+	my $filename = shift;
+	my $mime;
+	$filename =~ /\./ or return undef;
+
+	if ($mimetypes_file) {
+		my $file = $mimetypes_file;
+		#$file =~ m#^/# or $file = "$projectroot/$path/$file";
+		$mime = mimetype_guess_file($filename, $file);
+	}
+	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
+	return $mime;
+}
+
+sub git_blob_plain_mimetype {
+	my $fd = shift;
+	my $filename = shift;
+
+	if ($filename) {
+		my $mime = mimetype_guess($filename);
+		$mime and return $mime;
+	}
+
+	# just in case
+	return $default_blob_plain_mimetype unless $fd;
+
+	if (-T $fd) {
+		return 'text/plain' .
+		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
+	} elsif (! $filename) {
+		return 'application/octet-stream';
+	} elsif ($filename =~ m/\.png$/i) {
+		return 'image/png';
+	} elsif ($filename =~ m/\.gif$/i) {
+		return 'image/gif';
+	} elsif ($filename =~ m/\.jpe?g$/i) {
+		return 'image/jpeg';
+	} else {
+		return 'application/octet-stream';
+	}
+}
+
+## ======================================================================
+## functions printing HTML: header, footer, error page
+
+sub git_header_html {
+	my $status = shift || "200 OK";
+	my $expires = shift;
+
+	my $title = "$site_name git";
+	if (defined $project) {
+		$title .= " - $project";
+		if (defined $action) {
+			$title .= "/$action";
+			if (defined $file_name) {
+				$title .= " - $file_name";
+				if ($action eq "tree" && $file_name !~ m|/$|) {
+					$title .= "/";
+				}
+			}
+		}
+	}
+	my $content_type;
+	# require explicit support from the UA if we are to send the page as
+	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+	# we have to do this because MSIE sometimes globs '*/*', pretending to
+	# support xhtml+xml but choking when it gets what it asked for.
+	if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
+		$content_type = 'application/xhtml+xml';
+	} else {
+		$content_type = 'text/html';
+	}
+	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
+	print <<EOF;
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
+<!-- git core binaries version $git_version -->
+<head>
+<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
+<meta name="robots" content="index, nofollow"/>
+<title>$title</title>
+<link rel="stylesheet" type="text/css" href="$stylesheet"/>
+$rss_link
+</head>
+<body>
+EOF
+	print "<div class=\"page_header\">\n" .
+	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
+	      "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
+	      "</a>\n";
+	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
+	if (defined $project) {
+		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project));
+		if (defined $action) {
+			print " / $action";
+		}
+		print "\n";
+		if (!defined $searchtext) {
+			$searchtext = "";
+		}
+		my $search_hash;
+		if (defined $hash_base) {
+			$search_hash = $hash_base;
+		} elsif (defined $hash) {
+			$search_hash = $hash;
+		} else {
+			$search_hash = "HEAD";
+		}
+		$cgi->param("a", "search");
+		$cgi->param("h", $search_hash);
+		print $cgi->startform(-method => "get", -action => $my_uri) .
+		      "<div class=\"search\">\n" .
+		      $cgi->hidden(-name => "p") . "\n" .
+		      $cgi->hidden(-name => "a") . "\n" .
+		      $cgi->hidden(-name => "h") . "\n" .
+		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+		      "</div>" .
+		      $cgi->end_form() . "\n";
+	}
+	print "</div>\n";
+}
+
+sub git_footer_html {
+	print "<div class=\"page_footer\">\n";
+	if (defined $project) {
+		my $descr = git_read_description($project);
+		if (defined $descr) {
+			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
+		}
+		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n";
+	} else {
+		print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n";
+	}
+	print "</div>\n" .
+	      "</body>\n" .
+	      "</html>";
+}
+
+sub die_error {
+	my $status = shift || "403 Forbidden";
+	my $error = shift || "Malformed query, file missing or permission denied";
+
+	git_header_html($status);
+	print "<div class=\"page_body\">\n" .
+	      "<br/><br/>\n" .
+	      "$status - $error\n" .
+	      "<br/>\n" .
+	      "</div>\n";
+	git_footer_html();
+	exit;
+}
+
+## ----------------------------------------------------------------------
+## functions printing or outputting HTML: navigation
+
+sub git_page_nav {
+	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
+	$extra = '' if !defined $extra; # pager or formats
+
+	my @navs = qw(summary shortlog log commit commitdiff tree);
+	if ($suppress) {
+		@navs = grep { $_ ne $suppress } @navs;
+	}
+
+	my %arg = map { $_, ''} @navs;
+	if (defined $head) {
+		for (qw(commit commitdiff)) {
+			$arg{$_} = ";h=$head";
+		}
+		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
+			for (qw(shortlog log)) {
+				$arg{$_} = ";h=$head";
+			}
+		}
+	}
+	$arg{tree} .= ";h=$treehead" if defined $treehead;
+	$arg{tree} .= ";hb=$treebase" if defined $treebase;
+
+	print "<div class=\"page_nav\">\n" .
+		(join " | ",
+		 map { $_ eq $current
+					 ? $_
+					 : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_")
+				 }
+		 @navs);
+	print "<br/>\n$extra<br/>\n" .
+	      "</div>\n";
+}
+
+sub git_get_paging_nav {
+	my ($action, $hash, $head, $page, $nrevs) = @_;
+	my $paging_nav;
+
+
+	if ($hash ne $head || $page) {
+		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
+	} else {
+		$paging_nav .= "HEAD";
+	}
+
+	if ($page > 0) {
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)),
+							 -accesskey => "p", -title => "Alt-p"}, "prev");
+	} else {
+		$paging_nav .= " &sdot; prev";
+	}
+
+	if ($nrevs >= (100 * ($page+1)-1)) {
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)),
+							 -accesskey => "n", -title => "Alt-n"}, "next");
+	} else {
+		$paging_nav .= " &sdot; next";
+	}
+
+	return $paging_nav;
+}
+
+## ......................................................................
+## functions printing or outputting HTML: div
+
+sub git_header_div {
+	my ($action, $title, $hash, $hash_base) = @_;
+	my $rest = '';
+
+	$rest .= ";h=$hash" if $hash;
+	$rest .= ";hb=$hash_base" if $hash_base;
+
+	print "<div class=\"header\">\n" .
+	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"),
+	               -class => "title"}, $title ? $title : $action) . "\n" .
+	      "</div>\n";
+}
+
+sub git_print_page_path {
+	my $name = shift;
+	my $type = shift;
+
+	if (!defined $name) {
+		print "<div class=\"page_path\"><b>/</b></div>\n";
+	} elsif ($type =~ "blob") {
+		print "<div class=\"page_path\"><b>" .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n";
+	} else {
+		print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
+	}
+}
+
+## ......................................................................
+## functions printing large fragments of HTML
+
 sub git_shortlog_body {
 	# uses global variable $project
 	my ($revlist, $from, $to, $refs, $extra) = @_;
@@ -1265,6 +1293,191 @@ sub git_heads_body {
 	print "</table>\n";
 }
 
+## ----------------------------------------------------------------------
+## functions printing large fragments, format as one of arguments
+
+sub git_diff_print {
+	my $from = shift;
+	my $from_name = shift;
+	my $to = shift;
+	my $to_name = shift;
+	my $format = shift || "html";
+
+	my $from_tmp = "/dev/null";
+	my $to_tmp = "/dev/null";
+	my $pid = $$;
+
+	# create tmp from-file
+	if (defined $from) {
+		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";
+		open my $fd2, "> $from_tmp";
+		open my $fd, "-|", $GIT, "cat-file", "blob", $from;
+		my @file = <$fd>;
+		print $fd2 @file;
+		close $fd2;
+		close $fd;
+	}
+
+	# create tmp to-file
+	if (defined $to) {
+		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";
+		open my $fd2, "> $to_tmp";
+		open my $fd, "-|", $GIT, "cat-file", "blob", $to;
+		my @file = <$fd>;
+		print $fd2 @file;
+		close $fd2;
+		close $fd;
+	}
+
+	open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp";
+	if ($format eq "plain") {
+		undef $/;
+		print <$fd>;
+		$/ = "\n";
+	} else {
+		while (my $line = <$fd>) {
+			chomp $line;
+			my $char = substr($line, 0, 1);
+			my $diff_class = "";
+			if ($char eq '+') {
+				$diff_class = " add";
+			} elsif ($char eq "-") {
+				$diff_class = " rem";
+			} elsif ($char eq "@") {
+				$diff_class = " chunk_header";
+			} elsif ($char eq "\\") {
+				# skip errors
+				next;
+			}
+			while ((my $pos = index($line, "\t")) != -1) {
+				if (my $count = (8 - (($pos-1) % 8))) {
+					my $spaces = ' ' x $count;
+					$line =~ s/\t/$spaces/;
+				}
+			}
+			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
+		}
+	}
+	close $fd;
+
+	if (defined $from) {
+		unlink($from_tmp);
+	}
+	if (defined $to) {
+		unlink($to_tmp);
+	}
+}
+
+
+## ======================================================================
+## ======================================================================
+## actions
+
+# git-logo (cached in browser for one day)
+sub git_logo {
+	binmode STDOUT, ':raw';
+	print $cgi->header(-type => 'image/png', -expires => '+1d');
+	# cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g'
+	print	"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
+		"\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
+		"\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
+		"\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
+		"\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
+		"\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
+		"\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
+		"\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
+		"\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
+		"\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
+		"\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
+		"\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
+		"\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
+}
+
+sub git_project_list {
+	my @list = git_read_projects();
+	my @projects;
+	if (!@list) {
+		die_error(undef, "No project found.");
+	}
+	foreach my $pr (@list) {
+		my $head = git_read_head($pr->{'path'});
+		if (!defined $head) {
+			next;
+		}
+		$ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
+		my %co = git_read_commit($head);
+		if (!%co) {
+			next;
+		}
+		$pr->{'commit'} = \%co;
+		if (!defined $pr->{'descr'}) {
+			my $descr = git_read_description($pr->{'path'}) || "";
+			$pr->{'descr'} = chop_str($descr, 25, 5);
+		}
+		if (!defined $pr->{'owner'}) {
+			$pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+		}
+		push @projects, $pr;
+	}
+	git_header_html();
+	if (-f $home_text) {
+		print "<div class=\"index_include\">\n";
+		open (my $fd, $home_text);
+		print <$fd>;
+		close $fd;
+		print "</div>\n";
+	}
+	print "<table class=\"project_list\">\n" .
+	      "<tr>\n";
+	if (!defined($order) || (defined($order) && ($order eq "project"))) {
+		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+		print "<th>Project</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n";
+	}
+	if (defined($order) && ($order eq "descr")) {
+		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+		print "<th>Description</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n";
+	}
+	if (defined($order) && ($order eq "owner")) {
+		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+		print "<th>Owner</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n";
+	}
+	if (defined($order) && ($order eq "age")) {
+		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
+		print "<th>Last Change</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n";
+	}
+	print "<th></th>\n" .
+	      "</tr>\n";
+	my $alternate = 0;
+	foreach my $pr (@projects) {
+		if ($alternate) {
+			print "<tr class=\"dark\">\n";
+		} else {
+			print "<tr class=\"light\">\n";
+		}
+		$alternate ^= 1;
+		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
+		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" .
+		      "<td class=\"link\">" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") .
+		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") .
+		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
+		      "</td>\n" .
+		      "</tr>\n";
+	}
+	print "</table>\n";
+	git_footer_html();
+}
+
 sub git_summary {
 	my $descr = git_read_description($project) || "none";
 	my $head = git_read_head($project);
@@ -1326,20 +1539,6 @@ sub git_summary {
 	git_footer_html();
 }
 
-sub git_print_page_path {
-	my $name = shift;
-	my $type = shift;
-
-	if (!defined $name) {
-		print "<div class=\"page_path\"><b>/</b></div>\n";
-	} elsif ($type =~ "blob") {
-		print "<div class=\"page_path\"><b>" .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n";
-	} else {
-		print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
-	}
-}
-
 sub git_tag {
 	my $head = git_read_head($project);
 	git_header_html();
@@ -1546,84 +1745,6 @@ sub git_heads {
 	git_footer_html();
 }
 
-sub git_get_hash_by_path {
-	my $base = shift;
-	my $path = shift || return undef;
-
-	my $tree = $base;
-
-	open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
-		or die_error(undef, "Open git-ls-tree failed.");
-	my $line = <$fd>;
-	close $fd or return undef;
-
-	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
-	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
-	return $3;
-}
-
-sub mimetype_guess_file {
-	my $filename = shift;
-	my $mimemap = shift;
-	-r $mimemap or return undef;
-
-	my %mimemap;
-	open(MIME, $mimemap) or return undef;
-	while (<MIME>) {
-		my ($mime, $exts) = split(/\t+/);
-		my @exts = split(/\s+/, $exts);
-		foreach my $ext (@exts) {
-			$mimemap{$ext} = $mime;
-		}
-	}
-	close(MIME);
-
-	$filename =~ /\.(.*?)$/;
-	return $mimemap{$1};
-}
-
-sub mimetype_guess {
-	my $filename = shift;
-	my $mime;
-	$filename =~ /\./ or return undef;
-
-	if ($mimetypes_file) {
-		my $file = $mimetypes_file;
-		#$file =~ m#^/# or $file = "$projectroot/$path/$file";
-		$mime = mimetype_guess_file($filename, $file);
-	}
-	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
-	return $mime;
-}
-
-sub git_blob_plain_mimetype {
-	my $fd = shift;
-	my $filename = shift;
-
-	if ($filename) {
-		my $mime = mimetype_guess($filename);
-		$mime and return $mime;
-	}
-
-	# just in case
-	return $default_blob_plain_mimetype unless $fd;
-
-	if (-T $fd) {
-		return 'text/plain' .
-		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
-	} elsif (! $filename) {
-		return 'application/octet-stream';
-	} elsif ($filename =~ m/\.png$/i) {
-		return 'image/png';
-	} elsif ($filename =~ m/\.gif$/i) {
-		return 'image/gif';
-	} elsif ($filename =~ m/\.jpe?g$/i) {
-		return 'image/jpeg';
-	} else {
-		return 'application/octet-stream';
-	}
-}
-
 sub git_blob_plain {
 	if (!defined $hash) {
 		if (defined $file_name) {
@@ -1793,98 +1914,6 @@ #			      " | " . $cgi->a({-href => "$my
 	git_footer_html();
 }
 
-sub git_rss {
-	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
-	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) 
-		or die_error(undef, "Open git-rev-list failed.");
-	my @revlist = map { chomp; $_ } <$fd>;
-	close $fd or die_error(undef, "Reading rev-list failed.");
-	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
-	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
-	print "<channel>\n";
-	print "<title>$project</title>\n".
-	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
-	      "<description>$project log</description>\n".
-	      "<language>en</language>\n";
-
-	for (my $i = 0; $i <= $#revlist; $i++) {
-		my $commit = $revlist[$i];
-		my %co = git_read_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)) {
-			last;
-		}
-		my %cd = date_str($co{'committer_epoch'});
-		open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} 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";
-		my $comment = $co{'comment'};
-		foreach my $line (@$comment) {
-			$line = decode("utf8", $line, Encode::FB_DEFAULT);
-			print "$line<br/>\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 = validate_input(unquote($7));
-			$file = decode("utf8", $file, Encode::FB_DEFAULT);
-			print "$file<br/>\n";
-		}
-		print "]]>\n" .
-		      "</content:encoded>\n" .
-		      "</item>\n";
-	}
-	print "</channel></rss>";
-}
-
-sub git_opml {
-	my @list = git_read_projects();
-
-	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
-	      "<opml version=\"1.0\">\n".
-	      "<head>".
-	      "  <title>$site_name Git OPML Export</title>\n".
-	      "</head>\n".
-	      "<body>\n".
-	      "<outline text=\"git RSS feeds\">\n";
-
-	foreach my $pr (@list) {
-		my %proj = %$pr;
-		my $head = git_read_head($proj{'path'});
-		if (!defined $head) {
-			next;
-		}
-		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
-		my %co = git_read_commit($head);
-		if (!%co) {
-			next;
-		}
-
-		my $path = esc_html(chop_str($proj{'path'}, 25, 5));
-		my $rss  = "$my_url?p=$proj{'path'};a=rss";
-		my $html = "$my_url?p=$proj{'path'};a=summary";
-		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
-	}
-	print "</outline>\n".
-	      "</body>\n".
-	      "</opml>\n";
-}
-
 sub git_log {
 	my $head = git_read_head($project);
 	if (!defined $hash) {
@@ -2556,3 +2585,98 @@ sub git_shortlog {
 
 	git_footer_html();
 }
+
+## ......................................................................
+## feeds (RSS, OPML)
+
+sub git_rss {
+	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project)
+		or die_error(undef, "Open git-rev-list failed.");
+	my @revlist = map { chomp; $_ } <$fd>;
+	close $fd or die_error(undef, "Reading rev-list failed.");
+	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
+	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
+	print "<channel>\n";
+	print "<title>$project</title>\n".
+	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
+	      "<description>$project log</description>\n".
+	      "<language>en</language>\n";
+
+	for (my $i = 0; $i <= $#revlist; $i++) {
+		my $commit = $revlist[$i];
+		my %co = git_read_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)) {
+			last;
+		}
+		my %cd = date_str($co{'committer_epoch'});
+		open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} 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";
+		my $comment = $co{'comment'};
+		foreach my $line (@$comment) {
+			$line = decode("utf8", $line, Encode::FB_DEFAULT);
+			print "$line<br/>\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 = validate_input(unquote($7));
+			$file = decode("utf8", $file, Encode::FB_DEFAULT);
+			print "$file<br/>\n";
+		}
+		print "]]>\n" .
+		      "</content:encoded>\n" .
+		      "</item>\n";
+	}
+	print "</channel></rss>";
+}
+
+sub git_opml {
+	my @list = git_read_projects();
+
+	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
+	      "<opml version=\"1.0\">\n".
+	      "<head>".
+	      "  <title>$site_name Git OPML Export</title>\n".
+	      "</head>\n".
+	      "<body>\n".
+	      "<outline text=\"git RSS feeds\">\n";
+
+	foreach my $pr (@list) {
+		my %proj = %$pr;
+		my $head = git_read_head($proj{'path'});
+		if (!defined $head) {
+			next;
+		}
+		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
+		my %co = git_read_commit($head);
+		if (!%co) {
+			next;
+		}
+
+		my $path = esc_html(chop_str($proj{'path'}, 25, 5));
+		my $rss  = "$my_url?p=$proj{'path'};a=rss";
+		my $html = "$my_url?p=$proj{'path'};a=summary";
+		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
+	}
+	print "</outline>\n".
+	      "</body>\n".
+	      "</opml>\n";
+}
-- 
1.4.1.1
Jakub Narebski· Jul 31, 2006, 19:22 UTC · re: Jakub Narebski · lore

[PATCH 20 (amend)] gitweb: Reordering code and dividing it into categories

Reorder gitweb code around, divide it into sections (categories) and add some comments.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 gitweb/gitweb.cgi | 1616 ++++++++++++++++++++++++++++-------------------------
 1 files changed, 840 insertions(+), 776 deletions(-)
Show changes to gitweb/gitweb.cgi +840 −776
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index cb6af39..c1ee79e 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -160,21 +160,7 @@ if (defined $searchtext) {
 	$searchtext = quotemeta $searchtext;
 }
 
-sub validate_input {
-	my $input = shift;
-
-	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
-		return $input;
-	}
-	if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
-		return undef;
-	}
-	if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
-		return undef;
-	}
-	return $input;
-}
-
+# dispatch
 if (!defined $action || $action eq "summary") {
 	git_summary();
 	exit;
@@ -235,6 +221,24 @@ if (!defined $action || $action eq "summ
 	exit;
 }
 
+## ======================================================================
+## validation, quoting/unquoting and escaping
+
+sub validate_input {
+	my $input = shift;
+
+	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
+		return $input;
+	}
+	if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
+		return undef;
+	}
+	if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
+		return undef;
+	}
+	return $input;
+}
+
 # quote unsafe chars, but keep the slash, even when it's not
 # correct, but quoted slashes look too horrible in bookmarks
 sub esc_param {
@@ -264,6 +268,29 @@ sub unquote {
 	return $str;
 }
 
+## ----------------------------------------------------------------------
+## HTML aware string manipulation
+
+sub chop_str {
+	my $str = shift;
+	my $len = shift;
+	my $add_len = shift || 10;
+
+	# allow only $len chars, but don't cut a word if it would fit in $add_len
+	# if it doesn't fit, cut it if it's still longer than the dots we would add
+	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
+	my $body = $1;
+	my $tail = $2;
+	if (length($tail) > 4) {
+		$tail = " ...";
+		$body =~ s/&[^;]*$//; # remove chopped character entities
+	}
+	return "$body$tail";
+}
+
+## ----------------------------------------------------------------------
+## functions returning short strings
+
 # CSS class for given age value (in seconds)
 sub age_class {
 	my $age = shift;
@@ -277,202 +304,108 @@ sub age_class {
 	}
 }
 
-sub git_header_html {
-	my $status = shift || "200 OK";
-	my $expires = shift;
+# convert age in seconds to "nn units ago" string
+sub age_string {
+	my $age = shift;
+	my $age_str;
 
-	my $title = "$site_name git";
-	if (defined $project) {
-		$title .= " - $project";
-		if (defined $action) {
-			$title .= "/$action";
-			if (defined $file_name) {
-				$title .= " - $file_name";
-				if ($action eq "tree" && $file_name !~ m|/$|) {
-					$title .= "/";
-				}
-			}
-		}
-	}
-	my $content_type;
-	# require explicit support from the UA if we are to send the page as
-	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
-	# we have to do this because MSIE sometimes globs '*/*', pretending to
-	# support xhtml+xml but choking when it gets what it asked for.
-	if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
-		$content_type = 'application/xhtml+xml';
+	if ($age > 60*60*24*365*2) {
+		$age_str = (int $age/60/60/24/365);
+		$age_str .= " years ago";
+	} elsif ($age > 60*60*24*(365/12)*2) {
+		$age_str = int $age/60/60/24/(365/12);
+		$age_str .= " months ago";
+	} elsif ($age > 60*60*24*7*2) {
+		$age_str = int $age/60/60/24/7;
+		$age_str .= " weeks ago";
+	} elsif ($age > 60*60*24*2) {
+		$age_str = int $age/60/60/24;
+		$age_str .= " days ago";
+	} elsif ($age > 60*60*2) {
+		$age_str = int $age/60/60;
+		$age_str .= " hours ago";
+	} elsif ($age > 60*2) {
+		$age_str = int $age/60;
+		$age_str .= " min ago";
+	} elsif ($age > 2) {
+		$age_str = int $age;
+		$age_str .= " sec ago";
 	} else {
-		$content_type = 'text/html';
-	}
-	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
-	print <<EOF;
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
-<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
-<!-- git core binaries version $git_version -->
-<head>
-<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
-<meta name="robots" content="index, nofollow"/>
-<title>$title</title>
-<link rel="stylesheet" type="text/css" href="$stylesheet"/>
-$rss_link
-</head>
-<body>
-EOF
-	print "<div class=\"page_header\">\n" .
-	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
-	      "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
-	      "</a>\n";
-	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
-	if (defined $project) {
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project));
-		if (defined $action) {
-			print " / $action";
-		}
-		print "\n";
-		if (!defined $searchtext) {
-			$searchtext = "";
-		}
-		my $search_hash;
-		if (defined $hash_base) {
-			$search_hash = $hash_base;
-		} elsif (defined $hash) {
-			$search_hash = $hash;
-		} else {
-			$search_hash = "HEAD";
-		}
-		$cgi->param("a", "search");
-		$cgi->param("h", $search_hash);
-		print $cgi->startform(-method => "get", -action => $my_uri) .
-		      "<div class=\"search\">\n" .
-		      $cgi->hidden(-name => "p") . "\n" .
-		      $cgi->hidden(-name => "a") . "\n" .
-		      $cgi->hidden(-name => "h") . "\n" .
-		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
-		      "</div>" .
-		      $cgi->end_form() . "\n";
+		$age_str .= " right now";
 	}
-	print "</div>\n";
+	return $age_str;
 }
 
-sub git_footer_html {
-	print "<div class=\"page_footer\">\n";
-	if (defined $project) {
-		my $descr = git_read_description($project);
-		if (defined $descr) {
-			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
-		}
-		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n";
+# convert file mode in octal to symbolic file mode string
+sub mode_str {
+	my $mode = oct shift;
+
+	if (S_ISDIR($mode & S_IFMT)) {
+		return 'drwxr-xr-x';
+	} elsif (S_ISLNK($mode)) {
+		return 'lrwxrwxrwx';
+	} elsif (S_ISREG($mode)) {
+		# git cares only about the executable bit
+		if ($mode & S_IXUSR) {
+			return '-rwxr-xr-x';
+		} else {
+			return '-rw-r--r--';
+		};
 	} else {
-		print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n";
+		return '----------';
 	}
-	print "</div>\n" .
-	      "</body>\n" .
-	      "</html>";
 }
 
-sub die_error {
-	my $status = shift || "403 Forbidden";
-	my $error = shift || "Malformed query, file missing or permission denied";
+# convert file mode in octal to file type string
+sub file_type {
+	my $mode = oct shift;
 
-	git_header_html($status);
-	print "<div class=\"page_body\">\n" .
-	      "<br/><br/>\n" .
-	      "$status - $error\n" .
-	      "<br/>\n" .
-	      "</div>\n";
-	git_footer_html();
-	exit;
+	if (S_ISDIR($mode & S_IFMT)) {
+		return "directory";
+	} elsif (S_ISLNK($mode)) {
+		return "symlink";
+	} elsif (S_ISREG($mode)) {
+		return "file";
+	} else {
+		return "unknown";
+	}
 }
 
-sub git_page_nav {
-	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
-	$extra = '' if !defined $extra; # pager or formats
+## ----------------------------------------------------------------------
+## functions returning short HTML fragments, or transforming HTML fragments
+## which don't beling to other sections
 
-	my @navs = qw(summary shortlog log commit commitdiff tree);
-	if ($suppress) {
-		@navs = grep { $_ ne $suppress } @navs;
-	}
+# format line of commit message or tag comment
+sub format_log_line_html {
+	my $line = shift;
 
-	my %arg = map { $_, ''} @navs;
-	if (defined $head) {
-		for (qw(commit commitdiff)) {
-			$arg{$_} = ";h=$head";
-		}
-		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
-			for (qw(shortlog log)) {
-				$arg{$_} = ";h=$head";
-			}
+	$line = esc_html($line);
+	$line =~ s/ /&nbsp;/g;
+	if ($line =~ m/([0-9a-fA-F]{40})/) {
+		my $hash_text = $1;
+		if (git_get_type($hash_text) eq "commit") {
+			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
+			$line =~ s/$hash_text/$link/;
 		}
 	}
-	$arg{tree} .= ";h=$treehead" if defined $treehead;
-	$arg{tree} .= ";hb=$treebase" if defined $treebase;
-
-	print "<div class=\"page_nav\">\n" .
-		(join " | ",
-		 map { $_ eq $current
-					 ? $_
-					 : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_")
-				 }
-		 @navs);
-	print "<br/>\n$extra<br/>\n" .
-	      "</div>\n";
-}
-
-sub git_header_div {
-	my ($action, $title, $hash, $hash_base) = @_;
-	my $rest = '';
-
-	$rest .= ";h=$hash" if $hash;
-	$rest .= ";hb=$hash_base" if $hash_base;
-
-	print "<div class=\"header\">\n" .
-	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"),
-	               -class => "title"}, $title ? $title : $action) . "\n" .
-	      "</div>\n";
+	return $line;
 }
 
-sub git_get_paging_nav {
-	my ($action, $hash, $head, $page, $nrevs) = @_;
-	my $paging_nav;
-
-
-	if ($hash ne $head || $page) {
-		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
-	} else {
-		$paging_nav .= "HEAD";
-	}
-
-	if ($page > 0) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)),
-							 -accesskey => "p", -title => "Alt-p"}, "prev");
-	} else {
-		$paging_nav .= " &sdot; prev";
-	}
+# format marker of refs pointing to given object
+sub git_get_referencing {
+	my ($refs, $id) = @_;
 
-	if ($nrevs >= (100 * ($page+1)-1)) {
-		$paging_nav .= " &sdot; " .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)),
-							 -accesskey => "n", -title => "Alt-n"}, "next");
+	if (defined $refs->{$id}) {
+		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
 	} else {
-		$paging_nav .= " &sdot; next";
+		return "";
 	}
-
-	return $paging_nav;
 }
 
-sub git_get_type {
-	my $hash = shift;
-
-	open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return;
-	my $type = <$fd>;
-	close $fd or return;
-	chomp $type;
-	return $type;
-}
+## ----------------------------------------------------------------------
+## git utility subroutines, invoking git commands
 
+# get HEAD ref of given project as hash
 sub git_read_head {
 	my $project = shift;
 	my $oENV = $ENV{'GIT_DIR'};
@@ -491,6 +424,57 @@ sub git_read_head {
 	return $retval;
 }
 
+# get type of given object
+sub git_get_type {
+	my $hash = shift;
+
+	open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return;
+	my $type = <$fd>;
+	close $fd or return;
+	chomp $type;
+	return $type;
+}
+
+sub git_get_project_config {
+	my $key = shift;
+
+	return unless ($key);
+	$key =~ s/^gitweb\.//;
+	return if ($key =~ m/\W/);
+
+	my $val = qx($GIT repo-config --get gitweb.$key);
+	return ($val);
+}
+
+sub git_get_project_config_bool {
+	my $val = git_get_project_config (@_);
+	if ($val and $val =~ m/true|yes|on/) {
+		return (1);
+	}
+	return; # implicit false
+}
+
+# get hash of given path at given ref
+sub git_get_hash_by_path {
+	my $base = shift;
+	my $path = shift || return undef;
+
+	my $tree = $base;
+
+	open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
+		or die_error(undef, "Open git-ls-tree failed.");
+	my $line = <$fd>;
+	close $fd or return undef;
+
+	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
+	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+	return $3;
+}
+
+## ......................................................................
+## git utility functions, directly accessing git repository
+
+# assumes that PATH is not symref
 sub git_read_hash {
 	my $path = shift;
 
@@ -513,6 +497,100 @@ sub git_read_description {
 	return $descr;
 }
 
+sub git_read_projects {
+	my @list;
+
+	if (-d $projects_list) {
+		# search in directory
+		my $dir = $projects_list;
+		opendir my ($dh), $dir or return undef;
+		while (my $dir = readdir($dh)) {
+			if (-e "$projectroot/$dir/HEAD") {
+				my $pr = {
+					path => $dir,
+				};
+				push @list, $pr
+			}
+		}
+		closedir($dh);
+	} elsif (-f $projects_list) {
+		# read from file(url-encoded):
+		# 'git%2Fgit.git Linus+Torvalds'
+		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
+		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
+		open my ($fd), $projects_list or return undef;
+		while (my $line = <$fd>) {
+			chomp $line;
+			my ($path, $owner) = split ' ', $line;
+			$path = unescape($path);
+			$owner = unescape($owner);
+			if (!defined $path) {
+				next;
+			}
+			if (-e "$projectroot/$path/HEAD") {
+				my $pr = {
+					path => $path,
+					owner => decode("utf8", $owner, Encode::FB_DEFAULT),
+				};
+				push @list, $pr
+			}
+		}
+		close $fd;
+	}
+	@list = sort {$a->{'path'} cmp $b->{'path'}} @list;
+	return @list;
+}
+
+sub read_info_ref {
+	my $type = shift || "";
+	my %refs;
+	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11
+	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{}
+	open my $fd, "$projectroot/$project/info/refs" or return;
+	while (my $line = <$fd>) {
+		chomp $line;
+		# attention: for $type == "" it saves only last path part of ref name
+		# e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
+		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
+			if (defined $refs{$1}) {
+				$refs{$1} .= " / $2";
+			} else {
+				$refs{$1} = $2;
+			}
+		}
+	}
+	close $fd or return;
+	return \%refs;
+}
+
+## ----------------------------------------------------------------------
+## parse to hash functions
+
+sub date_str {
+	my $epoch = shift;
+	my $tz = shift || "-0000";
+
+	my %date;
+	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
+	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
+	$date{'hour'} = $hour;
+	$date{'minute'} = $min;
+	$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{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;
+
+	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
+	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
+	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
+	$date{'hour_local'} = $hour;
+	$date{'minute_local'} = $min;
+	$date{'tz_local'} = $tz;
+	return %date;
+}
+
 sub git_read_tag {
 	my $tag_id = shift;
 	my %tag;
@@ -548,37 +626,6 @@ sub git_read_tag {
 	return %tag
 }
 
-sub age_string {
-	my $age = shift;
-	my $age_str;
-
-	if ($age > 60*60*24*365*2) {
-		$age_str = (int $age/60/60/24/365);
-		$age_str .= " years ago";
-	} elsif ($age > 60*60*24*(365/12)*2) {
-		$age_str = int $age/60/60/24/(365/12);
-		$age_str .= " months ago";
-	} elsif ($age > 60*60*24*7*2) {
-		$age_str = int $age/60/60/24/7;
-		$age_str .= " weeks ago";
-	} elsif ($age > 60*60*24*2) {
-		$age_str = int $age/60/60/24;
-		$age_str .= " days ago";
-	} elsif ($age > 60*60*2) {
-		$age_str = int $age/60/60;
-		$age_str .= " hours ago";
-	} elsif ($age > 60*2) {
-		$age_str = int $age/60;
-		$age_str .= " min ago";
-	} elsif ($age > 2) {
-		$age_str = int $age;
-		$age_str .= " sec ago";
-	} else {
-		$age_str .= " right now";
-	}
-	return $age_str;
-}
-
 sub git_read_commit {
 	my $commit_id = shift;
 	my $commit_text = shift;
@@ -673,380 +720,8 @@ sub git_read_commit {
 	return %co;
 }
 
-sub git_diff_print {
-	my $from = shift;
-	my $from_name = shift;
-	my $to = shift;
-	my $to_name = shift;
-	my $format = shift || "html";
-
-	my $from_tmp = "/dev/null";
-	my $to_tmp = "/dev/null";
-	my $pid = $$;
-
-	# create tmp from-file
-	if (defined $from) {
-		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";
-		open my $fd2, "> $from_tmp";
-		open my $fd, "-|", $GIT, "cat-file", "blob", $from;
-		my @file = <$fd>;
-		print $fd2 @file;
-		close $fd2;
-		close $fd;
-	}
-
-	# create tmp to-file
-	if (defined $to) {
-		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";
-		open my $fd2, "> $to_tmp";
-		open my $fd, "-|", $GIT, "cat-file", "blob", $to;
-		my @file = <$fd>;
-		print $fd2 @file;
-		close $fd2;
-		close $fd;
-	}
-
-	open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp";
-	if ($format eq "plain") {
-		undef $/;
-		print <$fd>;
-		$/ = "\n";
-	} else {
-		while (my $line = <$fd>) {
-			chomp $line;
-			my $char = substr($line, 0, 1);
-			my $diff_class = "";
-			if ($char eq '+') {
-				$diff_class = " add";
-			} elsif ($char eq "-") {
-				$diff_class = " rem";
-			} elsif ($char eq "@") {
-				$diff_class = " chunk_header";
-			} elsif ($char eq "\\") {
-				# skip errors
-				next;
-			}
-			while ((my $pos = index($line, "\t")) != -1) {
-				if (my $count = (8 - (($pos-1) % 8))) {
-					my $spaces = ' ' x $count;
-					$line =~ s/\t/$spaces/;
-				}
-			}
-			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
-		}
-	}
-	close $fd;
-
-	if (defined $from) {
-		unlink($from_tmp);
-	}
-	if (defined $to) {
-		unlink($to_tmp);
-	}
-}
-
-sub mode_str {
-	my $mode = oct shift;
-
-	if (S_ISDIR($mode & S_IFMT)) {
-		return 'drwxr-xr-x';
-	} elsif (S_ISLNK($mode)) {
-		return 'lrwxrwxrwx';
-	} elsif (S_ISREG($mode)) {
-		# git cares only about the executable bit
-		if ($mode & S_IXUSR) {
-			return '-rwxr-xr-x';
-		} else {
-			return '-rw-r--r--';
-		};
-	} else {
-		return '----------';
-	}
-}
-
-sub chop_str {
-	my $str = shift;
-	my $len = shift;
-	my $add_len = shift || 10;
-
-	# allow only $len chars, but don't cut a word if it would fit in $add_len
-	# if it doesn't fit, cut it if it's still longer than the dots we would add
-	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
-	my $body = $1;
-	my $tail = $2;
-	if (length($tail) > 4) {
-		$tail = " ...";
-		$body =~ s/&[^;]*$//; # remove chopped character entities
-	}
-	return "$body$tail";
-}
-
-sub file_type {
-	my $mode = oct shift;
-
-	if (S_ISDIR($mode & S_IFMT)) {
-		return "directory";
-	} elsif (S_ISLNK($mode)) {
-		return "symlink";
-	} elsif (S_ISREG($mode)) {
-		return "file";
-	} else {
-		return "unknown";
-	}
-}
-
-sub format_log_line_html {
-	my $line = shift;
-
-	$line = esc_html($line);
-	$line =~ s/ /&nbsp;/g;
-	if ($line =~ m/([0-9a-fA-F]{40})/) {
-		my $hash_text = $1;
-		if (git_get_type($hash_text) eq "commit") {
-			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
-			$line =~ s/$hash_text/$link/;
-		}
-	}
-	return $line;
-}
-
-sub date_str {
-	my $epoch = shift;
-	my $tz = shift || "-0000";
-
-	my %date;
-	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
-	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
-	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
-	$date{'hour'} = $hour;
-	$date{'minute'} = $min;
-	$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{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;
-
-	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
-	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
-	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
-	$date{'hour_local'} = $hour;
-	$date{'minute_local'} = $min;
-	$date{'tz_local'} = $tz;
-	return %date;
-}
-
-# git-logo (cached in browser for one day)
-sub git_logo {
-	binmode STDOUT, ':raw';
-	print $cgi->header(-type => 'image/png', -expires => '+1d');
-	# cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g'
-	print	"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
-		"\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
-		"\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
-		"\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
-		"\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
-		"\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
-		"\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
-		"\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
-		"\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
-		"\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
-		"\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
-		"\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
-		"\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
-}
-
-sub get_file_owner {
-	my $path = shift;
-
-	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
-	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
-	if (!defined $gcos) {
-		return undef;
-	}
-	my $owner = $gcos;
-	$owner =~ s/[,;].*$//;
-	return decode("utf8", $owner, Encode::FB_DEFAULT);
-}
-
-sub git_read_projects {
-	my @list;
-
-	if (-d $projects_list) {
-		# search in directory
-		my $dir = $projects_list;
-		opendir my ($dh), $dir or return undef;
-		while (my $dir = readdir($dh)) {
-			if (-e "$projectroot/$dir/HEAD") {
-				my $pr = {
-					path => $dir,
-				};
-				push @list, $pr
-			}
-		}
-		closedir($dh);
-	} elsif (-f $projects_list) {
-		# read from file(url-encoded):
-		# 'git%2Fgit.git Linus+Torvalds'
-		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
-		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
-		open my ($fd), $projects_list or return undef;
-		while (my $line = <$fd>) {
-			chomp $line;
-			my ($path, $owner) = split ' ', $line;
-			$path = unescape($path);
-			$owner = unescape($owner);
-			if (!defined $path) {
-				next;
-			}
-			if (-e "$projectroot/$path/HEAD") {
-				my $pr = {
-					path => $path,
-					owner => decode("utf8", $owner, Encode::FB_DEFAULT),
-				};
-				push @list, $pr
-			}
-		}
-		close $fd;
-	}
-	@list = sort {$a->{'path'} cmp $b->{'path'}} @list;
-	return @list;
-}
-
-sub git_get_project_config {
-	my $key = shift;
-
-	return unless ($key);
-	$key =~ s/^gitweb\.//;
-	return if ($key =~ m/\W/);
-
-	my $val = qx($GIT repo-config --get gitweb.$key);
-	return ($val);
-}
-
-sub git_get_project_config_bool {
-	my $val = git_get_project_config (@_);
-	if ($val and $val =~ m/true|yes|on/) {
-		return (1);
-	}
-	return; # implicit false
-}
-
-sub git_project_list {
-	my @list = git_read_projects();
-	my @projects;
-	if (!@list) {
-		die_error(undef, "No project found.");
-	}
-	foreach my $pr (@list) {
-		my $head = git_read_head($pr->{'path'});
-		if (!defined $head) {
-			next;
-		}
-		$ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
-		my %co = git_read_commit($head);
-		if (!%co) {
-			next;
-		}
-		$pr->{'commit'} = \%co;
-		if (!defined $pr->{'descr'}) {
-			my $descr = git_read_description($pr->{'path'}) || "";
-			$pr->{'descr'} = chop_str($descr, 25, 5);
-		}
-		if (!defined $pr->{'owner'}) {
-			$pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
-		}
-		push @projects, $pr;
-	}
-	git_header_html();
-	if (-f $home_text) {
-		print "<div class=\"index_include\">\n";
-		open (my $fd, $home_text);
-		print <$fd>;
-		close $fd;
-		print "</div>\n";
-	}
-	print "<table class=\"project_list\">\n" .
-	      "<tr>\n";
-	if (!defined($order) || (defined($order) && ($order eq "project"))) {
-		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
-		print "<th>Project</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n";
-	}
-	if (defined($order) && ($order eq "descr")) {
-		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
-		print "<th>Description</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n";
-	}
-	if (defined($order) && ($order eq "owner")) {
-		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
-		print "<th>Owner</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n";
-	}
-	if (defined($order) && ($order eq "age")) {
-		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
-		print "<th>Last Change</th>\n";
-	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n";
-	}
-	print "<th></th>\n" .
-	      "</tr>\n";
-	my $alternate = 0;
-	foreach my $pr (@projects) {
-		if ($alternate) {
-			print "<tr class=\"dark\">\n";
-		} else {
-			print "<tr class=\"light\">\n";
-		}
-		$alternate ^= 1;
-		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
-		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
-		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" .
-		      "<td class=\"link\">" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
-		      "</td>\n" .
-		      "</tr>\n";
-	}
-	print "</table>\n";
-	git_footer_html();
-}
-
-sub read_info_ref {
-	my $type = shift || "";
-	my %refs;
-	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11
-	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{}
-	open my $fd, "$projectroot/$project/info/refs" or return;
-	while (my $line = <$fd>) {
-		chomp $line;
-		# attention: for $type == "" it saves only last path part of ref name
-		# e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
-		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
-			if (defined $refs{$1}) {
-				$refs{$1} .= " / $2";
-			} else {
-				$refs{$1} = $2;
-			}
-		}
-	}
-	close $fd or return;
-	return \%refs;
-}
-
-sub git_get_referencing {
-	my ($refs, $id) = @_;
-
-	if (defined $refs->{$id}) {
-		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
-	} else {
-		return "";
-	}
-}
+## ......................................................................
+## parse to array of hashes functions
 
 sub git_read_refs {
 	my $ref_dir = shift;
@@ -1115,6 +790,299 @@ sub git_read_refs {
 	return \@reflist;
 }
 
+## ----------------------------------------------------------------------
+## filesystem-related functions
+
+sub get_file_owner {
+	my $path = shift;
+
+	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
+	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
+	if (!defined $gcos) {
+		return undef;
+	}
+	my $owner = $gcos;
+	$owner =~ s/[,;].*$//;
+	return decode("utf8", $owner, Encode::FB_DEFAULT);
+}
+
+## ......................................................................
+## mimetype related functions
+
+sub mimetype_guess_file {
+	my $filename = shift;
+	my $mimemap = shift;
+	-r $mimemap or return undef;
+
+	my %mimemap;
+	open(MIME, $mimemap) or return undef;
+	while (<MIME>) {
+		my ($mime, $exts) = split(/\t+/);
+		my @exts = split(/\s+/, $exts);
+		foreach my $ext (@exts) {
+			$mimemap{$ext} = $mime;
+		}
+	}
+	close(MIME);
+
+	$filename =~ /\.(.*?)$/;
+	return $mimemap{$1};
+}
+
+sub mimetype_guess {
+	my $filename = shift;
+	my $mime;
+	$filename =~ /\./ or return undef;
+
+	if ($mimetypes_file) {
+		my $file = $mimetypes_file;
+		#$file =~ m#^/# or $file = "$projectroot/$path/$file";
+		$mime = mimetype_guess_file($filename, $file);
+	}
+	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
+	return $mime;
+}
+
+sub git_blob_plain_mimetype {
+	my $fd = shift;
+	my $filename = shift;
+
+	if ($filename) {
+		my $mime = mimetype_guess($filename);
+		$mime and return $mime;
+	}
+
+	# just in case
+	return $default_blob_plain_mimetype unless $fd;
+
+	if (-T $fd) {
+		return 'text/plain' .
+		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
+	} elsif (! $filename) {
+		return 'application/octet-stream';
+	} elsif ($filename =~ m/\.png$/i) {
+		return 'image/png';
+	} elsif ($filename =~ m/\.gif$/i) {
+		return 'image/gif';
+	} elsif ($filename =~ m/\.jpe?g$/i) {
+		return 'image/jpeg';
+	} else {
+		return 'application/octet-stream';
+	}
+}
+
+## ======================================================================
+## functions printing HTML: header, footer, error page
+
+sub git_header_html {
+	my $status = shift || "200 OK";
+	my $expires = shift;
+
+	my $title = "$site_name git";
+	if (defined $project) {
+		$title .= " - $project";
+		if (defined $action) {
+			$title .= "/$action";
+			if (defined $file_name) {
+				$title .= " - $file_name";
+				if ($action eq "tree" && $file_name !~ m|/$|) {
+					$title .= "/";
+				}
+			}
+		}
+	}
+	my $content_type;
+	# require explicit support from the UA if we are to send the page as
+	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+	# we have to do this because MSIE sometimes globs '*/*', pretending to
+	# support xhtml+xml but choking when it gets what it asked for.
+	if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
+		$content_type = 'application/xhtml+xml';
+	} else {
+		$content_type = 'text/html';
+	}
+	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
+	print <<EOF;
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
+<!-- git core binaries version $git_version -->
+<head>
+<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
+<meta name="robots" content="index, nofollow"/>
+<title>$title</title>
+<link rel="stylesheet" type="text/css" href="$stylesheet"/>
+$rss_link
+</head>
+<body>
+EOF
+	print "<div class=\"page_header\">\n" .
+	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
+	      "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
+	      "</a>\n";
+	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
+	if (defined $project) {
+		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project));
+		if (defined $action) {
+			print " / $action";
+		}
+		print "\n";
+		if (!defined $searchtext) {
+			$searchtext = "";
+		}
+		my $search_hash;
+		if (defined $hash_base) {
+			$search_hash = $hash_base;
+		} elsif (defined $hash) {
+			$search_hash = $hash;
+		} else {
+			$search_hash = "HEAD";
+		}
+		$cgi->param("a", "search");
+		$cgi->param("h", $search_hash);
+		print $cgi->startform(-method => "get", -action => $my_uri) .
+		      "<div class=\"search\">\n" .
+		      $cgi->hidden(-name => "p") . "\n" .
+		      $cgi->hidden(-name => "a") . "\n" .
+		      $cgi->hidden(-name => "h") . "\n" .
+		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+		      "</div>" .
+		      $cgi->end_form() . "\n";
+	}
+	print "</div>\n";
+}
+
+sub git_footer_html {
+	print "<div class=\"page_footer\">\n";
+	if (defined $project) {
+		my $descr = git_read_description($project);
+		if (defined $descr) {
+			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
+		}
+		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n";
+	} else {
+		print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n";
+	}
+	print "</div>\n" .
+	      "</body>\n" .
+	      "</html>";
+}
+
+sub die_error {
+	my $status = shift || "403 Forbidden";
+	my $error = shift || "Malformed query, file missing or permission denied";
+
+	git_header_html($status);
+	print "<div class=\"page_body\">\n" .
+	      "<br/><br/>\n" .
+	      "$status - $error\n" .
+	      "<br/>\n" .
+	      "</div>\n";
+	git_footer_html();
+	exit;
+}
+
+## ----------------------------------------------------------------------
+## functions printing or outputting HTML: navigation
+
+sub git_page_nav {
+	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_;
+	$extra = '' if !defined $extra; # pager or formats
+
+	my @navs = qw(summary shortlog log commit commitdiff tree);
+	if ($suppress) {
+		@navs = grep { $_ ne $suppress } @navs;
+	}
+
+	my %arg = map { $_, ''} @navs;
+	if (defined $head) {
+		for (qw(commit commitdiff)) {
+			$arg{$_} = ";h=$head";
+		}
+		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
+			for (qw(shortlog log)) {
+				$arg{$_} = ";h=$head";
+			}
+		}
+	}
+	$arg{tree} .= ";h=$treehead" if defined $treehead;
+	$arg{tree} .= ";hb=$treebase" if defined $treebase;
+
+	print "<div class=\"page_nav\">\n" .
+		(join " | ",
+		 map { $_ eq $current
+					 ? $_
+					 : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_")
+				 }
+		 @navs);
+	print "<br/>\n$extra<br/>\n" .
+	      "</div>\n";
+}
+
+sub git_get_paging_nav {
+	my ($action, $hash, $head, $page, $nrevs) = @_;
+	my $paging_nav;
+
+
+	if ($hash ne $head || $page) {
+		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
+	} else {
+		$paging_nav .= "HEAD";
+	}
+
+	if ($page > 0) {
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)),
+							 -accesskey => "p", -title => "Alt-p"}, "prev");
+	} else {
+		$paging_nav .= " &sdot; prev";
+	}
+
+	if ($nrevs >= (100 * ($page+1)-1)) {
+		$paging_nav .= " &sdot; " .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)),
+							 -accesskey => "n", -title => "Alt-n"}, "next");
+	} else {
+		$paging_nav .= " &sdot; next";
+	}
+
+	return $paging_nav;
+}
+
+## ......................................................................
+## functions printing or outputting HTML: div
+
+sub git_header_div {
+	my ($action, $title, $hash, $hash_base) = @_;
+	my $rest = '';
+
+	$rest .= ";h=$hash" if $hash;
+	$rest .= ";hb=$hash_base" if $hash_base;
+
+	print "<div class=\"header\">\n" .
+	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"),
+	               -class => "title"}, $title ? $title : $action) . "\n" .
+	      "</div>\n";
+}
+
+sub git_print_page_path {
+	my $name = shift;
+	my $type = shift;
+
+	if (!defined $name) {
+		print "<div class=\"page_path\"><b>/</b></div>\n";
+	} elsif ($type =~ "blob") {
+		print "<div class=\"page_path\"><b>" .
+			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n";
+	} else {
+		print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
+	}
+}
+
+## ......................................................................
+## functions printing large fragments of HTML
+
 sub git_shortlog_body {
 	# uses global variable $project
 	my ($revlist, $from, $to, $refs, $extra) = @_;
@@ -1265,6 +1233,191 @@ sub git_heads_body {
 	print "</table>\n";
 }
 
+## ----------------------------------------------------------------------
+## functions printing large fragments, format as one of arguments
+
+sub git_diff_print {
+	my $from = shift;
+	my $from_name = shift;
+	my $to = shift;
+	my $to_name = shift;
+	my $format = shift || "html";
+
+	my $from_tmp = "/dev/null";
+	my $to_tmp = "/dev/null";
+	my $pid = $$;
+
+	# create tmp from-file
+	if (defined $from) {
+		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";
+		open my $fd2, "> $from_tmp";
+		open my $fd, "-|", $GIT, "cat-file", "blob", $from;
+		my @file = <$fd>;
+		print $fd2 @file;
+		close $fd2;
+		close $fd;
+	}
+
+	# create tmp to-file
+	if (defined $to) {
+		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";
+		open my $fd2, "> $to_tmp";
+		open my $fd, "-|", $GIT, "cat-file", "blob", $to;
+		my @file = <$fd>;
+		print $fd2 @file;
+		close $fd2;
+		close $fd;
+	}
+
+	open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp";
+	if ($format eq "plain") {
+		undef $/;
+		print <$fd>;
+		$/ = "\n";
+	} else {
+		while (my $line = <$fd>) {
+			chomp $line;
+			my $char = substr($line, 0, 1);
+			my $diff_class = "";
+			if ($char eq '+') {
+				$diff_class = " add";
+			} elsif ($char eq "-") {
+				$diff_class = " rem";
+			} elsif ($char eq "@") {
+				$diff_class = " chunk_header";
+			} elsif ($char eq "\\") {
+				# skip errors
+				next;
+			}
+			while ((my $pos = index($line, "\t")) != -1) {
+				if (my $count = (8 - (($pos-1) % 8))) {
+					my $spaces = ' ' x $count;
+					$line =~ s/\t/$spaces/;
+				}
+			}
+			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
+		}
+	}
+	close $fd;
+
+	if (defined $from) {
+		unlink($from_tmp);
+	}
+	if (defined $to) {
+		unlink($to_tmp);
+	}
+}
+
+
+## ======================================================================
+## ======================================================================
+## actions
+
+# git-logo (cached in browser for one day)
+sub git_logo {
+	binmode STDOUT, ':raw';
+	print $cgi->header(-type => 'image/png', -expires => '+1d');
+	# cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g'
+	print	"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
+		"\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
+		"\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
+		"\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
+		"\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
+		"\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
+		"\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
+		"\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
+		"\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
+		"\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
+		"\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
+		"\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
+		"\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
+}
+
+sub git_project_list {
+	my @list = git_read_projects();
+	my @projects;
+	if (!@list) {
+		die_error(undef, "No project found.");
+	}
+	foreach my $pr (@list) {
+		my $head = git_read_head($pr->{'path'});
+		if (!defined $head) {
+			next;
+		}
+		$ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
+		my %co = git_read_commit($head);
+		if (!%co) {
+			next;
+		}
+		$pr->{'commit'} = \%co;
+		if (!defined $pr->{'descr'}) {
+			my $descr = git_read_description($pr->{'path'}) || "";
+			$pr->{'descr'} = chop_str($descr, 25, 5);
+		}
+		if (!defined $pr->{'owner'}) {
+			$pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+		}
+		push @projects, $pr;
+	}
+	git_header_html();
+	if (-f $home_text) {
+		print "<div class=\"index_include\">\n";
+		open (my $fd, $home_text);
+		print <$fd>;
+		close $fd;
+		print "</div>\n";
+	}
+	print "<table class=\"project_list\">\n" .
+	      "<tr>\n";
+	if (!defined($order) || (defined($order) && ($order eq "project"))) {
+		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
+		print "<th>Project</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n";
+	}
+	if (defined($order) && ($order eq "descr")) {
+		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
+		print "<th>Description</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n";
+	}
+	if (defined($order) && ($order eq "owner")) {
+		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
+		print "<th>Owner</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n";
+	}
+	if (defined($order) && ($order eq "age")) {
+		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
+		print "<th>Last Change</th>\n";
+	} else {
+		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n";
+	}
+	print "<th></th>\n" .
+	      "</tr>\n";
+	my $alternate = 0;
+	foreach my $pr (@projects) {
+		if ($alternate) {
+			print "<tr class=\"dark\">\n";
+		} else {
+			print "<tr class=\"light\">\n";
+		}
+		$alternate ^= 1;
+		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
+		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" .
+		      "<td class=\"link\">" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") .
+		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") .
+		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
+		      "</td>\n" .
+		      "</tr>\n";
+	}
+	print "</table>\n";
+	git_footer_html();
+}
+
 sub git_summary {
 	my $descr = git_read_description($project) || "none";
 	my $head = git_read_head($project);
@@ -1326,20 +1479,6 @@ sub git_summary {
 	git_footer_html();
 }
 
-sub git_print_page_path {
-	my $name = shift;
-	my $type = shift;
-
-	if (!defined $name) {
-		print "<div class=\"page_path\"><b>/</b></div>\n";
-	} elsif ($type =~ "blob") {
-		print "<div class=\"page_path\"><b>" .
-			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n";
-	} else {
-		print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
-	}
-}
-
 sub git_tag {
 	my $head = git_read_head($project);
 	git_header_html();
@@ -1546,84 +1685,6 @@ sub git_heads {
 	git_footer_html();
 }
 
-sub git_get_hash_by_path {
-	my $base = shift;
-	my $path = shift || return undef;
-
-	my $tree = $base;
-
-	open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
-		or die_error(undef, "Open git-ls-tree failed.");
-	my $line = <$fd>;
-	close $fd or return undef;
-
-	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
-	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
-	return $3;
-}
-
-sub mimetype_guess_file {
-	my $filename = shift;
-	my $mimemap = shift;
-	-r $mimemap or return undef;
-
-	my %mimemap;
-	open(MIME, $mimemap) or return undef;
-	while (<MIME>) {
-		my ($mime, $exts) = split(/\t+/);
-		my @exts = split(/\s+/, $exts);
-		foreach my $ext (@exts) {
-			$mimemap{$ext} = $mime;
-		}
-	}
-	close(MIME);
-
-	$filename =~ /\.(.*?)$/;
-	return $mimemap{$1};
-}
-
-sub mimetype_guess {
-	my $filename = shift;
-	my $mime;
-	$filename =~ /\./ or return undef;
-
-	if ($mimetypes_file) {
-		my $file = $mimetypes_file;
-		#$file =~ m#^/# or $file = "$projectroot/$path/$file";
-		$mime = mimetype_guess_file($filename, $file);
-	}
-	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
-	return $mime;
-}
-
-sub git_blob_plain_mimetype {
-	my $fd = shift;
-	my $filename = shift;
-
-	if ($filename) {
-		my $mime = mimetype_guess($filename);
-		$mime and return $mime;
-	}
-
-	# just in case
-	return $default_blob_plain_mimetype unless $fd;
-
-	if (-T $fd) {
-		return 'text/plain' .
-		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
-	} elsif (! $filename) {
-		return 'application/octet-stream';
-	} elsif ($filename =~ m/\.png$/i) {
-		return 'image/png';
-	} elsif ($filename =~ m/\.gif$/i) {
-		return 'image/gif';
-	} elsif ($filename =~ m/\.jpe?g$/i) {
-		return 'image/jpeg';
-	} else {
-		return 'application/octet-stream';
-	}
-}
-
 sub git_blob_plain {
 	if (!defined $hash) {
 		if (defined $file_name) {
@@ -1793,98 +1854,6 @@ #			      " | " . $cgi->a({-href => "$my
 	git_footer_html();
 }
 
-sub git_rss {
-	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
-	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) 
-		or die_error(undef, "Open git-rev-list failed.");
-	my @revlist = map { chomp; $_ } <$fd>;
-	close $fd or die_error(undef, "Reading rev-list failed.");
-	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
-	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
-	print "<channel>\n";
-	print "<title>$project</title>\n".
-	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
-	      "<description>$project log</description>\n".
-	      "<language>en</language>\n";
-
-	for (my $i = 0; $i <= $#revlist; $i++) {
-		my $commit = $revlist[$i];
-		my %co = git_read_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)) {
-			last;
-		}
-		my %cd = date_str($co{'committer_epoch'});
-		open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} 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";
-		my $comment = $co{'comment'};
-		foreach my $line (@$comment) {
-			$line = decode("utf8", $line, Encode::FB_DEFAULT);
-			print "$line<br/>\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 = validate_input(unquote($7));
-			$file = decode("utf8", $file, Encode::FB_DEFAULT);
-			print "$file<br/>\n";
-		}
-		print "]]>\n" .
-		      "</content:encoded>\n" .
-		      "</item>\n";
-	}
-	print "</channel></rss>";
-}
-
-sub git_opml {
-	my @list = git_read_projects();
-
-	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
-	      "<opml version=\"1.0\">\n".
-	      "<head>".
-	      "  <title>$site_name Git OPML Export</title>\n".
-	      "</head>\n".
-	      "<body>\n".
-	      "<outline text=\"git RSS feeds\">\n";
-
-	foreach my $pr (@list) {
-		my %proj = %$pr;
-		my $head = git_read_head($proj{'path'});
-		if (!defined $head) {
-			next;
-		}
-		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
-		my %co = git_read_commit($head);
-		if (!%co) {
-			next;
-		}
-
-		my $path = esc_html(chop_str($proj{'path'}, 25, 5));
-		my $rss  = "$my_url?p=$proj{'path'};a=rss";
-		my $html = "$my_url?p=$proj{'path'};a=summary";
-		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
-	}
-	print "</outline>\n".
-	      "</body>\n".
-	      "</opml>\n";
-}
-
 sub git_log {
 	my $head = git_read_head($project);
 	if (!defined $hash) {
@@ -2556,3 +2525,98 @@ sub git_shortlog {
 
 	git_footer_html();
 }
+
+## ......................................................................
+## feeds (RSS, OPML)
+
+sub git_rss {
+	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project)
+		or die_error(undef, "Open git-rev-list failed.");
+	my @revlist = map { chomp; $_ } <$fd>;
+	close $fd or die_error(undef, "Reading rev-list failed.");
+	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
+	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
+	print "<channel>\n";
+	print "<title>$project</title>\n".
+	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
+	      "<description>$project log</description>\n".
+	      "<language>en</language>\n";
+
+	for (my $i = 0; $i <= $#revlist; $i++) {
+		my $commit = $revlist[$i];
+		my %co = git_read_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)) {
+			last;
+		}
+		my %cd = date_str($co{'committer_epoch'});
+		open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} 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";
+		my $comment = $co{'comment'};
+		foreach my $line (@$comment) {
+			$line = decode("utf8", $line, Encode::FB_DEFAULT);
+			print "$line<br/>\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 = validate_input(unquote($7));
+			$file = decode("utf8", $file, Encode::FB_DEFAULT);
+			print "$file<br/>\n";
+		}
+		print "]]>\n" .
+		      "</content:encoded>\n" .
+		      "</item>\n";
+	}
+	print "</channel></rss>";
+}
+
+sub git_opml {
+	my @list = git_read_projects();
+
+	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
+	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
+	      "<opml version=\"1.0\">\n".
+	      "<head>".
+	      "  <title>$site_name Git OPML Export</title>\n".
+	      "</head>\n".
+	      "<body>\n".
+	      "<outline text=\"git RSS feeds\">\n";
+
+	foreach my $pr (@list) {
+		my %proj = %$pr;
+		my $head = git_read_head($proj{'path'});
+		if (!defined $head) {
+			next;
+		}
+		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
+		my %co = git_read_commit($head);
+		if (!%co) {
+			next;
+		}
+
+		my $path = esc_html(chop_str($proj{'path'}, 25, 5));
+		my $rss  = "$my_url?p=$proj{'path'};a=rss";
+		my $html = "$my_url?p=$proj{'path'};a=summary";
+		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
+	}
+	print "</outline>\n".
+	      "</body>\n".
+	      "</opml>\n";
+}
-- 
1.4.1.1
Matthias Lederhofer· Jul 31, 2006, 21:46 UTC · re: Jakub Narebski · lore

[PATCH] gitweb: use a hash to lookup the sub for an action

Signed-off-by: Matthias Lederhofer <matled@gmx.net>
---
 gitweb/gitweb.cgi |   81 ++++++++++++++++-------------------------------------
 1 files changed, 25 insertions(+), 56 deletions(-)
Show changes to gitweb/gitweb.cgi +25 −56
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index c1ee79e..75390c8 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -161,65 +161,34 @@ if (defined $searchtext) {
 }
 
 # dispatch
-if (!defined $action || $action eq "summary") {
-	git_summary();
-	exit;
-} elsif ($action eq "heads") {
-	git_heads();
-	exit;
-} elsif ($action eq "tags") {
-	git_tags();
-	exit;
-} elsif ($action eq "blob") {
-	git_blob();
-	exit;
-} elsif ($action eq "blob_plain") {
-	git_blob_plain();
-	exit;
-} elsif ($action eq "tree") {
-	git_tree();
-	exit;
-} elsif ($action eq "rss") {
-	git_rss();
-	exit;
-} elsif ($action eq "commit") {
-	git_commit();
-	exit;
-} elsif ($action eq "log") {
-	git_log();
-	exit;
-} elsif ($action eq "blobdiff") {
-	git_blobdiff();
-	exit;
-} elsif ($action eq "blobdiff_plain") {
-	git_blobdiff_plain();
-	exit;
-} elsif ($action eq "commitdiff") {
-	git_commitdiff();
-	exit;
-} elsif ($action eq "commitdiff_plain") {
-	git_commitdiff_plain();
-	exit;
-} elsif ($action eq "history") {
-	git_history();
-	exit;
-} elsif ($action eq "search") {
-	git_search();
-	exit;
-} elsif ($action eq "shortlog") {
-	git_shortlog();
-	exit;
-} elsif ($action eq "tag") {
-	git_tag();
-	exit;
-} elsif ($action eq "blame") {
-	git_blame2();
-	exit;
-} else {
+my %actions = (
+	"blame" => \&git_blame2,
+	"blobdiff" => \&git_blobdiff,
+	"blobdiff_plain" => \&git_blobdiff_plain,
+	"blob" => \&git_blob,
+	"blob_plain" => \&git_blob_plain,
+	"commitdiff" => \&git_commitdiff,
+	"commitdiff_plain" => \&git_commitdiff_plain,
+	"commit" => \&git_commit,
+	"heads" => \&git_heads,
+	"history" => \&git_history,
+	"log" => \&git_log,
+	"rss" => \&git_rss,
+	"search" => \&git_search,
+	"shortlog" => \&git_shortlog,
+	"summary" => \&git_summary,
+	"tag" => \&git_tag,
+	"tags" => \&git_tags,
+	"tree" => \&git_tree,
+);
+
+$action = 'summary' if (!defined($action));
+if (!defined($actions{$action})) {
 	undef $action;
 	die_error(undef, "Unknown action.");
-	exit;
 }
+$actions{$action}->();
+exit;
 
 ## ======================================================================
 ## validation, quoting/unquoting and escaping
-- 
1.4.2.rc2.ge0bed
Jakub Narebski· Jul 31, 2006, 22:55 UTC · re: Matthias Lederhofer · lore

Re: [PATCH 21] gitweb: use a hash to lookup the sub for an action

Matthias Lederhofer wrote:
> Signed-off-by: Matthias Lederhofer <matled@gmx.net>
Acked-by: Jakub Narebski <jnareb@gmail.com>
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Luben Tuikov· Aug 1, 2006, 02:50 UTC · re: Matthias Lederhofer · lore

Re: [PATCH] gitweb: use a hash to lookup the sub for an action

--- Matthias Lederhofer <matled@gmx.net> wrote:
> Signed-off-by: Matthias Lederhofer <matled@gmx.net>
That's a great patch.
   Luben
Show 107 quoted lines
> ---
>  gitweb/gitweb.cgi |   81 ++++++++++++++++-------------------------------------
>  1 files changed, 25 insertions(+), 56 deletions(-)
> 
> diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
> index c1ee79e..75390c8 100755
> --- a/gitweb/gitweb.cgi
> +++ b/gitweb/gitweb.cgi
> @@ -161,65 +161,34 @@ if (defined $searchtext) {
>  }
>  
>  # dispatch
> -if (!defined $action || $action eq "summary") {
> -	git_summary();
> -	exit;
> -} elsif ($action eq "heads") {
> -	git_heads();
> -	exit;
> -} elsif ($action eq "tags") {
> -	git_tags();
> -	exit;
> -} elsif ($action eq "blob") {
> -	git_blob();
> -	exit;
> -} elsif ($action eq "blob_plain") {
> -	git_blob_plain();
> -	exit;
> -} elsif ($action eq "tree") {
> -	git_tree();
> -	exit;
> -} elsif ($action eq "rss") {
> -	git_rss();
> -	exit;
> -} elsif ($action eq "commit") {
> -	git_commit();
> -	exit;
> -} elsif ($action eq "log") {
> -	git_log();
> -	exit;
> -} elsif ($action eq "blobdiff") {
> -	git_blobdiff();
> -	exit;
> -} elsif ($action eq "blobdiff_plain") {
> -	git_blobdiff_plain();
> -	exit;
> -} elsif ($action eq "commitdiff") {
> -	git_commitdiff();
> -	exit;
> -} elsif ($action eq "commitdiff_plain") {
> -	git_commitdiff_plain();
> -	exit;
> -} elsif ($action eq "history") {
> -	git_history();
> -	exit;
> -} elsif ($action eq "search") {
> -	git_search();
> -	exit;
> -} elsif ($action eq "shortlog") {
> -	git_shortlog();
> -	exit;
> -} elsif ($action eq "tag") {
> -	git_tag();
> -	exit;
> -} elsif ($action eq "blame") {
> -	git_blame2();
> -	exit;
> -} else {
> +my %actions = (
> +	"blame" => \&git_blame2,
> +	"blobdiff" => \&git_blobdiff,
> +	"blobdiff_plain" => \&git_blobdiff_plain,
> +	"blob" => \&git_blob,
> +	"blob_plain" => \&git_blob_plain,
> +	"commitdiff" => \&git_commitdiff,
> +	"commitdiff_plain" => \&git_commitdiff_plain,
> +	"commit" => \&git_commit,
> +	"heads" => \&git_heads,
> +	"history" => \&git_history,
> +	"log" => \&git_log,
> +	"rss" => \&git_rss,
> +	"search" => \&git_search,
> +	"shortlog" => \&git_shortlog,
> +	"summary" => \&git_summary,
> +	"tag" => \&git_tag,
> +	"tags" => \&git_tags,
> +	"tree" => \&git_tree,
> +);
> +
> +$action = 'summary' if (!defined($action));
> +if (!defined($actions{$action})) {
>  	undef $action;
>  	die_error(undef, "Unknown action.");
> -	exit;
>  }
> +$actions{$action}->();
> +exit;
>  
>  ## ======================================================================
>  ## validation, quoting/unquoting and escaping
> -- 
> 1.4.2.rc2.ge0bed
> 
> -
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
Jakub Narebski· Aug 1, 2006, 00:59 UTC · re: Jakub Narebski · lore

[PATCH 22]

>From d69d3357f134bb5548f18fafff7664499adc1fcd Mon Sep 17 00:00:00 2001
From: Jakub Narebski <jnareb@gmail.com>
Date: Tue, 1 Aug 2006 02:56:51 +0200
Subject: [PATCH] gitweb: Refactoring git_project_list

Slightly reworking git_project_list, including moving setting $order, as it is used only in this action. Mostly reindent.

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
After Matthias Lederhofer <matled@gmx.net> patch,
"[PATCH] gitweb: use a hash to lookup the sub for an action"
 gitweb/gitweb.cgi |   57 +++++++++++++++++++++++++++++++++--------------------
 1 files changed, 35 insertions(+), 22 deletions(-)
Show changes to gitweb/gitweb.cgi +35 −22
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 75390c8..27c36ef 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -80,14 +80,6 @@ if (defined $action) {
 	}
 }
 
-our $order = $cgi->param('o');
-if (defined $order) {
-	if ($order =~ m/[^0-9a-zA-Z_]/) {
-		undef $order;
-		die_error(undef, "Invalid order parameter.");
-	}
-}
-
 our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
 if (defined $project) {
 	$project =~ s|^/||; $project =~ s|/$||;
@@ -1303,10 +1295,15 @@ sub git_logo {
 }
 
 sub git_project_list {
+	my $order = $cgi->param('o');
+	if (defined $order && $order !~ m/project|descr|owner|age/) {
+		die_error(undef, "Invalid order parameter '$order'.");
+	}
+
 	my @list = git_read_projects();
 	my @projects;
 	if (!@list) {
-		die_error(undef, "No project found.");
+		die_error(undef, "No projects found.");
 	}
 	foreach my $pr (@list) {
 		my $head = git_read_head($pr->{'path'});
@@ -1328,6 +1325,7 @@ sub git_project_list {
 		}
 		push @projects, $pr;
 	}
+
 	git_header_html();
 	if (-f $home_text) {
 		print "<div class=\"index_include\">\n";
@@ -1338,29 +1336,42 @@ sub git_project_list {
 	}
 	print "<table class=\"project_list\">\n" .
 	      "<tr>\n";
-	if (!defined($order) || (defined($order) && ($order eq "project"))) {
+	$order ||= "project";
+	if ($order eq "project") {
 		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
 		print "<th>Project</th>\n";
 	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n";
+		print "<th>" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("o=project"),
+		               -class => "header"}, "Project") .
+		      "</th>\n";
 	}
-	if (defined($order) && ($order eq "descr")) {
+	if ($order eq "descr") {
 		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
 		print "<th>Description</th>\n";
 	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n";
+		print "<th>" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("o=descr"),
+		               -class => "header"}, "Description") .
+		      "</th>\n";
 	}
-	if (defined($order) && ($order eq "owner")) {
+	if ($order eq "owner") {
 		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
 		print "<th>Owner</th>\n";
 	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n";
+		print "<th>" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("o=owner"),
+		               -class => "header"}, "Owner") .
+		      "</th>\n";
 	}
-	if (defined($order) && ($order eq "age")) {
+	if ($order eq "age") {
 		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
 		print "<th>Last Change</th>\n";
 	} else {
-		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n";
+		print "<th>" .
+		      $cgi->a({-href => "$my_uri?" . esc_param("o=age"),
+		               -class => "header"}, "Last Change") .
+		      "</th>\n";
 	}
 	print "<th></th>\n" .
 	      "</tr>\n";
@@ -1372,14 +1383,16 @@ sub git_project_list {
 			print "<tr class=\"light\">\n";
 		}
 		$alternate ^= 1;
-		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
+		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"),
+		                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
 		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
 		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
-		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" .
+		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
+		      $pr->{'commit'}{'age_string'} . "</td>\n" .
 		      "<td class=\"link\">" .
-		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") .
-		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary")   . " | " .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " .
+		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
 		      "</td>\n" .
 		      "</tr>\n";
 	}
-- 
1.4.1.1
Junio C Hamano· Aug 1, 2006, 02:12 UTC · re: Jakub Narebski · lore

Perhaps an obvious cut and paste error.

Show changes to gitweb/gitweb.cgi +1 −1
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 4e79390..a4a46d9 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -1441,7 +1434,7 @@ sub git_summary {
 	my $headlist = git_read_refs("refs/heads");
 	if (defined @$headlist) {
 		git_header_div('heads');
-		git_heads_body($taglist, $head, 0, 15,
+		git_heads_body($headlist, $head, 0, 15,
 		               $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
 	}
 
Jakub Narebski· Aug 1, 2006, 07:23 UTC · re: Junio C Hamano · lore

Re: Perhaps an obvious cut and paste error.

Junio C Hamano wrote:
Show 14 quoted lines
> diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
> index 4e79390..a4a46d9 100755
> --- a/gitweb/gitweb.cgi
> +++ b/gitweb/gitweb.cgi
> @@ -1441,7 +1434,7 @@ sub git_summary {
>       my $headlist = git_read_refs("refs/heads");
>       if (defined @$headlist) {
>               git_header_div('heads');
> -             git_heads_body($taglist, $head, 0, 15,
> +             git_heads_body($headlist, $head, 0, 15,
>                              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
>       }
>  
> 
Yes, of course. ACK.
-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Junio C Hamano· Aug 1, 2006, 04:24 UTC · re: Jakub Narebski · lore

A few more fixups to gitweb

I've applied most of the recent gitweb changes from the list (except the one that processes gitweb.perl into gitweb.cgi, not because I am opposed to it, but because merging it early would make things a bit more inconvenient while many patches are in flight on the list) and fixed up a few things I noticed. I'll be pushing out the results on "next" branch shortly, so if people want to hack on gitweb further please base your changes on this version (I am CC'ing this message to people who recently worked on, or expressed interest in working on gitweb).

One thing to note. Please make sure that you do not see anything in Apache error log after you make your changes. I do not remember the details but kernel.org folks were very unhappy earlier when gitweb spewed stuff into the error log, and if I recall correctly things that output to the error stream were not friendly to the http-server cache for some reason.

Jakub Narebski· Aug 1, 2006, 07:36 UTC · re: Junio C Hamano · lore

Re: A few more fixups to gitweb

Junio C Hamano wrote:
Show 6 quoted lines
> One thing to note.  Please make sure that you do not see
> anything in Apache error log after you make your changes.  I do
> not remember the details but kernel.org folks were very unhappy
> earlier when gitweb spewed stuff into the error log, and if I
> recall correctly things that output to the error stream were not
> friendly to the http-server cache for some reason.

By the way, I wonder why git when cloning/fetching via http protocol uses e.g. "git/1.4.2.rc2.ge0bed" as User-Agent: string when fetching objects and packs, and e.g. "curl/7.15.4 (i486-pc-linux-gnu) libcurl/7.15.4 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5" as User-Agent: when fetching refs (heads and tags) and info/refs.

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Junio C Hamano· Aug 1, 2006, 08:04 UTC · re: Jakub Narebski · lore

Re: A few more fixups to gitweb

Jakub Narebski <jnareb@gmail.com> writes:
Show 5 quoted lines
> By the way, I wonder why git when cloning/fetching via http protocol
> uses e.g. "git/1.4.2.rc2.ge0bed" as User-Agent: string when fetching objects
> and packs, and e.g. "curl/7.15.4 (i486-pc-linux-gnu) libcurl/7.15.4
> OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5" as User-Agent: when fetching refs
> (heads and tags) and info/refs.
"Why?", meaning if we deliberately do so for some good reason?
There isn't.

git-http-fetch uses its own User-Agent string, but the shell script wrappers that use curl executable do not bother setting customized User-Agent string; that is why.

Jakub Narebski· Aug 1, 2006, 09:34 UTC · re: Junio C Hamano · lore

[PATCH 1/2] Use tabs for indent in shell scripts

Consistently use tabs for indenting in some shell scripts. Needs checking for consistency (command1 && command2 && command3 split into lines).

Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
 git-clone.sh     |   94 +++++-----
 git-fetch.sh     |  493 +++++++++++++++++++++++++++---------------------------
 git-ls-remote.sh |   50 +++--
 3 files changed, 319 insertions(+), 318 deletions(-)
Show changes to 3 files +319 −318

git-clone.sh, git-fetch.sh, git-ls-remote.sh

diff --git a/git-clone.sh b/git-clone.sh
index a92b22a..adb752d 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -18,7 +18,7 @@ get_repo_base() {
 }
 
 if [ -n "$GIT_SSL_NO_VERIFY" ]; then
-    curl_extra_args="-k"
+	curl_extra_args="-k"
 fi
 
 http_fetch () {
@@ -43,7 +43,7 @@ Perhaps git-update-server-info needs to 
 		*^*)	continue;;
 		esac
 		if test -n "$use_separate_remote" &&
-		   branch_name=`expr "z$name" : 'zheads/\(.*\)'`
+			branch_name=`expr "z$name" : 'zheads/\(.*\)'`
 		then
 			tname="remotes/$origin/$branch_name"
 		else
@@ -119,13 +119,13 @@ while
 	*,--na|*,--nak|*,--nake|*,--naked|\
 	*,-b|*,--b|*,--ba|*,--bar|*,--bare) bare=yes ;;
 	*,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;;
-        *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) 
-          local_shared=yes; use_local=yes ;;
+	*,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared)
+		local_shared=yes; use_local=yes ;;
 	1,--template) usage ;;
 	*,--template)
 		shift; template="--template=$1" ;;
 	*,--template=*)
-	  template="$1" ;;
+		template="$1" ;;
 	*,-q|*,--quiet) quiet=-q ;;
 	*,--use-separate-remote)
 		use_separate_remote=t ;;
@@ -137,18 +137,18 @@ while
 	*,-o|*,--or|*,--ori|*,--orig|*,--origi|*,--origin)
 		case "$2" in
 		'')
-		    usage ;;
+			usage ;;
 		*/*)
-		    echo >&2 "'$2' is not suitable for an origin name"
-		    exit 1
+			echo >&2 "'$2' is not suitable for an origin name"
+			exit 1
 		esac
 		git-check-ref-format "heads/$2" || {
-		    echo >&2 "'$2' is not suitable for a branch name"
-		    exit 1
+			echo >&2 "'$2' is not suitable for a branch name"
+			exit 1
 		}
 		test -z "$origin_override" || {
-		    echo >&2 "Do not give more than one --origin options."
-		    exit 1
+			echo >&2 "Do not give more than one --origin options."
+			exit 1
 		}
 		origin_override=yes
 		origin="$2"; shift
@@ -167,8 +167,8 @@ done
 repo="$1"
 if test -z "$repo"
 then
-    echo >&2 'you must specify a repository to clone.'
-    exit 1
+	echo >&2 'you must specify a repository to clone.'
+	exit 1
 fi
 
 # --bare implies --no-checkout
@@ -225,9 +225,9 @@ then
 		echo "$reference/objects" >"$GIT_DIR/objects/info/alternates"
 		(cd "$reference" && tar cf - refs) |
 		(cd "$GIT_DIR/refs" &&
-		 mkdir reference-tmp &&
-		 cd reference-tmp &&
-		 tar xf -)
+			mkdir reference-tmp &&
+			cd reference-tmp &&
+			tar xf -)
 	else
 		echo >&2 "$reference: not a local directory." && usage
 	fi
@@ -245,26 +245,26 @@ yes,yes)
 
 	case "$local_shared" in
 	no)
-	    # See if we can hardlink and drop "l" if not.
-	    sample_file=$(cd "$repo" && \
-			  find objects -type f -print | sed -e 1q)
+		# See if we can hardlink and drop "l" if not.
+		sample_file=$(cd "$repo" && \
+			find objects -type f -print | sed -e 1q)
 
-	    # objects directory should not be empty since we are cloning!
-	    test -f "$repo/$sample_file" || exit
+		# objects directory should not be empty since we are cloning!
+		test -f "$repo/$sample_file" || exit
 
-	    l=
-	    if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
-	    then
-		    l=l
-	    fi &&
-	    rm -f "$GIT_DIR/objects/sample" &&
-	    cd "$repo" &&
-	    find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
-	    ;;
+		l=
+		if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
+		then
+			l=l
+		fi &&
+		rm -f "$GIT_DIR/objects/sample" &&
+		cd "$repo" &&
+		find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
+		;;
 	yes)
-	    mkdir -p "$GIT_DIR/objects/info"
-	    echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates"
-	    ;;
+		mkdir -p "$GIT_DIR/objects/info"
+		echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates"
+		;;
 	esac
 	git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
 	;;
@@ -282,19 +282,19 @@ yes,yes)
 			rm -f "$GIT_DIR/TMP_ALT"
 		if test -f "$GIT_DIR/TMP_ALT"
 		then
-		    ( cd "$D" &&
-		      . git-parse-remote &&
-		      resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
-		    while read alt
-		    do
-			case "$alt" in 'bad alternate: '*) die "$alt";; esac
-			case "$quiet" in
-			'')	echo >&2 "Getting alternate: $alt" ;;
-			esac
-			rsync $quiet -av --ignore-existing  \
-			    --exclude info "$alt" "$GIT_DIR/objects" || exit
-		    done
-		    rm -f "$GIT_DIR/TMP_ALT"
+			( cd "$D" &&
+				. git-parse-remote &&
+				resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
+			while read alt
+			do
+				case "$alt" in 'bad alternate: '*) die "$alt";; esac
+				case "$quiet" in
+				'')	echo >&2 "Getting alternate: $alt" ;;
+				esac
+				rsync $quiet -av --ignore-existing  \
+					--exclude info "$alt" "$GIT_DIR/objects" || exit
+			done
+			rm -f "$GIT_DIR/TMP_ALT"
 		fi
 		git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
 		;;
diff --git a/git-fetch.sh b/git-fetch.sh
index c2eebee..cea6e0b 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -90,124 +90,124 @@ then
 fi
 
 append_fetch_head () {
-    head_="$1"
-    remote_="$2"
-    remote_name_="$3"
-    remote_nick_="$4"
-    local_name_="$5"
-    case "$6" in
-    t) not_for_merge_='not-for-merge' ;;
-    '') not_for_merge_= ;;
-    esac
+	head_="$1"
+	remote_="$2"
+	remote_name_="$3"
+	remote_nick_="$4"
+	local_name_="$5"
+	case "$6" in
+		t) not_for_merge_='not-for-merge' ;;
+		'') not_for_merge_= ;;
+	esac
 
-    # remote-nick is the URL given on the command line (or a shorthand)
-    # remote-name is the $GIT_DIR relative refs/ path we computed
-    # for this refspec.
+	# remote-nick is the URL given on the command line (or a shorthand)
+	# remote-name is the $GIT_DIR relative refs/ path we computed
+	# for this refspec.
 
-    # the $note_ variable will be fed to git-fmt-merge-msg for further
-    # processing.
-    case "$remote_name_" in
-    HEAD)
-	note_= ;;
-    refs/heads/*)
-	note_="$(expr "$remote_name_" : 'refs/heads/\(.*\)')"
-	note_="branch '$note_' of " ;;
-    refs/tags/*)
-	note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')"
-	note_="tag '$note_' of " ;;
-    refs/remotes/*)
-	note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')"
-	note_="remote branch '$note_' of " ;;
-    *)
-	note_="$remote_name of " ;;
-    esac
-    remote_1_=$(expr "z$remote_" : 'z\(.*\)\.git/*$') &&
+	# the $note_ variable will be fed to git-fmt-merge-msg for further
+	# processing.
+	case "$remote_name_" in
+	HEAD)
+		note_= ;;
+	refs/heads/*)
+		note_="$(expr "$remote_name_" : 'refs/heads/\(.*\)')"
+		note_="branch '$note_' of " ;;
+	refs/tags/*)
+		note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')"
+		note_="tag '$note_' of " ;;
+	refs/remotes/*)
+		note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')"
+		note_="remote branch '$note_' of " ;;
+	*)
+		note_="$remote_name of " ;;
+	esac
+	remote_1_=$(expr "z$remote_" : 'z\(.*\)\.git/*$') &&
 	remote_="$remote_1_"
-    note_="$note_$remote_"
+	note_="$note_$remote_"
 
-    # 2.6.11-tree tag would not be happy to be fed to resolve.
-    if git-cat-file commit "$head_" >/dev/null 2>&1
-    then
-	headc_=$(git-rev-parse --verify "$head_^0") || exit
-	echo "$headc_	$not_for_merge_	$note_" >>"$GIT_DIR/FETCH_HEAD"
-	[ "$verbose" ] && echo >&2 "* committish: $head_"
-	[ "$verbose" ] && echo >&2 "  $note_"
-    else
-	echo "$head_	not-for-merge	$note_" >>"$GIT_DIR/FETCH_HEAD"
-	[ "$verbose" ] && echo >&2 "* non-commit: $head_"
-	[ "$verbose" ] && echo >&2 "  $note_"
-    fi
-    if test "$local_name_" != ""
-    then
-	# We are storing the head locally.  Make sure that it is
-	# a fast forward (aka "reverse push").
-	fast_forward_local "$local_name_" "$head_" "$note_"
-    fi
+	# 2.6.11-tree tag would not be happy to be fed to resolve.
+	if git-cat-file commit "$head_" >/dev/null 2>&1
+	then
+		headc_=$(git-rev-parse --verify "$head_^0") || exit
+		echo "$headc_	$not_for_merge_	$note_" >>"$GIT_DIR/FETCH_HEAD"
+		[ "$verbose" ] && echo >&2 "* committish: $head_"
+		[ "$verbose" ] && echo >&2 "  $note_"
+	else
+		echo "$head_	not-for-merge	$note_" >>"$GIT_DIR/FETCH_HEAD"
+		[ "$verbose" ] && echo >&2 "* non-commit: $head_"
+		[ "$verbose" ] && echo >&2 "  $note_"
+	fi
+	if test "$local_name_" != ""
+	then
+		# We are storing the head locally.  Make sure that it is
+		# a fast forward (aka "reverse push").
+		fast_forward_local "$local_name_" "$head_" "$note_"
+	fi
 }
 
 fast_forward_local () {
-    mkdir -p "$(dirname "$GIT_DIR/$1")"
-    case "$1" in
-    refs/tags/*)
-	# Tags need not be pointing at commits so there
-	# is no way to guarantee "fast-forward" anyway.
-	if test -f "$GIT_DIR/$1"
-	then
-		if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+	mkdir -p "$(dirname "$GIT_DIR/$1")"
+	case "$1" in
+	refs/tags/*)
+		# Tags need not be pointing at commits so there
+		# is no way to guarantee "fast-forward" anyway.
+		if test -f "$GIT_DIR/$1"
 		then
-			[ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
+			if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+			then
+				[ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
+			else
+				echo >&2 "* $1: updating with $3"
+				git-update-ref -m "$rloga: updating tag" "$1" "$2"
+			fi
 		else
-			echo >&2 "* $1: updating with $3"
-			git-update-ref -m "$rloga: updating tag" "$1" "$2"
+			echo >&2 "* $1: storing $3"
+			git-update-ref -m "$rloga: storing tag" "$1" "$2"
 		fi
-	else
-		echo >&2 "* $1: storing $3"
-		git-update-ref -m "$rloga: storing tag" "$1" "$2"
-	fi
-	;;
+		;;
 
-    refs/heads/* | refs/remotes/*)
-	# $1 is the ref being updated.
-	# $2 is the new value for the ref.
-	local=$(git-rev-parse --verify "$1^0" 2>/dev/null)
-	if test "$local"
-	then
-	    # Require fast-forward.
-	    mb=$(git-merge-base "$local" "$2") &&
-	    case "$2,$mb" in
-	    $local,*)
-	        if test -n "$verbose"
+	refs/heads/* | refs/remotes/*)
+		# $1 is the ref being updated.
+		# $2 is the new value for the ref.
+		local=$(git-rev-parse --verify "$1^0" 2>/dev/null)
+		if test "$local"
 		then
-			echo >&2 "* $1: same as $3"
+			# Require fast-forward.
+			mb=$(git-merge-base "$local" "$2") &&
+			case "$2,$mb" in
+			$local,*)
+				if test -n "$verbose"
+				then
+					echo >&2 "* $1: same as $3"
+				fi
+				;;
+			*,$local)
+				echo >&2 "* $1: fast forward to $3"
+				echo >&2 "  from $local to $2"
+				git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
+				;;
+			*)
+				false
+				;;
+			esac || {
+				echo >&2 "* $1: does not fast forward to $3;"
+				case ",$force,$single_force," in
+				*,t,*)
+					echo >&2 "  forcing update."
+					git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
+					;;
+				*)
+					echo >&2 "  not updating."
+					exit 1
+					;;
+				esac
+			}
+		else
+			echo >&2 "* $1: storing $3"
+			git-update-ref -m "$rloga: storing head" "$1" "$2"
 		fi
 		;;
-	    *,$local)
-		echo >&2 "* $1: fast forward to $3"
-		echo >&2 "  from $local to $2"
-		git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
-		;;
-	    *)
-		false
-		;;
-	    esac || {
-		echo >&2 "* $1: does not fast forward to $3;"
-		case ",$force,$single_force," in
-		*,t,*)
-			echo >&2 "  forcing update."
-			git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
-			;;
-		*)
-			echo >&2 "  not updating."
-			exit 1
-			;;
-		esac
-	    }
-	else
-	    echo >&2 "* $1: storing $3"
-	    git-update-ref -m "$rloga: storing head" "$1" "$2"
-	fi
-	;;
-    esac
+	esac
 }
 
 case "$update_head_ok" in
@@ -225,26 +225,27 @@ reflist=$(get_remote_refs_for_fetch "$@"
 if test "$tags"
 then
 	taglist=`IFS="	" &&
-		  (
+		(
 			git-ls-remote $upload_pack --tags "$remote" ||
 			echo fail ouch
-		  ) |
-	          while read sha1 name
-		  do
+		) |
+		while read sha1 name
+		do
 			case "$sha1" in
 			fail)
 				exit 1
 			esac
 			case "$name" in
-			*^*) continue ;;
+			*^*)
+				continue ;;
 			esac
-		  	if git-check-ref-format "$name"
+			if git-check-ref-format "$name"
 			then
-			    echo ".${name}:${name}"
+				echo ".${name}:${name}"
 			else
-			    echo >&2 "warning: tag ${name} ignored"
+				echo >&2 "warning: tag ${name} ignored"
 			fi
-		  done` || exit
+		done` || exit
 	if test "$#" -gt 1
 	then
 		# remote URL plus explicit refspecs; we need to merge them.
@@ -256,146 +257,146 @@ then
 fi
 
 fetch_main () {
-  reflist="$1"
-  refs=
+	reflist="$1"
+	refs=
 
-  for ref in $reflist
-  do
-      refs="$refs$LF$ref"
+	for ref in $reflist
+	do
+		refs="$refs$LF$ref"
 
-      # These are relative path from $GIT_DIR, typically starting at refs/
-      # but may be HEAD
-      if expr "z$ref" : 'z\.' >/dev/null
-      then
-	  not_for_merge=t
-	  ref=$(expr "z$ref" : 'z\.\(.*\)')
-      else
-	  not_for_merge=
-      fi
-      if expr "z$ref" : 'z+' >/dev/null
-      then
-	  single_force=t
-	  ref=$(expr "z$ref" : 'z+\(.*\)')
-      else
-	  single_force=
-      fi
-      remote_name=$(expr "z$ref" : 'z\([^:]*\):')
-      local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)')
+		# These are relative path from $GIT_DIR, typically starting at refs/
+		# but may be HEAD
+		if expr "z$ref" : 'z\.' >/dev/null
+		then
+			not_for_merge=t
+			ref=$(expr "z$ref" : 'z\.\(.*\)')
+		else
+			not_for_merge=
+		fi
+		if expr "z$ref" : 'z+' >/dev/null
+		then
+			single_force=t
+			ref=$(expr "z$ref" : 'z+\(.*\)')
+		else
+			single_force=
+		fi
+		remote_name=$(expr "z$ref" : 'z\([^:]*\):')
+		local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)')
 
-      rref="$rref$LF$remote_name"
+		rref="$rref$LF$remote_name"
 
-      # There are transports that can fetch only one head at a time...
-      case "$remote" in
-      http://* | https://*)
-	  if [ -n "$GIT_SSL_NO_VERIFY" ]; then
-	      curl_extra_args="-k"
-	  fi
-	  max_depth=5
-	  depth=0
-	  head="ref: $remote_name"
-	  while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
-	  do
-	    remote_name_quoted=$(@@PERL@@ -e '
-	      my $u = $ARGV[0];
-              $u =~ s/^ref:\s*//;
-	      $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
-	      print "$u";
-	  ' "$head")
-	    head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
-	    depth=$( expr \( $depth + 1 \) )
-	  done
-	  expr "z$head" : "z$_x40\$" >/dev/null ||
-	      die "Failed to fetch $remote_name from $remote"
-	  echo >&2 Fetching "$remote_name from $remote" using http
-	  git-http-fetch -v -a "$head" "$remote/" || exit
-	  ;;
-      rsync://*)
-	  TMP_HEAD="$GIT_DIR/TMP_HEAD"
-	  rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
-	  head=$(git-rev-parse --verify TMP_HEAD)
-	  rm -f "$TMP_HEAD"
-	  test "$rsync_slurped_objects" || {
-	      rsync -av --ignore-existing --exclude info \
-		  "$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
+		# There are transports that can fetch only one head at a time...
+		case "$remote" in
+		http://* | https://*)
+			if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+				curl_extra_args="-k"
+			fi
+			max_depth=5
+			depth=0
+			head="ref: $remote_name"
+			while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
+			do
+				remote_name_quoted=$(@@PERL@@ -e '
+					my $u = $ARGV[0];
+					$u =~ s/^ref:\s*//;
+					$u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
+					print "$u";
+					' "$head")
+				head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+				depth=$( expr \( $depth + 1 \) )
+			done
+			expr "z$head" : "z$_x40\$" >/dev/null ||
+				die "Failed to fetch $remote_name from $remote"
+			echo >&2 Fetching "$remote_name from $remote" using http
+			git-http-fetch -v -a "$head" "$remote/" || exit
+			;;
+		rsync://*)
+			TMP_HEAD="$GIT_DIR/TMP_HEAD"
+			rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
+			head=$(git-rev-parse --verify TMP_HEAD)
+			rm -f "$TMP_HEAD"
+			test "$rsync_slurped_objects" || {
+				rsync -av --ignore-existing --exclude info \
+					"$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
 
-	      # Look at objects/info/alternates for rsync -- http will
-	      # support it natively and git native ones will do it on
-	      # the remote end.  Not having that file is not a crime.
-	      rsync -q "$remote/objects/info/alternates" \
-		  "$GIT_DIR/TMP_ALT" 2>/dev/null ||
-		  rm -f "$GIT_DIR/TMP_ALT"
-	      if test -f "$GIT_DIR/TMP_ALT"
-	      then
-		  resolve_alternates "$remote" <"$GIT_DIR/TMP_ALT" |
-		  while read alt
-		  do
-		      case "$alt" in 'bad alternate: '*) die "$alt";; esac
-		      echo >&2 "Getting alternate: $alt"
-		      rsync -av --ignore-existing --exclude info \
-		      "$alt" "$GIT_OBJECT_DIRECTORY/" || exit
-		  done
-		  rm -f "$GIT_DIR/TMP_ALT"
-	      fi
-	      rsync_slurped_objects=t
-	  }
-	  ;;
-      *)
-	  # We will do git native transport with just one call later.
-	  continue ;;
-      esac
+			# Look at objects/info/alternates for rsync -- http will
+			# support it natively and git native ones will do it on
+			# the remote end.  Not having that file is not a crime.
+			rsync -q "$remote/objects/info/alternates" \
+				"$GIT_DIR/TMP_ALT" 2>/dev/null ||
+				rm -f "$GIT_DIR/TMP_ALT"
+			if test -f "$GIT_DIR/TMP_ALT"
+			then
+				resolve_alternates "$remote" <"$GIT_DIR/TMP_ALT" |
+				while read alt
+				do
+					case "$alt" in 'bad alternate: '*) die "$alt";; esac
+					echo >&2 "Getting alternate: $alt"
+					rsync -av --ignore-existing --exclude info \
+						"$alt" "$GIT_OBJECT_DIRECTORY/" || exit
+				done
+				rm -f "$GIT_DIR/TMP_ALT"
+			fi
+			rsync_slurped_objects=t
+			}
+			;;
+		*)
+			# We will do git native transport with just one call later.
+			continue ;;
+		esac
 
-      append_fetch_head "$head" "$remote" \
-	  "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+		append_fetch_head "$head" "$remote" \
+			"$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
 
-  done
+	done
 
-  case "$remote" in
-  http://* | https://* | rsync://* )
-      ;; # we are already done.
-  *)
-    ( : subshell because we muck with IFS
-      IFS=" 	$LF"
-      (
-	  git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
-      ) |
-      while read sha1 remote_name
-      do
-	  case "$sha1" in
-	  failed)
-		  echo >&2 "Fetch failure: $remote"
-		  exit 1 ;;
-	  esac
-	  found=
-	  single_force=
-	  for ref in $refs
-	  do
-	      case "$ref" in
-	      +$remote_name:*)
-		  single_force=t
-		  not_for_merge=
-		  found="$ref"
-		  break ;;
-	      .+$remote_name:*)
-		  single_force=t
-		  not_for_merge=t
-		  found="$ref"
-		  break ;;
-	      .$remote_name:*)
-		  not_for_merge=t
-		  found="$ref"
-		  break ;;
-	      $remote_name:*)
-		  not_for_merge=
-		  found="$ref"
-		  break ;;
-	      esac
-	  done
-	  local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
-	  append_fetch_head "$sha1" "$remote" \
-		  "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
-      done
-    ) || exit ;;
-  esac
+	case "$remote" in
+	http://* | https://* | rsync://* )
+		;; # we are already done.
+	*)
+		( : subshell because we muck with IFS
+			IFS=" 	$LF"
+			(
+				git-fetch-pack $exec $keep "$remote" $rref || echo failed "$remote"
+			) |
+			while read sha1 remote_name
+			do
+				case "$sha1" in
+				failed)
+					echo >&2 "Fetch failure: $remote"
+					exit 1 ;;
+				esac
+				found=
+				single_force=
+				for ref in $refs
+				do
+					case "$ref" in
+					+$remote_name:*)
+						single_force=t
+						not_for_merge=
+						found="$ref"
+						break ;;
+					.+$remote_name:*)
+						single_force=t
+						not_for_merge=t
+						found="$ref"
+						break ;;
+					.$remote_name:*)
+						not_for_merge=t
+						found="$ref"
+						break ;;
+					$remote_name:*)
+						not_for_merge=
+						found="$ref"
+						break ;;
+					esac
+				done
+				local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
+				append_fetch_head "$sha1" "$remote" \
+					"$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+			done
+		) || exit ;;
+	esac
 
 }
 
@@ -439,7 +440,7 @@ case ",$update_head_ok,$orig_head," in
 	curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
 	if test "$curr_head" != "$orig_head"
 	then
-	    git-update-ref \
+		git-update-ref \
 			-m "$rloga: Undoing incorrectly fetched HEAD." \
 			HEAD "$orig_head"
 		die "Cannot fetch into the current branch."
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
index 2fdcaf7..38122ad 100755
--- a/git-ls-remote.sh
+++ b/git-ls-remote.sh
@@ -2,36 +2,36 @@ #!/bin/sh
 #
 
 usage () {
-    echo >&2 "usage: $0 [--heads] [--tags] [-u|--upload-pack <upload-pack>]"
-    echo >&2 "          <repository> <refs>..."
-    exit 1;
+	echo >&2 "usage: $0 [--heads] [--tags] [-u|--upload-pack <upload-pack>]"
+	echo >&2 "          <repository> <refs>..."
+	exit 1;
 }
 
 die () {
-    echo >&2 "$*"
-    exit 1
+	echo >&2 "$*"
+	exit 1
 }
 
 exec=
 while case "$#" in 0) break;; esac
 do
-  case "$1" in
-  -h|--h|--he|--hea|--head|--heads)
-  heads=heads; shift ;;
-  -t|--t|--ta|--tag|--tags)
-  tags=tags; shift ;;
-  -u|--u|--up|--upl|--uploa|--upload|--upload-|--upload-p|--upload-pa|\
-  --upload-pac|--upload-pack)
-	shift
-	exec="--exec=$1"
-	shift;;
-  --)
-  shift; break ;;
-  -*)
-  usage ;;
-  *)
-  break ;;
-  esac
+	case "$1" in
+	-h|--h|--he|--hea|--head|--heads)
+		heads=heads; shift ;;
+	-t|--t|--ta|--tag|--tags)
+		tags=tags; shift ;;
+	-u|--u|--up|--upl|--uploa|--upload|--upload-|--upload-p|--upload-pa|\
+		--upload-pac|--upload-pack)
+		shift
+		exec="--exec=$1"
+		shift;;
+	--)
+		shift; break ;;
+	-*)
+		usage ;;
+	*)
+		break ;;
+	esac
 done
 
 case "$#" in 0) usage ;; esac
@@ -50,9 +50,9 @@ tmpdir=$tmp-d
 
 case "$peek_repo" in
 http://* | https://* )
-        if [ -n "$GIT_SSL_NO_VERIFY" ]; then
-            curl_extra_args="-k"
-        fi
+	if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+		curl_extra_args="-k"
+	fi
 	curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
 		echo "failed	slurping"
 	;;
-- 
1.4.1.1
Jakub Narebski· Aug 1, 2006, 09:36 UTC · re: Jakub Narebski · lore

[PATCH 2/2] Set User-Agent string in shell scripts used for fetching

This needs checking for shell portability.
Signed-off-by: Jakub Narebski <jnareb@gmail.com>
---
Junio C Hamano wrote:
Show 15 quoted lines
> Jakub Narebski <jnareb@gmail.com> writes:
> 
>> By the way, I wonder why git when cloning/fetching via http protocol
>> uses e.g. "git/1.4.2.rc2.ge0bed" as User-Agent: string when fetching objects
>> and packs, and e.g. "curl/7.15.4 (i486-pc-linux-gnu) libcurl/7.15.4
>> OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5" as User-Agent: when fetching refs
>> (heads and tags) and info/refs.
> 
> "Why?", meaning if we deliberately do so for some good reason?
> 
> There isn't.
> 
> git-http-fetch uses its own User-Agent string, but the shell
> script wrappers that use curl executable do not bother setting
> customized User-Agent string; that is why.
So there :-)
 git-clone.sh     |    4 +++-
 git-fetch.sh     |    5 ++++-
 git-ls-remote.sh |    7 +++++--
 3 files changed, 12 insertions(+), 4 deletions(-)
Show changes to 3 files +12 −4

git-clone.sh, git-fetch.sh, git-ls-remote.sh

diff --git a/git-clone.sh b/git-clone.sh
index adb752d..a9676d6 100755
--- a/git-clone.sh
+++ b/git-clone.sh
@@ -20,10 +20,12 @@ get_repo_base() {
 if [ -n "$GIT_SSL_NO_VERIFY" ]; then
 	curl_extra_args="-k"
 fi
+curl_user_agent="$(git --version)"
+curl_user_agent="git/${curl_user_agent##git version }"
 
 http_fetch () {
 	# $1 = Remote, $2 = Local
-	curl -nsfL $curl_extra_args "$1" >"$2"
+	curl -nsfL $curl_extra_args $curl_user_agent "$1" >"$2"
 }
 
 clone_dumb_http () {
diff --git a/git-fetch.sh b/git-fetch.sh
index cea6e0b..6635bd5 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -291,6 +291,8 @@ fetch_main () {
 			if [ -n "$GIT_SSL_NO_VERIFY" ]; then
 				curl_extra_args="-k"
 			fi
+			curl_user_agent="$(git --version)"
+			curl_user_agent="git/${curl_user_agent##git version }"
 			max_depth=5
 			depth=0
 			head="ref: $remote_name"
@@ -302,7 +304,8 @@ fetch_main () {
 					$u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
 					print "$u";
 					' "$head")
-				head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+				head=$(curl -nsfL $curl_extra_args $curl_user_agent \
+					"$remote/$remote_name_quoted")
 				depth=$( expr \( $depth + 1 \) )
 			done
 			expr "z$head" : "z$_x40\$" >/dev/null ||
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
index 38122ad..32b93da 100755
--- a/git-ls-remote.sh
+++ b/git-ls-remote.sh
@@ -53,8 +53,11 @@ http://* | https://* )
 	if [ -n "$GIT_SSL_NO_VERIFY" ]; then
 		curl_extra_args="-k"
 	fi
-	curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
-		echo "failed	slurping"
+	curl_user_agent="$(git --version)"
+	curl_user_agent="git/${curl_user_agent##git version }"
+	curl -nsf $curl_extra_args $curl_user_agent --header "Pragma: no-cache" \
+		"$peek_repo/info/refs" ||
+		echo "failed slurping"
 	;;
 
 rsync://* )
-- 
1.4.1.1
Junio C Hamano· Aug 1, 2006, 09:51 UTC · re: Jakub Narebski · lore

Re: [PATCH 2/2] Set User-Agent string in shell scripts used for fetching

Jakub Narebski <jnareb@gmail.com> writes:
Show 9 quoted lines
>> "Why?", meaning if we deliberately do so for some good reason?
>> 
>> There isn't.
>> 
>> git-http-fetch uses its own User-Agent string, but the shell
>> script wrappers that use curl executable do not bother setting
>> customized User-Agent string; that is why.
>
> So there :-)

Who said we want to? Actually I explicitly said we do not bother ;-).

Junio C Hamano· Aug 1, 2006, 09:50 UTC · re: Jakub Narebski · lore

Re: [PATCH 1/2] Use tabs for indent in shell scripts

Jakub Narebski <jnareb@gmail.com> writes:
>  if [ -n "$GIT_SSL_NO_VERIFY" ]; then
> -    curl_extra_args="-k"
> +	curl_extra_args="-k"
>  fi
Things like this makes some sense...
>  		if test -n "$use_separate_remote" &&
> -		   branch_name=`expr "z$name" : 'zheads/\(.*\)'`
> +			branch_name=`expr "z$name" : 'zheads/\(.*\)'`
>  		then
... but not this, which is not just indent but an alignment.
Show 9 quoted lines
>  		case "$2" in
>  		'')
> -		    usage ;;
> +			usage ;;
>  		*/*)
> -		    echo >&2 "'$2' is not suitable for an origin name"
> -		    exit 1
> +			echo >&2 "'$2' is not suitable for an origin name"
> +			exit 1

Also shell scripts tend to become too deeply indented, and 4-column indentation helps to keep things within typical screen width.

In short, not very enthused.
Jakub Narebski· Aug 1, 2006, 10:01 UTC · re: Junio C Hamano · lore

Re: [PATCH 1/2] Use tabs for indent in shell scripts

Junio C Hamano wrote:
Show 15 quoted lines
> Jakub Narebski <jnareb@gmail.com> writes:
> 
>>  if [ -n "$GIT_SSL_NO_VERIFY" ]; then
>> -    curl_extra_args="-k"
>> +    curl_extra_args="-k"
>>  fi
> 
> Things like this makes some sense...
> 
>>              if test -n "$use_separate_remote" &&
>> -               branch_name=`expr "z$name" : 'zheads/\(.*\)'`
>> +                    branch_name=`expr "z$name" : 'zheads/\(.*\)'`
>>              then
> 
> ... but not this, which is not just indent but an alignment.

Sorry, my mistake. Tabs for indent, spaces for align, of course. So discard this chunk, please.

I'm not sure how to indent sequences like the following:
  command arg \
    arg arg arg \
    arg arg arg
The above I think everybody would agree on.
  command &&
    command &&
    command
  command ||
    command ||
    command
Not so sure about above
[...]
> Also shell scripts tend to become too deeply indented, and 4-column
> indentation helps to keep things within typical screen width.
> 
> In short, not very enthused.

I use tab-width 2 and script didn't get too indented. All changed scripts except git-clone.sh fit in 80 column window for tab width 2, and the lines in git-clone.sh which do not fit in 80 columns are not indented at all.

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Luben Tuikov· Aug 1, 2006, 20:10 UTC · re: Junio C Hamano · lore

Re: A few more fixups to gitweb

--- Junio C Hamano <junkio@cox.net> wrote:
Show 6 quoted lines
> One thing to note.  Please make sure that you do not see
> anything in Apache error log after you make your changes.  I do
> not remember the details but kernel.org folks were very unhappy
> earlier when gitweb spewed stuff into the error log, and if I
> recall correctly things that output to the error stream were not
> friendly to the http-server cache for some reason.

I've been meaning to mention this as well. Gitweb patches, especially recent ones, need more (thorough) testing, before posted to the list for inclusion.

    Luben
Matthias Lederhofer· Aug 1, 2006, 12:48 UTC · re: Jakub Narebski · lore

[PATCH] gitweb: clean up user configuration part

 * add a marker where the user configuration starts/ends
 * replaced the two $projectroot = lines
 * add a comment to $projectlist
 * reorder the variables
Signed-off-by: Matthias Lederhofer <matled@gmx.net>
---
I tried to put the variables at top which will be edited most likely
(cannot have sane default values).
---
 gitweb/gitweb.cgi |   58 ++++++++++++++++++++++++++++++++---------------------
 1 files changed, 35 insertions(+), 23 deletions(-)
Show changes to gitweb/gitweb.cgi +35 −23
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index 9448b72..ca2ef70 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -18,44 +18,42 @@ use File::Find qw();
 binmode STDOUT, ':utf8';
 
 our $cgi = new CGI;
-our $version = "267";
-our $my_url = $cgi->url();
-our $my_uri = $cgi->url(-absolute => 1);
-our $rss_link = "";
 
-# core git executable to use
-# this can just be "git" if your webserver has a sensible PATH
-our $GIT = "/usr/bin/git";
+##
+## BEGIN USER CONFIGURATION
+##
 
 # absolute fs-path which will be prepended to the project path
-#our $projectroot = "/pub/scm";
-our $projectroot = "/home/kay/public_html/pub/scm";
+our $projectroot = "/pub/git";
 
-# version of the core git binary
-our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+# source of projects list
+# file: list of projects, may use subdirectories
+# directory: all git repositories in the directory (not subdirectories)
+our $projects_list = "index/index.aux";
+#our $projects_list = $projectroot;
 
-# location for temporary files needed for diffs
-our $git_temp = "/tmp/gitweb";
-if (! -d $git_temp) {
-	mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
-}
+# html text to include at home page
+our $home_text = "indextext.html";
 
-# target of the home link on top of all pages
-our $home_link = $my_uri;
+# core git executable to use
+# this can just be "git" if your webserver has a sensible PATH
+our $GIT = "/usr/bin/git";
 
 # name of your site or organization to appear in page titles
 # replace this with something more descriptive for clearer bookmarks
 our $site_name = $ENV{'SERVER_NAME'} || "Untitled";
 
-# html text to include at home page
-our $home_text = "indextext.html";
+our $my_url = $cgi->url();
+our $my_uri = $cgi->url(-absolute => 1);
+our $rss_link = "";
+# target of the home link on top of all pages
+our $home_link = $my_uri;
 
 # URI of default stylesheet
 our $stylesheet = "gitweb.css";
 
-# source of projects list
-#our $projects_list = $projectroot;
-our $projects_list = "index/index.aux";
+# location for temporary files needed for diffs
+our $git_temp = "/tmp/gitweb";
 
 # default blob_plain mimetype and default charset for text/plain blob
 our $default_blob_plain_mimetype = 'text/plain';
@@ -65,6 +63,20 @@ # file to use for guessing MIME types be
 # (relative to the current git repository)
 our $mimetypes_file = undef;
 
+##
+## END USER CONFIGURATION
+##
+
+# version of the core git binary
+our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+
+our $version = "267";
+
+# create temp directory
+if (! -d $git_temp) {
+	mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
+}
+
 # input validation and dispatch
 our $action = $cgi->param('a');
 if (defined $action) {
-- 
1.4.2.rc2.g4713
Matthias Lederhofer· Aug 1, 2006, 13:53 UTC · re: Jakub Narebski · lore

[RFC/PATCH] gitweb: include perl files for configuration

gitweb will first include all files after __DATA__ and then $GITWEB_CONFIG

Signed-off-by: Matthias Lederhofer <matled@gmx.net>
---
I really hate upgrading gitweb because every time I've to copy the
configuration part from the old script to the new one.  I do some
regexp to find the values for some configuration values so I'd prefer
to include a perl file instead of a normal configuration file.
For example I'd use this with this patch:
$GIT = "/usr/local/bin/git";
$stylesheet = "/gitweb.css";
if ($ENV{'SCRIPT_NAME'} =~ m#^/([a-z0-9-]+)/#) {
    $projectroot = "/home/$1";
    $home_text =   $projectroot."/gitweb.html";
    $projects_list = $projectroot."/gitweb.list";
} else {
    $projectroot = "/home";
    $home_text = "/www/gitweb.html";
    $projects_list = "/www/gitweb.list";
}

In this setup there is one subdirectory per-user which contains a symlink to ../gitweb.cgi allowing them to maintain their own gitweb.

Later (see gitweb.pl post) the Makefile could also add the path to the configuration file automatically if a certain variable is set (by appending the filename to gitweb.cgi).

Using __DATA__ instead of a variable holding the filename allows to add the configuration file easily without editing the file in an editor or trying to replace some part (without breaking something) using sed.

This patch is on top of my last patch (user configuration cleanup).
---
 gitweb/gitweb.cgi |   35 ++++++++++++++++++++++++++++++++---
 1 files changed, 32 insertions(+), 3 deletions(-)
Show changes to gitweb/gitweb.cgi +32 −3
diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.cgi
index ca2ef70..96f8852 100755
--- a/gitweb/gitweb.cgi
+++ b/gitweb/gitweb.cgi
@@ -23,6 +23,12 @@ ##
 ## BEGIN USER CONFIGURATION
 ##
 
+# You can let gitweb 'do' (perldoc -f do) configuration files after this
+# configuration section to override the values.  The configuration files
+# will be executed in this order:
+# - files after __DATA__ (first file first)
+# - file in the $GITWEB_CONFIG environment variable
+
 # absolute fs-path which will be prepended to the project path
 our $projectroot = "/pub/git";
 
@@ -67,10 +73,31 @@ ##
 ## END USER CONFIGURATION
 ##
 
-# version of the core git binary
-our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
-
 our $version = "267";
+# die_error uses this, circumvents some warnings
+our $git_version = 'unknown';
+
+# 'do' configuration files
+for my $file (<DATA>, $ENV{'GITWEB_CONFIG'}) {
+	next if (!defined($file));
+	$file =~ s/\r?\n$//;
+	if (! -e $file) {
+		warn("configuration file '$file' does not exist");
+		die_error('500 Internal Server Error', 'Configuration error');
+	}
+	undef $@;
+	undef $!;
+	next if (defined(do($file)));
+	if ($@) {
+		warn("parse error in configuration file '$file': $@");
+	} else {
+		warn("error in configuration file '$file': $!");
+	}
+	die_error('500 Internal Server Error', 'Configuration error');
+}
+
+# version of the core git binary
+$git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
 
 # create temp directory
 if (! -d $git_temp) {
@@ -2608,3 +2635,5 @@ sub git_opml {
 	      "</body>\n".
 	      "</opml>\n";
 }
+
+__DATA__
-- 
1.4.2.rc2.g4713

← back to recent threads