summaryrefslogtreecommitdiff
path: root/gitweb/gitweb.cgi
blob: ec343924c8e12b7cca55185f235eb6342b1b0904 (plain)
    1 #!/usr/bin/perl
    2 
    3 # gitweb - simple web interface to track changes in git repositories
    4 #
    5 # (C) 2005-2006, Kay Sievers <kay.sievers@vrfy.org>
    6 # (C) 2005, Christian Gierke
    7 #
    8 # This program is licensed under the GPLv2
    9 
   10 use strict;
   11 use warnings;
   12 use CGI qw(:standard :escapeHTML -nosticky);
   13 use CGI::Util qw(unescape);
   14 use CGI::Carp qw(fatalsToBrowser);
   15 use Encode;
   16 use Fcntl ':mode';
   17 binmode STDOUT, ':utf8';
   18 
   19 our $cgi = new CGI;
   20 our $version = "267";
   21 our $my_url = $cgi->url();
   22 our $my_uri = $cgi->url(-absolute => 1);
   23 our $rss_link = "";
   24 
   25 # core git executable to use
   26 # this can just be "git" if your webserver has a sensible PATH
   27 our $GIT = "/usr/bin/git";
   28 
   29 # absolute fs-path which will be prepended to the project path
   30 #our $projectroot = "/pub/scm";
   31 our $projectroot = "/home/crux/scm";
   32 
   33 # version of the core git binary
   34 our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
   35 
   36 # location for temporary files needed for diffs
   37 our $git_temp = "/tmp/gitweb";
   38 if (! -d $git_temp) {
   39     mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
   40 }
   41 
   42 # target of the home link on top of all pages
   43 our $home_link = $my_uri;
   44 
   45 # name of your site or organization to appear in page titles
   46 # replace this with something more descriptive for clearer bookmarks
   47 our $site_name = $ENV{'SERVER_NAME'} || "Untitled";
   48 
   49 # html text to include at home page
   50 our $home_text = "indextext.html";
   51 
   52 # URI of default stylesheet
   53 our $stylesheet = "gitweb.css";
   54 
   55 # source of projects list
   56 #our $projects_list = $projectroot;
   57 our $projects_list = "index.aux";
   58 
   59 # default blob_plain mimetype and default charset for text/plain blob
   60 our $default_blob_plain_mimetype = 'text/plain';
   61 our $default_text_plain_charset  = undef;
   62 
   63 # file to use for guessing MIME types before trying /etc/mime.types
   64 # (relative to the current git repository)
   65 our $mimetypes_file = undef;
   66 
   67 # input validation and dispatch
   68 our $action = $cgi->param('a');
   69 if (defined $action) {
   70 	if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
   71 		undef $action;
   72 		die_error(undef, "Invalid action parameter.");
   73 	}
   74 	if ($action eq "git-logo.png") {
   75 		git_logo();
   76 		exit;
   77 	} elsif ($action eq "opml") {
   78 		git_opml();
   79 		exit;
   80 	}
   81 }
   82 
   83 our $order = $cgi->param('o');
   84 if (defined $order) {
   85 	if ($order =~ m/[^0-9a-zA-Z_]/) {
   86 		undef $order;
   87 		die_error(undef, "Invalid order parameter.");
   88 	}
   89 }
   90 
   91 our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
   92 if (defined $project) {
   93 	$project =~ s|^/||; $project =~ s|/$||;
   94 	$project = validate_input($project);
   95 	if (!defined($project)) {
   96 		die_error(undef, "Invalid project parameter.");
   97 	}
   98 	if (!(-d "$projectroot/$project")) {
   99 		undef $project;
  100 		die_error(undef, "No such directory.");
  101 	}
  102 	if (!(-e "$projectroot/$project/HEAD")) {
  103 		undef $project;
  104 		die_error(undef, "No such project.");
  105 	}
  106 	$rss_link = "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" .
  107 		    "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>";
  108 	$ENV{'GIT_DIR'} = "$projectroot/$project";
  109 } else {
  110 	git_project_list();
  111 	exit;
  112 }
  113 
  114 our $file_name = $cgi->param('f');
  115 if (defined $file_name) {
  116 	$file_name = validate_input($file_name);
  117 	if (!defined($file_name)) {
  118 		die_error(undef, "Invalid file parameter.");
  119 	}
  120 }
  121 
  122 our $hash = $cgi->param('h');
  123 if (defined $hash) {
  124 	$hash = validate_input($hash);
  125 	if (!defined($hash)) {
  126 		die_error(undef, "Invalid hash parameter.");
  127 	}
  128 }
  129 
  130 our $hash_parent = $cgi->param('hp');
  131 if (defined $hash_parent) {
  132 	$hash_parent = validate_input($hash_parent);
  133 	if (!defined($hash_parent)) {
  134 		die_error(undef, "Invalid hash parent parameter.");
  135 	}
  136 }
  137 
  138 our $hash_base = $cgi->param('hb');
  139 if (defined $hash_base) {
  140 	$hash_base = validate_input($hash_base);
  141 	if (!defined($hash_base)) {
  142 		die_error(undef, "Invalid hash base parameter.");
  143 	}
  144 }
  145 
  146 our $page = $cgi->param('pg');
  147 if (defined $page) {
  148 	if ($page =~ m/[^0-9]$/) {
  149 		undef $page;
  150 		die_error(undef, "Invalid page parameter.");
  151 	}
  152 }
  153 
  154 our $searchtext = $cgi->param('s');
  155 if (defined $searchtext) {
  156 	if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
  157 		undef $searchtext;
  158 		die_error(undef, "Invalid search parameter.");
  159 	}
  160 	$searchtext = quotemeta $searchtext;
  161 }
  162 
  163 sub validate_input {
  164 	my $input = shift;
  165 
  166 	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
  167 		return $input;
  168 	}
  169 	if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
  170 		return undef;
  171 	}
  172 	if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
  173 		return undef;
  174 	}
  175 	return $input;
  176 }
  177 
  178 if (!defined $action || $action eq "summary") {
  179 	git_summary();
  180 	exit;
  181 } elsif ($action eq "heads") {
  182 	git_heads();
  183 	exit;
  184 } elsif ($action eq "tags") {
  185 	git_tags();
  186 	exit;
  187 } elsif ($action eq "blob") {
  188 	git_blob();
  189 	exit;
  190 } elsif ($action eq "blob_plain") {
  191 	git_blob_plain();
  192 	exit;
  193 } elsif ($action eq "tree") {
  194 	git_tree();
  195 	exit;
  196 } elsif ($action eq "rss") {
  197 	git_rss();
  198 	exit;
  199 } elsif ($action eq "commit") {
  200 	git_commit();
  201 	exit;
  202 } elsif ($action eq "log") {
  203 	git_log();
  204 	exit;
  205 } elsif ($action eq "blobdiff") {
  206 	git_blobdiff();
  207 	exit;
  208 } elsif ($action eq "blobdiff_plain") {
  209 	git_blobdiff_plain();
  210 	exit;
  211 } elsif ($action eq "commitdiff") {
  212 	git_commitdiff();
  213 	exit;
  214 } elsif ($action eq "commitdiff_plain") {
  215 	git_commitdiff_plain();
  216 	exit;
  217 } elsif ($action eq "history") {
  218 	git_history();
  219 	exit;
  220 } elsif ($action eq "search") {
  221 	git_search();
  222 	exit;
  223 } elsif ($action eq "shortlog") {
  224 	git_shortlog();
  225 	exit;
  226 } elsif ($action eq "tag") {
  227 	git_tag();
  228 	exit;
  229 } elsif ($action eq "blame") {
  230 	git_blame2();
  231 	exit;
  232 } else {
  233 	undef $action;
  234 	die_error(undef, "Unknown action.");
  235 	exit;
  236 }
  237 
  238 # quote unsafe chars, but keep the slash, even when it's not
  239 # correct, but quoted slashes look too horrible in bookmarks
  240 sub esc_param {
  241 	my $str = shift;
  242 	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
  243 	$str =~ s/\+/%2B/g;
  244 	$str =~ s/ /\+/g;
  245 	return $str;
  246 }
  247 
  248 # replace invalid utf8 character with SUBSTITUTION sequence
  249 sub esc_html {
  250 	my $str = shift;
  251 	$str = decode("utf8", $str, Encode::FB_DEFAULT);
  252 	$str = escapeHTML($str);
  253 	return $str;
  254 }
  255 
  256 # git may return quoted and escaped filenames
  257 sub unquote {
  258 	my $str = shift;
  259 	if ($str =~ m/^"(.*)"$/) {
  260 		$str = $1;
  261 		$str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
  262 	}
  263 	return $str;
  264 }
  265 
  266 # CSS class for given age value (in seconds)
  267 sub age_class {
  268 	my $age = shift;
  269 
  270 	if ($age < 60*60*2) {
  271 		return "age0";
  272 	} elsif ($age < 60*60*24*2) {
  273 		return "age1";
  274 	} else {
  275 		return "age2";
  276 	}
  277 }
  278 
  279 sub git_header_html {
  280 	my $status = shift || "200 OK";
  281 	my $expires = shift;
  282 
  283 	my $title = "$site_name git";
  284 	if (defined $project) {
  285 		$title .= " - $project";
  286 		if (defined $action) {
  287 			$title .= "/$action";
  288 			if (defined $file_name) {
  289 				$title .= " - $file_name";
  290 				if ($action eq "tree" && $file_name !~ m|/$|) {
  291 					$title .= "/";
  292 				}
  293 			}
  294 		}
  295 	}
  296 	my $content_type;
  297 	# require explicit support from the UA if we are to send the page as
  298 	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
  299 	# we have to do this because MSIE sometimes globs '*/*', pretending to
  300 	# support xhtml+xml but choking when it gets what it asked for.
  301 	if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
  302 		$content_type = 'application/xhtml+xml';
  303 	} else {
  304 		$content_type = 'text/html';
  305 	}
  306     $content_type = 'text/html';
  307 	print $cgi->header(-type=>$content_type,  -charset => 'utf-8', -status=> $status, -expires => $expires);
  308 	print <<EOF;
  309 <?xml version="1.0" encoding="utf-8"?>
  310 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  311 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
  312 <!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
  313 <!-- git core binaries version $git_version -->
  314 <head>
  315 <meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
  316 <meta name="robots" content="index, nofollow"/>
  317 <title>$title</title>
  318 <link rel="stylesheet" type="text/css" href="$stylesheet"/>
  319 $rss_link
  320 </head>
  321 <body>
  322 <div class="cruxheader">
  323 	 <a href="/" title="">Home</a> :: 
  324 		 <a href="/Main/Documentation">Documentation</a> :: 
  325 			  <a href="/Main/Download">Download</a> :: 
  326 			  <a href="/Main/Development">Development</a> :: 
  327 			  <a href="/Main/Community">Community</a> :: 
  328 			  <a href="/Public/HomePage">Public</a> :: 
  329 			  <a href="/portdb">Ports</a> :: 
  330 			  <a href="/Main/Bugs" title="">Bugs</a> :: 
  331 			  <a href="/Main/Links" title="">Links</a> :: 
  332 			  <a href="/Main/About" title="">About</a>
  333 
  334 </div>
  335 EOF
  336 	print "<div class=\"page_header\">\n" .
  337 	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
  338 	      "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
  339 	      "</a>\n";
  340 	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
  341 	if (defined $project) {
  342 		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project));
  343 		if (defined $action) {
  344 			print " / $action";
  345 		}
  346 		print "\n";
  347 		if (!defined $searchtext) {
  348 			$searchtext = "";
  349 		}
  350 		my $search_hash;
  351 		if (defined $hash_base) {
  352 			$search_hash = $hash_base;
  353 		} elsif (defined $hash) {
  354 			$search_hash = $hash;
  355 		} else {
  356 			$search_hash = "HEAD";
  357 		}
  358 		$cgi->param("a", "search");
  359 		$cgi->param("h", $search_hash);
  360 		print $cgi->startform(-method => "get", -action => $my_uri) .
  361 		      "<div class=\"search\">\n" .
  362 		      $cgi->hidden(-name => "p") . "\n" .
  363 		      $cgi->hidden(-name => "a") . "\n" .
  364 		      $cgi->hidden(-name => "h") . "\n" .
  365 		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
  366 		      "</div>" .
  367 		      $cgi->end_form() . "\n";
  368 	}
  369 	print "</div><div class=\"content\">\n";
  370 }
  371 
  372 sub git_footer_html {
  373 	print "</div><div class=\"page_footer\">\n";
  374 	if (defined $project) {
  375 		my $descr = git_read_description($project);
  376 		if (defined $descr) {
  377 			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
  378 		}
  379 		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n";
  380 	} else {
  381 		print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n";
  382 	}
  383 	print "</div>\n" .
  384 	      "</body>\n" .
  385 	      "</html>";
  386 }
  387 
  388 sub die_error {
  389 	my $status = shift || "403 Forbidden";
  390 	my $error = shift || "Malformed query, file missing or permission denied";
  391 
  392 	git_header_html($status);
  393 	print "<div class=\"page_body\">\n" .
  394 	      "<br/><br/>\n" .
  395 	      "$status - $error\n" .
  396 	      "<br/>\n" .
  397 	      "</div>\n";
  398 	git_footer_html();
  399 	exit;
  400 }
  401 
  402 sub git_get_type {
  403 	my $hash = shift;
  404 
  405 	open my $fd, "-|", "$GIT cat-file -t $hash" or return;
  406 	my $type = <$fd>;
  407 	close $fd or return;
  408 	chomp $type;
  409 	return $type;
  410 }
  411 
  412 sub git_read_head {
  413 	my $project = shift;
  414 	my $oENV = $ENV{'GIT_DIR'};
  415 	my $retval = undef;
  416 	$ENV{'GIT_DIR'} = "$projectroot/$project";
  417 	if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") {
  418 		my $head = <$fd>;
  419 		close $fd;
  420 		if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
  421 			$retval = $1;
  422 		}
  423 	}
  424 	if (defined $oENV) {
  425 		$ENV{'GIT_DIR'} = $oENV;
  426 	}
  427 	return $retval;
  428 }
  429 
  430 sub git_read_hash {
  431 	my $path = shift;
  432 
  433 	open my $fd, "$projectroot/$path" or return undef;
  434 	my $head = <$fd>;
  435 	close $fd;
  436 	chomp $head;
  437 	if ($head =~ m/^[0-9a-fA-F]{40}$/) {
  438 		return $head;
  439 	}
  440 }
  441 
  442 sub git_read_description {
  443 	my $path = shift;
  444 
  445 	open my $fd, "$projectroot/$path/description" or return undef;
  446 	my $descr = <$fd>;
  447 	close $fd;
  448 	chomp $descr;
  449 	return $descr;
  450 }
  451 
  452 sub git_read_tag {
  453 	my $tag_id = shift;
  454 	my %tag;
  455 	my @comment;
  456 
  457 	open my $fd, "-|", "$GIT cat-file tag $tag_id" or return;
  458 	$tag{'id'} = $tag_id;
  459 	while (my $line = <$fd>) {
  460 		chomp $line;
  461 		if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
  462 			$tag{'object'} = $1;
  463 		} elsif ($line =~ m/^type (.+)$/) {
  464 			$tag{'type'} = $1;
  465 		} elsif ($line =~ m/^tag (.+)$/) {
  466 			$tag{'name'} = $1;
  467 		} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
  468 			$tag{'author'} = $1;
  469 			$tag{'epoch'} = $2;
  470 			$tag{'tz'} = $3;
  471 		} elsif ($line =~ m/--BEGIN/) {
  472 			push @comment, $line;
  473 			last;
  474 		} elsif ($line eq "") {
  475 			last;
  476 		}
  477 	}
  478 	push @comment, <$fd>;
  479 	$tag{'comment'} = \@comment;
  480 	close $fd or return;
  481 	if (!defined $tag{'name'}) {
  482 		return
  483 	};
  484 	return %tag
  485 }
  486 
  487 sub age_string {
  488 	my $age = shift;
  489 	my $age_str;
  490 
  491 	if ($age > 60*60*24*365*2) {
  492 		$age_str = (int $age/60/60/24/365);
  493 		$age_str .= " years ago";
  494 	} elsif ($age > 60*60*24*(365/12)*2) {
  495 		$age_str = int $age/60/60/24/(365/12);
  496 		$age_str .= " months ago";
  497 	} elsif ($age > 60*60*24*7*2) {
  498 		$age_str = int $age/60/60/24/7;
  499 		$age_str .= " weeks ago";
  500 	} elsif ($age > 60*60*24*2) {
  501 		$age_str = int $age/60/60/24;
  502 		$age_str .= " days ago";
  503 	} elsif ($age > 60*60*2) {
  504 		$age_str = int $age/60/60;
  505 		$age_str .= " hours ago";
  506 	} elsif ($age > 60*2) {
  507 		$age_str = int $age/60;
  508 		$age_str .= " min ago";
  509 	} elsif ($age > 2) {
  510 		$age_str = int $age;
  511 		$age_str .= " sec ago";
  512 	} else {
  513 		$age_str .= " right now";
  514 	}
  515 	return $age_str;
  516 }
  517 
  518 sub git_read_commit {
  519 	my $commit_id = shift;
  520 	my $commit_text = shift;
  521 
  522 	my @commit_lines;
  523 	my %co;
  524 
  525 	if (defined $commit_text) {
  526 		@commit_lines = @$commit_text;
  527 	} else {
  528 		$/ = "\0";
  529 		open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return;
  530 		@commit_lines = split '\n', <$fd>;
  531 		close $fd or return;
  532 		$/ = "\n";
  533 		pop @commit_lines;
  534 	}
  535 	my $header = shift @commit_lines;
  536 	if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
  537 		return;
  538 	}
  539 	($co{'id'}, my @parents) = split ' ', $header;
  540 	$co{'parents'} = \@parents;
  541 	$co{'parent'} = $parents[0];
  542 	while (my $line = shift @commit_lines) {
  543 		last if $line eq "\n";
  544 		if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
  545 			$co{'tree'} = $1;
  546 		} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
  547 			$co{'author'} = $1;
  548 			$co{'author_epoch'} = $2;
  549 			$co{'author_tz'} = $3;
  550 			if ($co{'author'} =~ m/^([^<]+) </) {
  551 				$co{'author_name'} = $1;
  552 			} else {
  553 				$co{'author_name'} = $co{'author'};
  554 			}
  555 		} elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
  556 			$co{'committer'} = $1;
  557 			$co{'committer_epoch'} = $2;
  558 			$co{'committer_tz'} = $3;
  559 			$co{'committer_name'} = $co{'committer'};
  560 			$co{'committer_name'} =~ s/ <.*//;
  561 		}
  562 	}
  563 	if (!defined $co{'tree'}) {
  564 		return;
  565 	};
  566 
  567 	foreach my $title (@commit_lines) {
  568 		$title =~ s/^    //;
  569 		if ($title ne "") {
  570 			$co{'title'} = chop_str($title, 80, 5);
  571 			# remove leading stuff of merges to make the interesting part visible
  572 			if (length($title) > 50) {
  573 				$title =~ s/^Automatic //;
  574 				$title =~ s/^merge (of|with) /Merge ... /i;
  575 				if (length($title) > 50) {
  576 					$title =~ s/(http|rsync):\/\///;
  577 				}
  578 				if (length($title) > 50) {
  579 					$title =~ s/(master|www|rsync)\.//;
  580 				}
  581 				if (length($title) > 50) {
  582 					$title =~ s/kernel.org:?//;
  583 				}
  584 				if (length($title) > 50) {
  585 					$title =~ s/\/pub\/scm//;
  586 				}
  587 			}
  588 			$co{'title_short'} = chop_str($title, 50, 5);
  589 			last;
  590 		}
  591 	}
  592 	# remove added spaces
  593 	foreach my $line (@commit_lines) {
  594 		$line =~ s/^    //;
  595 	}
  596 	$co{'comment'} = \@commit_lines;
  597 
  598 	my $age = time - $co{'committer_epoch'};
  599 	$co{'age'} = $age;
  600 	$co{'age_string'} = age_string($age);
  601 	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
  602 	if ($age > 60*60*24*7*2) {
  603 		$co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
  604 		$co{'age_string_age'} = $co{'age_string'};
  605 	} else {
  606 		$co{'age_string_date'} = $co{'age_string'};
  607 		$co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
  608 	}
  609 	return %co;
  610 }
  611 
  612 sub git_diff_print {
  613 	my $from = shift;
  614 	my $from_name = shift;
  615 	my $to = shift;
  616 	my $to_name = shift;
  617 	my $format = shift || "html";
  618 
  619 	my $from_tmp = "/dev/null";
  620 	my $to_tmp = "/dev/null";
  621 	my $pid = $$;
  622 
  623 	# create tmp from-file
  624 	if (defined $from) {
  625 		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";
  626 		open my $fd2, "> $from_tmp";
  627 		open my $fd, "-|", "$GIT cat-file blob $from";
  628 		my @file = <$fd>;
  629 		print $fd2 @file;
  630 		close $fd2;
  631 		close $fd;
  632 	}
  633 
  634 	# create tmp to-file
  635 	if (defined $to) {
  636 		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";
  637 		open my $fd2, "> $to_tmp";
  638 		open my $fd, "-|", "$GIT cat-file blob $to";
  639 		my @file = <$fd>;
  640 		print $fd2 @file;
  641 		close $fd2;
  642 		close $fd;
  643 	}
  644 
  645 	open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp";
  646 	if ($format eq "plain") {
  647 		undef $/;
  648 		print <$fd>;
  649 		$/ = "\n";
  650 	} else {
  651 		while (my $line = <$fd>) {
  652 			chomp($line);
  653 			my $char = substr($line, 0, 1);
  654 			my $diff_class = "";
  655 			if ($char eq '+') {
  656 				$diff_class = " add";
  657 			} elsif ($char eq "-") {
  658 				$diff_class = " rem";
  659 			} elsif ($char eq "@") {
  660 				$diff_class = " chunk_header";
  661 			} elsif ($char eq "\\") {
  662 				# skip errors
  663 				next;
  664 			}
  665 			while ((my $pos = index($line, "\t")) != -1) {
  666 				if (my $count = (8 - (($pos-1) % 8))) {
  667 					my $spaces = ' ' x $count;
  668 					$line =~ s/\t/$spaces/;
  669 				}
  670 			}
  671 			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
  672 		}
  673 	}
  674 	close $fd;
  675 
  676 	if (defined $from) {
  677 		unlink($from_tmp);
  678 	}
  679 	if (defined $to) {
  680 		unlink($to_tmp);
  681 	}
  682 }
  683 
  684 sub mode_str {
  685 	my $mode = oct shift;
  686 
  687 	if (S_ISDIR($mode & S_IFMT)) {
  688 		return 'drwxr-xr-x';
  689 	} elsif (S_ISLNK($mode)) {
  690 		return 'lrwxrwxrwx';
  691 	} elsif (S_ISREG($mode)) {
  692 		# git cares only about the executable bit
  693 		if ($mode & S_IXUSR) {
  694 			return '-rwxr-xr-x';
  695 		} else {
  696 			return '-rw-r--r--';
  697 		};
  698 	} else {
  699 		return '----------';
  700 	}
  701 }
  702 
  703 sub chop_str {
  704 	my $str = shift;
  705 	my $len = shift;
  706 	my $add_len = shift || 10;
  707 
  708 	# allow only $len chars, but don't cut a word if it would fit in $add_len
  709 	# if it doesn't fit, cut it if it's still longer than the dots we would add
  710 	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
  711 	my $body = $1;
  712 	my $tail = $2;
  713 	if (length($tail) > 4) {
  714 		$tail = " ...";
  715 	}
  716 	return "$body$tail";
  717 }
  718 
  719 sub file_type {
  720 	my $mode = oct shift;
  721 
  722 	if (S_ISDIR($mode & S_IFMT)) {
  723 		return "directory";
  724 	} elsif (S_ISLNK($mode)) {
  725 		return "symlink";
  726 	} elsif (S_ISREG($mode)) {
  727 		return "file";
  728 	} else {
  729 		return "unknown";
  730 	}
  731 }
  732 
  733 sub format_log_line_html {
  734 	my $line = shift;
  735 
  736 	$line = esc_html($line);
  737 	$line =~ s/ /&nbsp;/g;
  738 	if ($line =~ m/([0-9a-fA-F]{40})/) {
  739 		my $hash_text = $1;
  740 		if (git_get_type($hash_text) eq "commit") {
  741 			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
  742 			$line =~ s/$hash_text/$link/;
  743 		}
  744 	}
  745 	return $line;
  746 }
  747 
  748 sub date_str {
  749 	my $epoch = shift;
  750 	my $tz = shift || "-0000";
  751 
  752 	my %date;
  753 	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
  754 	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
  755 	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
  756 	$date{'hour'} = $hour;
  757 	$date{'minute'} = $min;
  758 	$date{'mday'} = $mday;
  759 	$date{'day'} = $days[$wday];
  760 	$date{'month'} = $months[$mon];
  761 	$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
  762 	$date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;
  763 
  764 	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
  765 	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
  766 	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
  767 	$date{'hour_local'} = $hour;
  768 	$date{'minute_local'} = $min;
  769 	$date{'tz_local'} = $tz;
  770 	return %date;
  771 }
  772 
  773 # git-logo (cached in browser for one day)
  774 sub git_logo {
  775 	binmode STDOUT, ':raw';
  776 	print $cgi->header(-type => 'image/png', -expires => '+1d');
  777 	# cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g'
  778 	print	"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
  779 		"\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
  780 		"\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
  781 		"\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
  782 		"\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
  783 		"\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
  784 		"\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
  785 		"\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
  786 		"\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
  787 		"\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
  788 		"\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
  789 		"\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
  790 		"\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
  791 }
  792 
  793 sub get_file_owner {
  794 	my $path = shift;
  795 
  796 	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
  797 	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
  798 	if (!defined $gcos) {
  799 		return undef;
  800 	}
  801 	my $owner = $gcos;
  802 	$owner =~ s/[,;].*$//;
  803 	return decode("utf8", $owner, Encode::FB_DEFAULT);
  804 }
  805 
  806 sub git_read_projects {
  807 	my @list;
  808 
  809 	if (-d $projects_list) {
  810 		# search in directory
  811 		my $dir = $projects_list;
  812 		opendir my ($dh), $dir or return undef;
  813 		while (my $dir = readdir($dh)) {
  814 			if (-e "$projectroot/$dir/HEAD") {
  815 				my $pr = {
  816 					path => $dir,
  817 				};
  818 				push @list, $pr
  819 			}
  820 		}
  821 		closedir($dh);
  822 	} elsif (-f $projects_list) {
  823 		# read from file(url-encoded):
  824 		# 'git%2Fgit.git Linus+Torvalds'
  825 		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
  826 		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
  827 		open my ($fd), $projects_list or return undef;
  828 		while (my $line = <$fd>) {
  829 			chomp $line;
  830 			my ($path, $owner) = split ' ', $line;
  831 			$path = unescape($path);
  832 			$owner = unescape($owner);
  833 			if (!defined $path) {
  834 				next;
  835 			}
  836 			if (-e "$projectroot/$path/HEAD") {
  837 				my $pr = {
  838 					path => $path,
  839 					owner => decode("utf8", $owner, Encode::FB_DEFAULT),
  840 				};
  841 				push @list, $pr
  842 			}
  843 		}
  844 		close $fd;
  845 	}
  846 	@list = sort {$a->{'path'} cmp $b->{'path'}} @list;
  847 	return @list;
  848 }
  849 
  850 sub git_get_project_config {
  851 	my $key = shift;
  852 
  853 	return unless ($key);
  854 	$key =~ s/^gitweb\.//;
  855 	return if ($key =~ m/\W/);
  856 
  857 	my $val = qx($GIT repo-config --get gitweb.$key);
  858 	return ($val);
  859 }
  860 
  861 sub git_get_project_config_bool {
  862 	my $val = git_get_project_config (@_);
  863 	if ($val and $val =~ m/true|yes|on/) {
  864 		return (1);
  865 	}
  866 	return; # implicit false
  867 }
  868 
  869 sub git_project_list {
  870 	my @list = git_read_projects();
  871 	my @projects;
  872 	if (!@list) {
  873 		die_error(undef, "No project found.");
  874 	}
  875 	foreach my $pr (@list) {
  876 		my $head = git_read_head($pr->{'path'});
  877 		if (!defined $head) {
  878 			next;
  879 		}
  880 		$ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
  881 		my %co = git_read_commit($head);
  882 		if (!%co) {
  883 			next;
  884 		}
  885 		$pr->{'commit'} = \%co;
  886 		if (!defined $pr->{'descr'}) {
  887 			my $descr = git_read_description($pr->{'path'}) || "";
  888 			$pr->{'descr'} = chop_str($descr, 25, 5);
  889 		}
  890 		if (!defined $pr->{'owner'}) {
  891 			$pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
  892 		}
  893 		push @projects, $pr;
  894 	}
  895 	git_header_html();
  896 	if (-f $home_text) {
  897 		print "<div class=\"index_include\">\n";
  898 		open (my $fd, $home_text);
  899 		print <$fd>;
  900 		close $fd;
  901 		print "</div>\n";
  902 	}
  903 	print "<table class=\"project_list\">\n" .
  904 	      "<tr>\n";
  905 	if (!defined($order) || (defined($order) && ($order eq "project"))) {
  906 		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
  907 		print "<th>Project</th>\n";
  908 	} else {
  909 		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n";
  910 	}
  911 	if (defined($order) && ($order eq "descr")) {
  912 		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
  913 		print "<th>Description</th>\n";
  914 	} else {
  915 		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n";
  916 	}
  917 	if (defined($order) && ($order eq "owner")) {
  918 		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
  919 		print "<th>Owner</th>\n";
  920 	} else {
  921 		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n";
  922 	}
  923 	if (defined($order) && ($order eq "age")) {
  924 		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
  925 		print "<th>Last Change</th>\n";
  926 	} else {
  927 		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n";
  928 	}
  929 	print "<th></th>\n" .
  930 	      "</tr>\n";
  931 	my $alternate = 0;
  932 	foreach my $pr (@projects) {
  933 		if ($alternate) {
  934 			print "<tr class=\"dark\">\n";
  935 		} else {
  936 			print "<tr class=\"light\">\n";
  937 		}
  938 		$alternate ^= 1;
  939 		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
  940 		      "<td>$pr->{'descr'}</td>\n" .
  941 		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
  942 		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" .
  943 		      "<td class=\"link\">" .
  944 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") .
  945 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") .
  946 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .
  947 		      "</td>\n" .
  948 		      "</tr>\n";
  949 	}
  950 	print "</table>\n";
  951 	git_footer_html();
  952 }
  953 
  954 sub read_info_ref {
  955 	my $type = shift || "";
  956 	my %refs;
  957 	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11
  958 	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{}
  959 	open my $fd, "$projectroot/$project/info/refs" or return;
  960 	while (my $line = <$fd>) {
  961 		chomp($line);
  962 		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
  963 			if (defined $refs{$1}) {
  964 				$refs{$1} .= " / $2";
  965 			} else {
  966 				$refs{$1} = $2;
  967 			}
  968 		}
  969 	}
  970 	close $fd or return;
  971 	return \%refs;
  972 }
  973 
  974 sub git_read_refs {
  975 	my $ref_dir = shift;
  976 	my @reflist;
  977 
  978 	my @refs;
  979 	opendir my $dh, "$projectroot/$project/$ref_dir";
  980 	while (my $dir = readdir($dh)) {
  981 		if ($dir =~ m/^\./) {
  982 			next;
  983 		}
  984 		if (-d "$projectroot/$project/$ref_dir/$dir") {
  985 			opendir my $dh2, "$projectroot/$project/$ref_dir/$dir";
  986 			my @subdirs = grep !m/^\./, readdir $dh2;
  987 			closedir($dh2);
  988 			foreach my $subdir (@subdirs) {
  989 				push @refs, "$dir/$subdir"
  990 			}
  991 			next;
  992 		}
  993 		push @refs, $dir;
  994 	}
  995 	closedir($dh);
  996 	foreach my $ref_file (@refs) {
  997 		my $ref_id = git_read_hash("$project/$ref_dir/$ref_file");
  998 		my $type = git_get_type($ref_id) || next;
  999 		my %ref_item;
 1000 		my %co;
 1001 		$ref_item{'type'} = $type;
 1002 		$ref_item{'id'} = $ref_id;
 1003 		$ref_item{'epoch'} = 0;
 1004 		$ref_item{'age'} = "unknown";
 1005 		if ($type eq "tag") {
 1006 			my %tag = git_read_tag($ref_id);
 1007 			$ref_item{'comment'} = $tag{'comment'};
 1008 			if ($tag{'type'} eq "commit") {
 1009 				%co = git_read_commit($tag{'object'});
 1010 				$ref_item{'epoch'} = $co{'committer_epoch'};
 1011 				$ref_item{'age'} = $co{'age_string'};
 1012 			} elsif (defined($tag{'epoch'})) {
 1013 				my $age = time - $tag{'epoch'};
 1014 				$ref_item{'epoch'} = $tag{'epoch'};
 1015 				$ref_item{'age'} = age_string($age);
 1016 			}
 1017 			$ref_item{'reftype'} = $tag{'type'};
 1018 			$ref_item{'name'} = $tag{'name'};
 1019 			$ref_item{'refid'} = $tag{'object'};
 1020 		} elsif ($type eq "commit"){
 1021 			%co = git_read_commit($ref_id);
 1022 			$ref_item{'reftype'} = "commit";
 1023 			$ref_item{'name'} = $ref_file;
 1024 			$ref_item{'title'} = $co{'title'};
 1025 			$ref_item{'refid'} = $ref_id;
 1026 			$ref_item{'epoch'} = $co{'committer_epoch'};
 1027 			$ref_item{'age'} = $co{'age_string'};
 1028 		}
 1029 
 1030 		push @reflist, \%ref_item;
 1031 	}
 1032 	# sort tags by age
 1033 	@reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
 1034 	return \@reflist;
 1035 }
 1036 
 1037 sub git_summary {
 1038 	my $descr = git_read_description($project) || "none";
 1039 	my $head = git_read_head($project);
 1040 	my %co = git_read_commit($head);
 1041 	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
 1042 
 1043 	my $owner;
 1044 	if (-f $projects_list) {
 1045 		open (my $fd , $projects_list);
 1046 		while (my $line = <$fd>) {
 1047 			chomp $line;
 1048 			my ($pr, $ow) = split ' ', $line;
 1049 			$pr = unescape($pr);
 1050 			$ow = unescape($ow);
 1051 			if ($pr eq $project) {
 1052 				$owner = decode("utf8", $ow, Encode::FB_DEFAULT);
 1053 				last;
 1054 			}
 1055 		}
 1056 		close $fd;
 1057 	}
 1058 	if (!defined $owner) {
 1059 		$owner = get_file_owner("$projectroot/$project");
 1060 	}
 1061 
 1062 	my $refs = read_info_ref();
 1063 	git_header_html();
 1064 	print "<div class=\"page_nav\">\n" .
 1065 	      "summary".
 1066 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 1067 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 1068 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
 1069 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
 1070 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree")}, "tree") .
 1071 	      "<br/><br/>\n" .
 1072 	      "</div>\n";
 1073 	print "<div class=\"title\">&nbsp;</div>\n";
 1074 	print "<table cellspacing=\"0\">\n" .
 1075 	      "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
 1076 	      "<tr><td>owner</td><td>$owner</td></tr>\n" .
 1077 	      "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
 1078 	      "</table>\n";
 1079 	open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed.");
 1080 	my (@revlist) = map { chomp; $_ } <$fd>;
 1081 	close $fd;
 1082 	print "<div>\n" .
 1083 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") .
 1084 	      "</div>\n";
 1085 	my $i = 16;
 1086 	print "<table cellspacing=\"0\">\n";
 1087 	my $alternate = 0;
 1088 	foreach my $commit (@revlist) {
 1089 		my %co = git_read_commit($commit);
 1090 		my %ad = date_str($co{'author_epoch'});
 1091 		if ($alternate) {
 1092 			print "<tr class=\"dark\">\n";
 1093 		} else {
 1094 			print "<tr class=\"light\">\n";
 1095 		}
 1096 		$alternate ^= 1;
 1097 		if ($i-- > 0) {
 1098 			my $ref = "";
 1099 			if (defined $refs->{$commit}) {
 1100 				$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
 1101 			}
 1102 			print "<td><i>$co{'age_string'}</i></td>\n" .
 1103 			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
 1104 			      "<td>";
 1105 			if (length($co{'title_short'}) < length($co{'title'})) {
 1106 				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"},
 1107 				      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
 1108 			} else {
 1109 				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"},
 1110 				      "<b>" . esc_html($co{'title'}) . "$ref</b>");
 1111 			}
 1112 			print "</td>\n" .
 1113 			      "<td class=\"link\">" .
 1114 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
 1115 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
 1116 			      "</td>\n" .
 1117 			      "</tr>";
 1118 		} else {
 1119 			print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...") . "</td>\n" .
 1120 			"</tr>";
 1121 			last;
 1122 		}
 1123 	}
 1124 	print "</table\n>";
 1125 
 1126 	my $taglist = git_read_refs("refs/tags");
 1127 	if (defined @$taglist) {
 1128 		print "<div>\n" .
 1129 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags"), -class => "title"}, "tags") .
 1130 		      "</div>\n";
 1131 		my $i = 16;
 1132 		print "<table cellspacing=\"0\">\n";
 1133 		my $alternate = 0;
 1134 		foreach my $entry (@$taglist) {
 1135 			my %tag = %$entry;
 1136 			my $comment_lines = $tag{'comment'};
 1137 			my $comment = shift @$comment_lines;
 1138 			if (defined($comment)) {
 1139 				$comment = chop_str($comment, 30, 5);
 1140 			}
 1141 			if ($alternate) {
 1142 				print "<tr class=\"dark\">\n";
 1143 			} else {
 1144 				print "<tr class=\"light\">\n";
 1145 			}
 1146 			$alternate ^= 1;
 1147 			if ($i-- > 0) {
 1148 				print "<td><i>$tag{'age'}</i></td>\n" .
 1149 				      "<td>" .
 1150 				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"},
 1151 				      "<b>" . esc_html($tag{'name'}) . "</b>") .
 1152 				      "</td>\n" .
 1153 				      "<td>";
 1154 				if (defined($comment)) {
 1155 				      print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment));
 1156 				}
 1157 				print "</td>\n" .
 1158 				      "<td class=\"link\">";
 1159 				if ($tag{'type'} eq "tag") {
 1160 				      print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
 1161 				}
 1162 				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
 1163 				if ($tag{'reftype'} eq "commit") {
 1164 				      print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
 1165 					    " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
 1166 				}
 1167 				print "</td>\n" .
 1168 				      "</tr>";
 1169 			} else {
 1170 				print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...") . "</td>\n" .
 1171 				"</tr>";
 1172 				last;
 1173 			}
 1174 		}
 1175 		print "</table\n>";
 1176 	}
 1177 
 1178 	my $headlist = git_read_refs("refs/heads");
 1179 	if (defined @$headlist) {
 1180 		print "<div>\n" .
 1181 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads"), -class => "title"}, "heads") .
 1182 		      "</div>\n";
 1183 		my $i = 16;
 1184 		print "<table cellspacing=\"0\">\n";
 1185 		my $alternate = 0;
 1186 		foreach my $entry (@$headlist) {
 1187 			my %tag = %$entry;
 1188 			if ($alternate) {
 1189 				print "<tr class=\"dark\">\n";
 1190 			} else {
 1191 				print "<tr class=\"light\">\n";
 1192 			}
 1193 			$alternate ^= 1;
 1194 			if ($i-- > 0) {
 1195 				print "<td><i>$tag{'age'}</i></td>\n" .
 1196 				      "<td>" .
 1197 				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"},
 1198 				      "<b>" . esc_html($tag{'name'}) . "</b>") .
 1199 				      "</td>\n" .
 1200 				      "<td class=\"link\">" .
 1201 				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
 1202 				      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
 1203 				      "</td>\n" .
 1204 				      "</tr>";
 1205 			} else {
 1206 				print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...") . "</td>\n" .
 1207 				"</tr>";
 1208 				last;
 1209 			}
 1210 		}
 1211 		print "</table\n>";
 1212 	}
 1213 	git_footer_html();
 1214 }
 1215 
 1216 sub git_print_page_path {
 1217 	my $name = shift;
 1218 	my $type = shift;
 1219 
 1220 	if (!defined $name) {
 1221 		print "<div class=\"page_path\"><b>/</b></div>\n";
 1222 	} elsif ($type =~ "blob") {
 1223 		print "<div class=\"page_path\"><b>" .
 1224 			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n";
 1225 	} else {
 1226 		print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
 1227 	}
 1228 }
 1229 
 1230 sub git_tag {
 1231 	my $head = git_read_head($project);
 1232 	git_header_html();
 1233 	print "<div class=\"page_nav\">\n" .
 1234 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1235 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 1236 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 1237 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
 1238 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
 1239 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" .
 1240 	      "<br/>\n" .
 1241 	      "</div>\n";
 1242 	my %tag = git_read_tag($hash);
 1243 	print "<div>\n" .
 1244 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" .
 1245 	      "</div>\n";
 1246 	print "<div class=\"title_text\">\n" .
 1247 	      "<table cellspacing=\"0\">\n" .
 1248 	      "<tr>\n" .
 1249 	      "<td>object</td>\n" .
 1250 	      "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" .
 1251 	      "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" .
 1252 	      "</tr>\n";
 1253 	if (defined($tag{'author'})) {
 1254 		my %ad = date_str($tag{'epoch'}, $tag{'tz'});
 1255 		print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
 1256 		print "<tr><td></td><td>" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "</td></tr>\n";
 1257 	}
 1258 	print "</table>\n\n" .
 1259 	      "</div>\n";
 1260 	print "<div class=\"page_body\">";
 1261 	my $comment = $tag{'comment'};
 1262 	foreach my $line (@$comment) {
 1263 		print esc_html($line) . "<br/>\n";
 1264 	}
 1265 	print "</div>\n";
 1266 	git_footer_html();
 1267 }
 1268 
 1269 sub git_blame2 {
 1270 	my $fd;
 1271 	my $ftype;
 1272 	die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame'));
 1273 	die_error('404 Not Found', "File name not defined") if (!$file_name);
 1274 	$hash_base ||= git_read_head($project);
 1275 	die_error(undef, "Reading commit failed") unless ($hash_base);
 1276 	my %co = git_read_commit($hash_base)
 1277 		or die_error(undef, "Reading commit failed");
 1278 	if (!defined $hash) {
 1279 		$hash = git_get_hash_by_path($hash_base, $file_name, "blob")
 1280 			or die_error(undef, "Error looking up file");
 1281 	}
 1282 	$ftype = git_get_type($hash);
 1283 	if ($ftype !~ "blob") {
 1284 		die_error("400 Bad Request", "object is not a blob");
 1285 	}
 1286 	open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base)
 1287 		or die_error(undef, "Open failed");
 1288 	git_header_html();
 1289 	print "<div class=\"page_nav\">\n" .
 1290 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1291 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 1292 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 1293 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
 1294 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
 1295 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
 1296 	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
 1297 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n";
 1298 	print "</div>\n".
 1299 		"<div>" .
 1300 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
 1301 		"</div>\n";
 1302 	git_print_page_path($file_name, $ftype);
 1303 	my @rev_color = (qw(light dark));
 1304 	my $num_colors = scalar(@rev_color);
 1305 	my $current_color = 0;
 1306 	my $last_rev;
 1307 	print "<div class=\"page_body\">\n";
 1308 	print "<table class=\"blame\">\n";
 1309 	print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n";
 1310 	while (<$fd>) {
 1311 		/^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
 1312 		my $full_rev = $1;
 1313 		my $rev = substr($full_rev, 0, 8);
 1314 		my $lineno = $2;
 1315 		my $data = $3;
 1316 
 1317 		if (!defined $last_rev) {
 1318 			$last_rev = $full_rev;
 1319 		} elsif ($last_rev ne $full_rev) {
 1320 			$last_rev = $full_rev;
 1321 			$current_color = ++$current_color % $num_colors;
 1322 		}
 1323 		print "<tr class=\"$rev_color[$current_color]\">\n";
 1324 		print "<td class=\"sha1\">" .
 1325 			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n";
 1326 		print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n";
 1327 		print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
 1328 		print "</tr>\n";
 1329 	}
 1330 	print "</table>\n";
 1331 	print "</div>";
 1332 	close $fd or print "Reading blob failed\n";
 1333 	git_footer_html();
 1334 }
 1335 
 1336 sub git_blame {
 1337 	my $fd;
 1338 	die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame'));
 1339 	die_error('404 Not Found', "What file will it be, master?") if (!$file_name);
 1340 	$hash_base ||= git_read_head($project);
 1341 	die_error(undef, "Reading commit failed.") unless ($hash_base);
 1342 	my %co = git_read_commit($hash_base)
 1343 		or die_error(undef, "Reading commit failed.");
 1344 	if (!defined $hash) {
 1345 		$hash = git_get_hash_by_path($hash_base, $file_name, "blob")
 1346 			or die_error(undef, "Error lookup file.");
 1347 	}
 1348 	open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
 1349 		or die_error(undef, "Open failed.");
 1350 	git_header_html();
 1351 	print "<div class=\"page_nav\">\n" .
 1352 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1353 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 1354 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 1355 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
 1356 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
 1357 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
 1358 	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
 1359 		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n";
 1360 	print "</div>\n".
 1361 		"<div>" .
 1362 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
 1363 		"</div>\n";
 1364 	git_print_page_path($file_name);
 1365 	print "<div class=\"page_body\">\n";
 1366 	print <<HTML;
 1367 <table class="blame">
 1368   <tr>
 1369     <th>Commit</th>
 1370     <th>Age</th>
 1371     <th>Author</th>
 1372     <th>Line</th>
 1373     <th>Data</th>
 1374   </tr>
 1375 HTML
 1376 	my @line_class = (qw(light dark));
 1377 	my $line_class_len = scalar (@line_class);
 1378 	my $line_class_num = $#line_class;
 1379 	while (my $line = <$fd>) {
 1380 		my $long_rev;
 1381 		my $short_rev;
 1382 		my $author;
 1383 		my $time;
 1384 		my $lineno;
 1385 		my $data;
 1386 		my $age;
 1387 		my $age_str;
 1388 		my $age_class;
 1389 
 1390 		chomp $line;
 1391 		$line_class_num = ($line_class_num + 1) % $line_class_len;
 1392 
 1393 		if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) {
 1394 			$long_rev = $1;
 1395 			$author   = $2;
 1396 			$time     = $3;
 1397 			$lineno   = $4;
 1398 			$data     = $5;
 1399 		} else {
 1400 			print qq(  <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
 1401 			next;
 1402 		}
 1403 		$short_rev  = substr ($long_rev, 0, 8);
 1404 		$age        = time () - $time;
 1405 		$age_str    = age_string ($age);
 1406 		$age_str    =~ s/ /&nbsp;/g;
 1407 		$age_class  = age_class($age);
 1408 		$author     = esc_html ($author);
 1409 		$author     =~ s/ /&nbsp;/g;
 1410 		# escape tabs
 1411 		while ((my $pos = index($data, "\t")) != -1) {
 1412 			if (my $count = (8 - ($pos % 8))) {
 1413 				my $spaces = ' ' x $count;
 1414 				$data =~ s/\t/$spaces/;
 1415 			}
 1416 		}
 1417 		$data = esc_html ($data);
 1418 
 1419 		print <<HTML;
 1420   <tr class="$line_class[$line_class_num]">
 1421     <td class="sha1"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td>
 1422     <td class="$age_class">$age_str</td>
 1423     <td>$author</td>
 1424     <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
 1425     <td class="pre">$data</td>
 1426   </tr>
 1427 HTML
 1428 	} # while (my $line = <$fd>)
 1429 	print "</table>\n\n";
 1430 	close $fd or print "Reading blob failed.\n";
 1431 	print "</div>";
 1432 	git_footer_html();
 1433 }
 1434 
 1435 sub git_tags {
 1436 	my $head = git_read_head($project);
 1437 	git_header_html();
 1438 	print "<div class=\"page_nav\">\n" .
 1439 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1440 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 1441 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 1442 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
 1443 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
 1444 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" .
 1445 	      "<br/>\n" .
 1446 	      "</div>\n";
 1447 	my $taglist = git_read_refs("refs/tags");
 1448 	print "<div>\n" .
 1449 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
 1450 	      "</div>\n";
 1451 	print "<table cellspacing=\"0\">\n";
 1452 	my $alternate = 0;
 1453 	if (defined @$taglist) {
 1454 		foreach my $entry (@$taglist) {
 1455 			my %tag = %$entry;
 1456 			my $comment_lines = $tag{'comment'};
 1457 			my $comment = shift @$comment_lines;
 1458 			if (defined($comment)) {
 1459 				$comment = chop_str($comment, 30, 5);
 1460 			}
 1461 			if ($alternate) {
 1462 				print "<tr class=\"dark\">\n";
 1463 			} else {
 1464 				print "<tr class=\"light\">\n";
 1465 			}
 1466 			$alternate ^= 1;
 1467 			print "<td><i>$tag{'age'}</i></td>\n" .
 1468 			      "<td>" .
 1469 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"},
 1470 			      "<b>" . esc_html($tag{'name'}) . "</b>") .
 1471 			      "</td>\n" .
 1472 			      "<td>";
 1473 			if (defined($comment)) {
 1474 			      print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment);
 1475 			}
 1476 			print "</td>\n" .
 1477 			      "<td class=\"link\">";
 1478 			if ($tag{'type'} eq "tag") {
 1479 			      print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | ";
 1480 			}
 1481 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
 1482 			if ($tag{'reftype'} eq "commit") {
 1483 			      print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
 1484 				    " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
 1485 			}
 1486 			print "</td>\n" .
 1487 			      "</tr>";
 1488 		}
 1489 	}
 1490 	print "</table\n>";
 1491 	git_footer_html();
 1492 }
 1493 
 1494 sub git_heads {
 1495 	my $head = git_read_head($project);
 1496 	git_header_html();
 1497 	print "<div class=\"page_nav\">\n" .
 1498 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1499 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 1500 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 1501 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") .
 1502 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") .
 1503 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" .
 1504 	      "<br/>\n" .
 1505 	      "</div>\n";
 1506 	my $taglist = git_read_refs("refs/heads");
 1507 	print "<div>\n" .
 1508 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
 1509 	      "</div>\n";
 1510 	print "<table cellspacing=\"0\">\n";
 1511 	my $alternate = 0;
 1512 	if (defined @$taglist) {
 1513 		foreach my $entry (@$taglist) {
 1514 			my %tag = %$entry;
 1515 			if ($alternate) {
 1516 				print "<tr class=\"dark\">\n";
 1517 			} else {
 1518 				print "<tr class=\"light\">\n";
 1519 			}
 1520 			$alternate ^= 1;
 1521 			print "<td><i>$tag{'age'}</i></td>\n" .
 1522 			      "<td>" .
 1523 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
 1524 			      "</td>\n" .
 1525 			      "<td class=\"link\">" .
 1526 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
 1527 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
 1528 			      "</td>\n" .
 1529 			      "</tr>";
 1530 		}
 1531 	}
 1532 	print "</table\n>";
 1533 	git_footer_html();
 1534 }
 1535 
 1536 sub git_get_hash_by_path {
 1537 	my $base = shift;
 1538 	my $path = shift || return undef;
 1539 
 1540 	my $tree = $base;
 1541 	my @parts = split '/', $path;
 1542 	while (my $part = shift @parts) {
 1543 		open my $fd, "-|", "$GIT ls-tree $tree" or die_error(undef, "Open git-ls-tree failed.");
 1544 		my (@entries) = map { chomp; $_ } <$fd>;
 1545 		close $fd or return undef;
 1546 		foreach my $line (@entries) {
 1547 			#'100644	blob	0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
 1548 			$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
 1549 			my $t_mode = $1;
 1550 			my $t_type = $2;
 1551 			my $t_hash = $3;
 1552 			my $t_name = validate_input(unquote($4));
 1553 			if ($t_name eq $part) {
 1554 				if (!(@parts)) {
 1555 					return $t_hash;
 1556 				}
 1557 				if ($t_type eq "tree") {
 1558 					$tree = $t_hash;
 1559 				}
 1560 				last;
 1561 			}
 1562 		}
 1563 	}
 1564 }
 1565 
 1566 sub mimetype_guess_file {
 1567 	my $filename = shift;
 1568 	my $mimemap = shift;
 1569 	-r $mimemap or return undef;
 1570 
 1571 	my %mimemap;
 1572 	open(MIME, $mimemap) or return undef;
 1573 	while (<MIME>) {
 1574 		my ($mime, $exts) = split(/\t+/);
 1575 		my @exts = split(/\s+/, $exts);
 1576 		foreach my $ext (@exts) {
 1577 			$mimemap{$ext} = $mime;
 1578 		}
 1579 	}
 1580 	close(MIME);
 1581 
 1582 	$filename =~ /\.(.*?)$/;
 1583 	return $mimemap{$1};
 1584 }
 1585 
 1586 sub mimetype_guess {
 1587 	my $filename = shift;
 1588 	my $mime;
 1589 	$filename =~ /\./ or return undef;
 1590 
 1591 	if ($mimetypes_file) {
 1592 		my $file = $mimetypes_file;
 1593 		#$file =~ m#^/# or $file = "$projectroot/$path/$file";
 1594 		$mime = mimetype_guess_file($filename, $file);
 1595 	}
 1596 	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
 1597 	return $mime;
 1598 }
 1599 
 1600 sub git_blob_plain_mimetype {
 1601 	my $fd = shift;
 1602 	my $filename = shift;
 1603 
 1604 	if ($filename) {
 1605 		my $mime = mimetype_guess($filename);
 1606 		$mime and return $mime;
 1607 	}
 1608 
 1609 	# just in case
 1610 	return $default_blob_plain_mimetype unless $fd;
 1611 
 1612 	if (-T $fd) {
 1613 		return 'text/plain' .
 1614 		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
 1615 	} elsif (! $filename) {
 1616 		return 'application/octet-stream';
 1617 	} elsif ($filename =~ m/\.png$/i) {
 1618 		return 'image/png';
 1619 	} elsif ($filename =~ m/\.gif$/i) {
 1620 		return 'image/gif';
 1621 	} elsif ($filename =~ m/\.jpe?g$/i) {
 1622 		return 'image/jpeg';
 1623 	} else {
 1624 		return 'application/octet-stream';
 1625 	}
 1626 }
 1627 
 1628 sub git_blob_plain {
 1629 	if (!defined $hash) {
 1630                 if (defined $file_name) {
 1631                         my $base = $hash_base || git_read_head($project);
 1632                         $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file.");
 1633                 } else {
 1634                         die_error(undef, "No file name defined.");
 1635                 }
 1636         }
 1637 	my $type = shift;
 1638 	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash");
 1639 
 1640 	$type ||= git_blob_plain_mimetype($fd, $file_name);
 1641 
 1642 	# save as filename, even when no $file_name is given
 1643 	my $save_as = "$hash";
 1644 	if (defined $file_name) {
 1645 		$save_as = $file_name;
 1646 	} elsif ($type =~ m/^text\//) {
 1647 		$save_as .= '.txt';
 1648 	}
 1649 
 1650 	print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\"");
 1651 	undef $/;
 1652 	binmode STDOUT, ':raw';
 1653 	print <$fd>;
 1654 	binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
 1655 	$/ = "\n";
 1656 	close $fd;
 1657 }
 1658 
 1659 sub git_blob {
 1660 	if (!defined $hash) {
 1661                 if (defined $file_name) {
 1662                         my $base = $hash_base || git_read_head($project);
 1663                         $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file.");
 1664                 } else {
 1665                         die_error(undef, "No file name defined.");
 1666                 }
 1667         }
 1668 	my $have_blame = git_get_project_config_bool ('blame');
 1669 	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed.");
 1670 	my $mimetype = git_blob_plain_mimetype($fd, $file_name);
 1671 	if ($mimetype !~ m/^text\//) {
 1672 		close $fd;
 1673 		return git_blob_plain($mimetype);
 1674 	}
 1675 	git_header_html();
 1676 	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
 1677 		print "<div class=\"page_nav\">\n" .
 1678 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1679 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 1680 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 1681 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
 1682 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
 1683 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
 1684 		if (defined $file_name) {
 1685 			if ($have_blame) {
 1686 				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") .  " | ";
 1687 			}
 1688 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
 1689 			" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "<br/>\n";
 1690 		} else {
 1691 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n";
 1692 		}
 1693 		print "</div>\n".
 1694 		       "<div>" .
 1695 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
 1696 		      "</div>\n";
 1697 	} else {
 1698 		print "<div class=\"page_nav\">\n" .
 1699 		      "<br/><br/></div>\n" .
 1700 		      "<div class=\"title\">$hash</div>\n";
 1701 	}
 1702 	git_print_page_path($file_name, "blob");
 1703 	print "<div class=\"page_body\">\n";
 1704 	my $nr;
 1705 	while (my $line = <$fd>) {
 1706 		chomp $line;
 1707 		$nr++;
 1708 		while ((my $pos = index($line, "\t")) != -1) {
 1709 			if (my $count = (8 - ($pos % 8))) {
 1710 				my $spaces = ' ' x $count;
 1711 				$line =~ s/\t/$spaces/;
 1712 			}
 1713 		}
 1714 		printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line);
 1715 	}
 1716 	close $fd or print "Reading blob failed.\n";
 1717 	print "</div>";
 1718 	git_footer_html();
 1719 }
 1720 
 1721 sub git_tree {
 1722 	if (!defined $hash) {
 1723 		$hash = git_read_head($project);
 1724 		if (defined $file_name) {
 1725 			my $base = $hash_base || $hash;
 1726 			$hash = git_get_hash_by_path($base, $file_name, "tree");
 1727 		}
 1728 		if (!defined $hash_base) {
 1729 			$hash_base = $hash;
 1730 		}
 1731 	}
 1732 	$/ = "\0";
 1733 	open my $fd, "-|", "$GIT ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed.");
 1734 	chomp (my (@entries) = <$fd>);
 1735 	close $fd or die_error(undef, "Reading tree failed.");
 1736 	$/ = "\n";
 1737 
 1738 	my $refs = read_info_ref();
 1739 	my $ref = "";
 1740 	if (defined $refs->{$hash_base}) {
 1741 		$ref = " <span class=\"tag\">" . esc_html($refs->{$hash_base}) . "</span>";
 1742 	}
 1743 	git_header_html();
 1744 	my $base_key = "";
 1745 	my $base = "";
 1746 	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
 1747 		$base_key = ";hb=$hash_base";
 1748 		print "<div class=\"page_nav\">\n" .
 1749 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1750 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash_base")}, "shortlog") .
 1751 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash_base")}, "log") .
 1752 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
 1753 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
 1754 		      " | tree" .
 1755 		      "<br/><br/>\n" .
 1756 		      "</div>\n";
 1757 		print "<div>\n" .
 1758 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
 1759 		      "</div>\n";
 1760 	} else {
 1761 		print "<div class=\"page_nav\">\n";
 1762 		print "<br/><br/></div>\n";
 1763 		print "<div class=\"title\">$hash</div>\n";
 1764 	}
 1765 	if (defined $file_name) {
 1766 		$base = esc_html("$file_name/");
 1767 	}
 1768 	git_print_page_path($file_name);
 1769 	print "<div class=\"page_body\">\n";
 1770 	print "<table cellspacing=\"0\">\n";
 1771 	my $alternate = 0;
 1772 	foreach my $line (@entries) {
 1773 		#'100644	blob	0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
 1774 		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
 1775 		my $t_mode = $1;
 1776 		my $t_type = $2;
 1777 		my $t_hash = $3;
 1778 		my $t_name = validate_input($4);
 1779 		if ($alternate) {
 1780 			print "<tr class=\"dark\">\n";
 1781 		} else {
 1782 			print "<tr class=\"light\">\n";
 1783 		}
 1784 		$alternate ^= 1;
 1785 		print "<td class=\"mode\">" . mode_str($t_mode) . "</td>\n";
 1786 		if ($t_type eq "blob") {
 1787 			print "<td class=\"list\">" .
 1788 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) .
 1789 			      "</td>\n" .
 1790 			      "<td class=\"link\">" .
 1791 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") .
 1792 #			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") .
 1793 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") .
 1794 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") .
 1795 			      "</td>\n";
 1796 		} elsif ($t_type eq "tree") {
 1797 			print "<td class=\"list\">" .
 1798 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) .
 1799 			      "</td>\n" .
 1800 			      "<td class=\"link\">" .
 1801 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") .
 1802 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") .
 1803 			      "</td>\n";
 1804 		}
 1805 		print "</tr>\n";
 1806 	}
 1807 	print "</table>\n" .
 1808 	      "</div>";
 1809 	git_footer_html();
 1810 }
 1811 
 1812 sub git_rss {
 1813 	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
 1814 	open my $fd, "-|", "$GIT rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed.");
 1815 	my (@revlist) = map { chomp; $_ } <$fd>;
 1816 	close $fd or die_error(undef, "Reading rev-list failed.");
 1817 	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
 1818 	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
 1819 	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
 1820 	print "<channel>\n";
 1821 	print "<title>$project</title>\n".
 1822 	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
 1823 	      "<description>$project log</description>\n".
 1824 	      "<language>en</language>\n";
 1825 
 1826 	for (my $i = 0; $i <= $#revlist; $i++) {
 1827 		my $commit = $revlist[$i];
 1828 		my %co = git_read_commit($commit);
 1829 		# we read 150, we always show 30 and the ones more recent than 48 hours
 1830 		if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) {
 1831 			last;
 1832 		}
 1833 		my %cd = date_str($co{'committer_epoch'});
 1834 		open $fd, "-|", "$GIT diff-tree -r $co{'parent'} $co{'id'}" or next;
 1835 		my @difftree = map { chomp; $_ } <$fd>;
 1836 		close $fd or next;
 1837 		print "<item>\n" .
 1838 		      "<title>" .
 1839 		      sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
 1840 		      "</title>\n" .
 1841 		      "<author>" . esc_html($co{'author'}) . "</author>\n" .
 1842 		      "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
 1843 		      "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" .
 1844 		      "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" .
 1845 		      "<description>" . esc_html($co{'title'}) . "</description>\n" .
 1846 		      "<content:encoded>" .
 1847 		      "<![CDATA[\n";
 1848 		my $comment = $co{'comment'};
 1849 		foreach my $line (@$comment) {
 1850 			$line = decode("utf8", $line, Encode::FB_DEFAULT);
 1851 			print "$line<br/>\n";
 1852 		}
 1853 		print "<br/>\n";
 1854 		foreach my $line (@difftree) {
 1855 			if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
 1856 				next;
 1857 			}
 1858 			my $file = validate_input(unquote($7));
 1859 			$file = decode("utf8", $file, Encode::FB_DEFAULT);
 1860 			print "$file<br/>\n";
 1861 		}
 1862 		print "]]>\n" .
 1863 		      "</content:encoded>\n" .
 1864 		      "</item>\n";
 1865 	}
 1866 	print "</channel></rss>";
 1867 }
 1868 
 1869 sub git_opml {
 1870 	my @list = git_read_projects();
 1871 
 1872 	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
 1873 	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
 1874 	      "<opml version=\"1.0\">\n".
 1875 	      "<head>".
 1876 	      "  <title>$site_name Git OPML Export</title>\n".
 1877 	      "</head>\n".
 1878 	      "<body>\n".
 1879 	      "<outline text=\"git RSS feeds\">\n";
 1880 
 1881 	foreach my $pr (@list) {
 1882 		my %proj = %$pr;
 1883 		my $head = git_read_head($proj{'path'});
 1884 		if (!defined $head) {
 1885 			next;
 1886 		}
 1887 		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
 1888 		my %co = git_read_commit($head);
 1889 		if (!%co) {
 1890 			next;
 1891 		}
 1892 
 1893 		my $path = esc_html(chop_str($proj{'path'}, 25, 5));
 1894 		my $rss  = "$my_url?p=$proj{'path'};a=rss";
 1895 		my $html = "$my_url?p=$proj{'path'};a=summary";
 1896 		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
 1897 	}
 1898 	print "</outline>\n".
 1899 	      "</body>\n".
 1900 	      "</opml>\n";
 1901 }
 1902 
 1903 sub git_log {
 1904 	my $head = git_read_head($project);
 1905 	if (!defined $hash) {
 1906 		$hash = $head;
 1907 	}
 1908 	if (!defined $page) {
 1909 		$page = 0;
 1910 	}
 1911 	my $refs = read_info_ref();
 1912 	git_header_html();
 1913 	print "<div class=\"page_nav\">\n";
 1914 	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 1915 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") .
 1916 	      " | log" .
 1917 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
 1918 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") .
 1919 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 1920 
 1921 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 1922 	open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed.");
 1923 	my (@revlist) = map { chomp; $_ } <$fd>;
 1924 	close $fd;
 1925 
 1926 	if ($hash ne $head || $page) {
 1927 		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD");
 1928 	} else {
 1929 		print "HEAD";
 1930 	}
 1931 	if ($page > 0) {
 1932 		print " &sdot; " .
 1933 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
 1934 	} else {
 1935 		print " &sdot; prev";
 1936 	}
 1937 	if ($#revlist >= (100 * ($page+1)-1)) {
 1938 		print " &sdot; " .
 1939 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
 1940 	} else {
 1941 		print " &sdot; next";
 1942 	}
 1943 	print "<br/>\n" .
 1944 	      "</div>\n";
 1945 	if (!@revlist) {
 1946 		print "<div>\n" .
 1947 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
 1948 		      "</div>\n";
 1949 		my %co = git_read_commit($hash);
 1950 		print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
 1951 	}
 1952 	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
 1953 		my $commit = $revlist[$i];
 1954 		my $ref = "";
 1955 		if (defined $refs->{$commit}) {
 1956 			$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
 1957 		}
 1958 		my %co = git_read_commit($commit);
 1959 		next if !%co;
 1960 		my %ad = date_str($co{'author_epoch'});
 1961 		print "<div>\n" .
 1962 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "title"},
 1963 		      "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref) . "\n";
 1964 		print "</div>\n";
 1965 		print "<div class=\"title_text\">\n" .
 1966 		      "<div class=\"log_link\">\n" .
 1967 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
 1968 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
 1969 		      "<br/>\n" .
 1970 		      "</div>\n" .
 1971 		      "<i>" . esc_html($co{'author_name'}) .  " [$ad{'rfc2822'}]</i><br/>\n" .
 1972 		      "</div>\n" .
 1973 		      "<div class=\"log_body\">\n";
 1974 		my $comment = $co{'comment'};
 1975 		my $empty = 0;
 1976 		foreach my $line (@$comment) {
 1977 			if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
 1978 				next;
 1979 			}
 1980 			if ($line eq "") {
 1981 				if ($empty) {
 1982 					next;
 1983 				}
 1984 				$empty = 1;
 1985 			} else {
 1986 				$empty = 0;
 1987 			}
 1988 			print format_log_line_html($line) . "<br/>\n";
 1989 		}
 1990 		if (!$empty) {
 1991 			print "<br/>\n";
 1992 		}
 1993 		print "</div>\n";
 1994 	}
 1995 	git_footer_html();
 1996 }
 1997 
 1998 sub git_commit {
 1999 	my %co = git_read_commit($hash);
 2000 	if (!%co) {
 2001 		die_error(undef, "Unknown commit object.");
 2002 	}
 2003 	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
 2004 	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
 2005 
 2006 	my @difftree;
 2007 	my $root = "";
 2008 	my $parent = $co{'parent'};
 2009 	if (!defined $parent) {
 2010 		$root = " --root";
 2011 		$parent = "";
 2012 	}
 2013 	open my $fd, "-|", "$GIT diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed.");
 2014 	@difftree = map { chomp; $_ } <$fd>;
 2015 	close $fd or die_error(undef, "Reading diff-tree failed.");
 2016 
 2017 	# non-textual hash id's can be cached
 2018 	my $expires;
 2019 	if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
 2020 		$expires = "+1d";
 2021 	}
 2022 	my $refs = read_info_ref();
 2023 	my $ref = "";
 2024 	if (defined $refs->{$co{'id'}}) {
 2025 		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>";
 2026 	}
 2027 	git_header_html(undef, $expires);
 2028 	print "<div class=\"page_nav\">\n" .
 2029 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 2030 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") .
 2031 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
 2032 	      " | commit";
 2033 	if (defined $co{'parent'}) {
 2034 		print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff");
 2035 	}
 2036 	print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "\n" .
 2037 		"<br/>\n";
 2038 	if (defined $file_name && defined $co{'parent'}) {
 2039 		my $parent = $co{'parent'};
 2040 		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame") . "\n";
 2041 	}
 2042 	print "<br/></div>\n";
 2043 
 2044 	if (defined $co{'parent'}) {
 2045 		print "<div>\n" .
 2046 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
 2047 		      "</div>\n";
 2048 	} else {
 2049 		print "<div>\n" .
 2050 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" .
 2051 		      "</div>\n";
 2052 	}
 2053 	print "<div class=\"title_text\">\n" .
 2054 	      "<table cellspacing=\"0\">\n";
 2055 	print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
 2056 	      "<tr>" .
 2057 	      "<td></td><td> $ad{'rfc2822'}";
 2058 	if ($ad{'hour_local'} < 6) {
 2059 		printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
 2060 	} else {
 2061 		printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
 2062 	}
 2063 	print "</td>" .
 2064 	      "</tr>\n";
 2065 	print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
 2066 	print "<tr><td></td><td> $cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "</td></tr>\n";
 2067 	print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
 2068 	print "<tr>" .
 2069 	      "<td>tree</td>" .
 2070 	      "<td class=\"sha1\">" .
 2071 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) .
 2072 	      "</td>" .
 2073 	      "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") .
 2074 	      "</td>" .
 2075 	      "</tr>\n";
 2076 	my $parents = $co{'parents'};
 2077 	foreach my $par (@$parents) {
 2078 		print "<tr>" .
 2079 		      "<td>parent</td>" .
 2080 		      "<td class=\"sha1\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "</td>" .
 2081 		      "<td class=\"link\">" .
 2082 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") .
 2083 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") .
 2084 		      "</td>" .
 2085 		      "</tr>\n";
 2086 	}
 2087 	print "</table>".
 2088 	      "</div>\n";
 2089 	print "<div class=\"page_body\">\n";
 2090 	my $comment = $co{'comment'};
 2091 	my $empty = 0;
 2092 	my $signed = 0;
 2093 	foreach my $line (@$comment) {
 2094 		# print only one empty line
 2095 		if ($line eq "") {
 2096 			if ($empty || $signed) {
 2097 				next;
 2098 			}
 2099 			$empty = 1;
 2100 		} else {
 2101 			$empty = 0;
 2102 		}
 2103 		if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
 2104 			$signed = 1;
 2105 			print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
 2106 		} else {
 2107 			$signed = 0;
 2108 			print format_log_line_html($line) . "<br/>\n";
 2109 		}
 2110 	}
 2111 	print "</div>\n";
 2112 	print "<div class=\"list_head\">\n";
 2113 	if ($#difftree > 10) {
 2114 		print(($#difftree + 1) . " files changed:\n");
 2115 	}
 2116 	print "</div>\n";
 2117 	print "<table class=\"diff_tree\">\n";
 2118 	my $alternate = 0;
 2119 	foreach my $line (@difftree) {
 2120 		# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M      ls-files.c'
 2121 		# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M      rev-tree.c'
 2122 		if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
 2123 			next;
 2124 		}
 2125 		my $from_mode = $1;
 2126 		my $to_mode = $2;
 2127 		my $from_id = $3;
 2128 		my $to_id = $4;
 2129 		my $status = $5;
 2130 		my $similarity = $6;
 2131 		my $file = validate_input(unquote($7));
 2132 		if ($alternate) {
 2133 			print "<tr class=\"dark\">\n";
 2134 		} else {
 2135 			print "<tr class=\"light\">\n";
 2136 		}
 2137 		$alternate ^= 1;
 2138 		if ($status eq "A") {
 2139 			my $mode_chng = "";
 2140 			if (S_ISREG(oct $to_mode)) {
 2141 				$mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777);
 2142 			}
 2143 			print "<td>" .
 2144 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" .
 2145 			      "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" .
 2146 			      "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n";
 2147 		} elsif ($status eq "D") {
 2148 			print "<td>" .
 2149 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" .
 2150 			      "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" .
 2151 			      "<td class=\"link\">" .
 2152 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") .
 2153 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") .
 2154 			      "</td>\n"
 2155 		} elsif ($status eq "M" || $status eq "T") {
 2156 			my $mode_chnge = "";
 2157 			if ($from_mode != $to_mode) {
 2158 				$mode_chnge = " <span class=\"file_status mode_chnge\">[changed";
 2159 				if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) {
 2160 					$mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode);
 2161 				}
 2162 				if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) {
 2163 					if (S_ISREG($from_mode) && S_ISREG($to_mode)) {
 2164 						$mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777);
 2165 					} elsif (S_ISREG($to_mode)) {
 2166 						$mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777);
 2167 					}
 2168 				}
 2169 				$mode_chnge .= "]</span>\n";
 2170 			}
 2171 			print "<td>";
 2172 			if ($to_id ne $from_id) {
 2173 				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file));
 2174 			} else {
 2175 				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file));
 2176 			}
 2177 			print "</td>\n" .
 2178 			      "<td>$mode_chnge</td>\n" .
 2179 			      "<td class=\"link\">";
 2180 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob");
 2181 			if ($to_id ne $from_id) {
 2182 				print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff");
 2183 			}
 2184 			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n";
 2185 			print "</td>\n";
 2186 		} elsif ($status eq "R") {
 2187 			my ($from_file, $to_file) = split "\t", $file;
 2188 			my $mode_chng = "";
 2189 			if ($from_mode != $to_mode) {
 2190 				$mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777);
 2191 			}
 2192 			print "<td>" .
 2193 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "</td>\n" .
 2194 			      "<td><span class=\"file_status moved\">[moved from " .
 2195 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$from_file"), -class => "list"}, esc_html($from_file)) .
 2196 			      " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" .
 2197 			      "<td class=\"link\">" .
 2198 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob");
 2199 			if ($to_id ne $from_id) {
 2200 				print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff");
 2201 			}
 2202 			print "</td>\n";
 2203 		}
 2204 		print "</tr>\n";
 2205 	}
 2206 	print "</table>\n";
 2207 	git_footer_html();
 2208 }
 2209 
 2210 sub git_blobdiff {
 2211 	mkdir($git_temp, 0700);
 2212 	git_header_html();
 2213 	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
 2214 		print "<div class=\"page_nav\">\n" .
 2215 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 2216 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 2217 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 2218 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
 2219 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
 2220 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") .
 2221 		      "<br/>\n";
 2222 		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain") .
 2223 		      "</div>\n";
 2224 		print "<div>\n" .
 2225 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" .
 2226 		      "</div>\n";
 2227 	} else {
 2228 		print "<div class=\"page_nav\">\n" .
 2229 		      "<br/><br/></div>\n" .
 2230 		      "<div class=\"title\">$hash vs $hash_parent</div>\n";
 2231 	}
 2232 	git_print_page_path($file_name, "blob");
 2233 	print "<div class=\"page_body\">\n" .
 2234 	      "<div class=\"diff_info\">blob:" .
 2235 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) .
 2236 	      " -> blob:" .
 2237 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) .
 2238 	      "</div>\n";
 2239 	git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash);
 2240 	print "</div>";
 2241 	git_footer_html();
 2242 }
 2243 
 2244 sub git_blobdiff_plain {
 2245 	mkdir($git_temp, 0700);
 2246 	print $cgi->header(-type => "text/plain", -charset => 'utf-8');
 2247 	git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain");
 2248 }
 2249 
 2250 sub git_commitdiff {
 2251 	mkdir($git_temp, 0700);
 2252 	my %co = git_read_commit($hash);
 2253 	if (!%co) {
 2254 		die_error(undef, "Unknown commit object.");
 2255 	}
 2256 	if (!defined $hash_parent) {
 2257 		$hash_parent = $co{'parent'};
 2258 	}
 2259 	open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
 2260 	my (@difftree) = map { chomp; $_ } <$fd>;
 2261 	close $fd or die_error(undef, "Reading diff-tree failed.");
 2262 
 2263 	# non-textual hash id's can be cached
 2264 	my $expires;
 2265 	if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
 2266 		$expires = "+1d";
 2267 	}
 2268 	my $refs = read_info_ref();
 2269 	my $ref = "";
 2270 	if (defined $refs->{$co{'id'}}) {
 2271 		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>";
 2272 	}
 2273 	git_header_html(undef, $expires);
 2274 	print "<div class=\"page_nav\">\n" .
 2275 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 2276 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") .
 2277 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
 2278 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
 2279 	      " | commitdiff" .
 2280 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "<br/>\n";
 2281 	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . "\n" .
 2282 	      "</div>\n";
 2283 	print "<div>\n" .
 2284 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
 2285 	      "</div>\n";
 2286 	print "<div class=\"page_body\">\n";
 2287 	my $comment = $co{'comment'};
 2288 	my $empty = 0;
 2289 	my $signed = 0;
 2290 	my @log = @$comment;
 2291 	# remove first and empty lines after that
 2292 	shift @log;
 2293 	while (defined $log[0] && $log[0] eq "") {
 2294 		shift @log;
 2295 	}
 2296 	foreach my $line (@log) {
 2297 		if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
 2298 			next;
 2299 		}
 2300 		if ($line eq "") {
 2301 			if ($empty) {
 2302 				next;
 2303 			}
 2304 			$empty = 1;
 2305 		} else {
 2306 			$empty = 0;
 2307 		}
 2308 		print format_log_line_html($line) . "<br/>\n";
 2309 	}
 2310 	print "<br/>\n";
 2311 	foreach my $line (@difftree) {
 2312 		# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M      ls-files.c'
 2313 		# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M      rev-tree.c'
 2314 		$line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/;
 2315 		my $from_mode = $1;
 2316 		my $to_mode = $2;
 2317 		my $from_id = $3;
 2318 		my $to_id = $4;
 2319 		my $status = $5;
 2320 		my $file = validate_input(unquote($6));
 2321 		if ($status eq "A") {
 2322 			print "<div class=\"diff_info\">" .  file_type($to_mode) . ":" .
 2323 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" .
 2324 			      "</div>\n";
 2325 			git_diff_print(undef, "/dev/null", $to_id, "b/$file");
 2326 		} elsif ($status eq "D") {
 2327 			print "<div class=\"diff_info\">" . file_type($from_mode) . ":" .
 2328 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" .
 2329 			      "</div>\n";
 2330 			git_diff_print($from_id, "a/$file", undef, "/dev/null");
 2331 		} elsif ($status eq "M") {
 2332 			if ($from_id ne $to_id) {
 2333 				print "<div class=\"diff_info\">" .
 2334 				      file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) .
 2335 				      " -> " .
 2336 				      file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id);
 2337 				print "</div>\n";
 2338 				git_diff_print($from_id, "a/$file",  $to_id, "b/$file");
 2339 			}
 2340 		}
 2341 	}
 2342 	print "<br/>\n" .
 2343 	      "</div>";
 2344 	git_footer_html();
 2345 }
 2346 
 2347 sub git_commitdiff_plain {
 2348 	mkdir($git_temp, 0700);
 2349 	open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
 2350 	my (@difftree) = map { chomp; $_ } <$fd>;
 2351 	close $fd or die_error(undef, "Reading diff-tree failed.");
 2352 
 2353 	# try to figure out the next tag after this commit
 2354 	my $tagname;
 2355 	my $refs = read_info_ref("tags");
 2356 	open $fd, "-|", "$GIT rev-list HEAD";
 2357 	chomp (my (@commits) = <$fd>);
 2358 	close $fd;
 2359 	foreach my $commit (@commits) {
 2360 		if (defined $refs->{$commit}) {
 2361 			$tagname = $refs->{$commit}
 2362 		}
 2363 		if ($commit eq $hash) {
 2364 			last;
 2365 		}
 2366 	}
 2367 
 2368 	print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\"");
 2369 	my %co = git_read_commit($hash);
 2370 	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
 2371 	my $comment = $co{'comment'};
 2372 	print "From: $co{'author'}\n" .
 2373 	      "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
 2374 	      "Subject: $co{'title'}\n";
 2375 	if (defined $tagname) {
 2376 	      print "X-Git-Tag: $tagname\n";
 2377 	}
 2378 	print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
 2379 	      "\n";
 2380 
 2381 	foreach my $line (@$comment) {;
 2382 		print "$line\n";
 2383 	}
 2384 	print "---\n\n";
 2385 
 2386 	foreach my $line (@difftree) {
 2387 		$line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/;
 2388 		my $from_id = $3;
 2389 		my $to_id = $4;
 2390 		my $status = $5;
 2391 		my $file = $6;
 2392 		if ($status eq "A") {
 2393 			git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain");
 2394 		} elsif ($status eq "D") {
 2395 			git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain");
 2396 		} elsif ($status eq "M") {
 2397 			git_diff_print($from_id, "a/$file",  $to_id, "b/$file", "plain");
 2398 		}
 2399 	}
 2400 }
 2401 
 2402 sub git_history {
 2403 	if (!defined $hash_base) {
 2404 		$hash_base = git_read_head($project);
 2405 	}
 2406 	my $ftype;
 2407 	my %co = git_read_commit($hash_base);
 2408 	if (!%co) {
 2409 		die_error(undef, "Unknown commit object.");
 2410 	}
 2411 	my $refs = read_info_ref();
 2412 	git_header_html();
 2413 	print "<div class=\"page_nav\">\n" .
 2414 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 2415 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 2416 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
 2417 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
 2418 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
 2419 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") .
 2420 	      "<br/><br/>\n" .
 2421 	      "</div>\n";
 2422 	print "<div>\n" .
 2423 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" .
 2424 	      "</div>\n";
 2425 	if (!defined $hash && defined $file_name) {
 2426 		$hash = git_get_hash_by_path($hash_base, $file_name);
 2427 	}
 2428 	if (defined $hash) {
 2429 		$ftype = git_get_type($hash);
 2430 	}
 2431 	git_print_page_path($file_name, $ftype);
 2432 
 2433 	open my $fd, "-|",
 2434 		"$GIT rev-list --full-history $hash_base -- \'$file_name\'";
 2435 	print "<table cellspacing=\"0\">\n";
 2436 	my $alternate = 0;
 2437 	while (my $line = <$fd>) {
 2438 		if ($line =~ m/^([0-9a-fA-F]{40})/){
 2439 			my $commit = $1;
 2440 			my %co = git_read_commit($commit);
 2441 			if (!%co) {
 2442 				next;
 2443 			}
 2444 			my $ref = "";
 2445 			if (defined $refs->{$commit}) {
 2446 				$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
 2447 			}
 2448 			if ($alternate) {
 2449 				print "<tr class=\"dark\">\n";
 2450 			} else {
 2451 				print "<tr class=\"light\">\n";
 2452 			}
 2453 			$alternate ^= 1;
 2454 			print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 2455 			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
 2456 			      "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" .
 2457 			      esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" .
 2458 			      "<td class=\"link\">" .
 2459 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
 2460 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
 2461 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=$commit;f=$file_name")}, "blob");
 2462 			my $blob = git_get_hash_by_path($hash_base, $file_name);
 2463 			my $blob_parent = git_get_hash_by_path($commit, $file_name);
 2464 			if (defined $blob && defined $blob_parent && $blob ne $blob_parent) {
 2465 				print " | " .
 2466 				$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")},
 2467 				"diff to current");
 2468 			}
 2469 			print "</td>\n" .
 2470 			      "</tr>\n";
 2471 		}
 2472 	}
 2473 	print "</table>\n";
 2474 	close $fd;
 2475 	git_footer_html();
 2476 }
 2477 
 2478 sub git_search {
 2479 	if (!defined $searchtext) {
 2480 		die_error("", "Text field empty.");
 2481 	}
 2482 	if (!defined $hash) {
 2483 		$hash = git_read_head($project);
 2484 	}
 2485 	my %co = git_read_commit($hash);
 2486 	if (!%co) {
 2487 		die_error(undef, "Unknown commit object.");
 2488 	}
 2489 	# pickaxe may take all resources of your box and run for several minutes
 2490 	# with every query - so decide by yourself how public you make this feature :)
 2491 	my $commit_search = 1;
 2492 	my $author_search = 0;
 2493 	my $committer_search = 0;
 2494 	my $pickaxe_search = 0;
 2495 	if ($searchtext =~ s/^author\\://i) {
 2496 		$author_search = 1;
 2497 	} elsif ($searchtext =~ s/^committer\\://i) {
 2498 		$committer_search = 1;
 2499 	} elsif ($searchtext =~ s/^pickaxe\\://i) {
 2500 		$commit_search = 0;
 2501 		$pickaxe_search = 1;
 2502 	}
 2503 	git_header_html();
 2504 	print "<div class=\"page_nav\">\n" .
 2505 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary;h=$hash")}, "summary") .
 2506 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
 2507 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
 2508 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
 2509 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") .
 2510 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") .
 2511 	      "<br/><br/>\n" .
 2512 	      "</div>\n";
 2513 
 2514 	print "<div>\n" .
 2515 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" .
 2516 	      "</div>\n";
 2517 	print "<table cellspacing=\"0\">\n";
 2518 	my $alternate = 0;
 2519 	if ($commit_search) {
 2520 		$/ = "\0";
 2521 		open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next;
 2522 		while (my $commit_text = <$fd>) {
 2523 			if (!grep m/$searchtext/i, $commit_text) {
 2524 				next;
 2525 			}
 2526 			if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
 2527 				next;
 2528 			}
 2529 			if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
 2530 				next;
 2531 			}
 2532 			my @commit_lines = split "\n", $commit_text;
 2533 			my %co = git_read_commit(undef, \@commit_lines);
 2534 			if (!%co) {
 2535 				next;
 2536 			}
 2537 			if ($alternate) {
 2538 				print "<tr class=\"dark\">\n";
 2539 			} else {
 2540 				print "<tr class=\"light\">\n";
 2541 			}
 2542 			$alternate ^= 1;
 2543 			print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 2544 			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
 2545 			      "<td>" .
 2546 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" . esc_html(chop_str($co{'title'}, 50)) . "</b><br/>");
 2547 			my $comment = $co{'comment'};
 2548 			foreach my $line (@$comment) {
 2549 				if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
 2550 					my $lead = esc_html($1) || "";
 2551 					$lead = chop_str($lead, 30, 10);
 2552 					my $match = esc_html($2) || "";
 2553 					my $trail = esc_html($3) || "";
 2554 					$trail = chop_str($trail, 30, 10);
 2555 					my $text = "$lead<span class=\"match\">$match</span>$trail";
 2556 					print chop_str($text, 80, 5) . "<br/>\n";
 2557 				}
 2558 			}
 2559 			print "</td>\n" .
 2560 			      "<td class=\"link\">" .
 2561 			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") .
 2562 			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree");
 2563 			print "</td>\n" .
 2564 			      "</tr>\n";
 2565 		}
 2566 		close $fd;
 2567 	}
 2568 
 2569 	if ($pickaxe_search) {
 2570 		$/ = "\n";
 2571 		open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'";
 2572 		undef %co;
 2573 		my @files;
 2574 		while (my $line = <$fd>) {
 2575 			if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
 2576 				my %set;
 2577 				$set{'file'} = $6;
 2578 				$set{'from_id'} = $3;
 2579 				$set{'to_id'} = $4;
 2580 				$set{'id'} = $set{'to_id'};
 2581 				if ($set{'id'} =~ m/0{40}/) {
 2582 					$set{'id'} = $set{'from_id'};
 2583 				}
 2584 				if ($set{'id'} =~ m/0{40}/) {
 2585 					next;
 2586 				}
 2587 				push @files, \%set;
 2588 			} elsif ($line =~ m/^([0-9a-fA-F]{40})$/){
 2589 				if (%co) {
 2590 					if ($alternate) {
 2591 						print "<tr class=\"dark\">\n";
 2592 					} else {
 2593 						print "<tr class=\"light\">\n";
 2594 					}
 2595 					$alternate ^= 1;
 2596 					print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 2597 					      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
 2598 					      "<td>" .
 2599 					      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" .
 2600 					      esc_html(chop_str($co{'title'}, 50)) . "</b><br/>");
 2601 					while (my $setref = shift @files) {
 2602 						my %set = %$setref;
 2603 						print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"},
 2604 						      "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") .
 2605 						      "<br/>\n";
 2606 					}
 2607 					print "</td>\n" .
 2608 					      "<td class=\"link\">" .
 2609 					      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") .
 2610 					      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree");
 2611 					print "</td>\n" .
 2612 					      "</tr>\n";
 2613 				}
 2614 				%co = git_read_commit($1);
 2615 			}
 2616 		}
 2617 		close $fd;
 2618 	}
 2619 	print "</table>\n";
 2620 	git_footer_html();
 2621 }
 2622 
 2623 sub git_shortlog {
 2624 	my $head = git_read_head($project);
 2625 	if (!defined $hash) {
 2626 		$hash = $head;
 2627 	}
 2628 	if (!defined $page) {
 2629 		$page = 0;
 2630 	}
 2631 	my $refs = read_info_ref();
 2632 	git_header_html();
 2633 	print "<div class=\"page_nav\">\n" .
 2634 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
 2635 	      " | shortlog" .
 2636 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
 2637 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
 2638 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") .
 2639 	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";
 2640 
 2641 	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
 2642 	open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed.");
 2643 	my (@revlist) = map { chomp; $_ } <$fd>;
 2644 	close $fd;
 2645 
 2646 	if ($hash ne $head || $page) {
 2647 		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD");
 2648 	} else {
 2649 		print "HEAD";
 2650 	}
 2651 	if ($page > 0) {
 2652 		print " &sdot; " .
 2653 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev");
 2654 	} else {
 2655 		print " &sdot; prev";
 2656 	}
 2657 	if ($#revlist >= (100 * ($page+1)-1)) {
 2658 		print " &sdot; " .
 2659 		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next");
 2660 	} else {
 2661 		print " &sdot; next";
 2662 	}
 2663 	print "<br/>\n" .
 2664 	      "</div>\n";
 2665 	print "<div>\n" .
 2666 	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, "&nbsp;") .
 2667 	      "</div>\n";
 2668 	print "<table cellspacing=\"0\">\n";
 2669 	my $alternate = 0;
 2670 	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
 2671 		my $commit = $revlist[$i];
 2672 		my $ref = "";
 2673 		if (defined $refs->{$commit}) {
 2674 			$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
 2675 		}
 2676 		my %co = git_read_commit($commit);
 2677 		my %ad = date_str($co{'author_epoch'});
 2678 		if ($alternate) {
 2679 			print "<tr class=\"dark\">\n";
 2680 		} else {
 2681 			print "<tr class=\"light\">\n";
 2682 		}
 2683 		$alternate ^= 1;
 2684 		print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
 2685 		      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
 2686 		      "<td>";
 2687 		if (length($co{'title_short'}) < length($co{'title'})) {
 2688 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"},
 2689 			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
 2690 		} else {
 2691 			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"},
 2692 			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
 2693 		}
 2694 		print "</td>\n" .
 2695 		      "<td class=\"link\">" .
 2696 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
 2697 		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
 2698 		      "</td>\n" .
 2699 		      "</tr>";
 2700 	}
 2701 	if ($#revlist >= (100 * ($page+1)-1)) {
 2702 		print "<tr>\n" .
 2703 		      "<td>" .
 2704 		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -title => "Alt-n"}, "next") .
 2705 		      "</td>\n" .
 2706 		      "</tr>\n";
 2707 	}
 2708 	print "</table\n>";
 2709 	git_footer_html();
 2710 }

Generated by cgit