diff options
| author | Junio C Hamano <junkio@cox.net> | 2006-08-12 19:24:09 -0700 | 
|---|---|---|
| committer | Junio C Hamano <junkio@cox.net> | 2006-08-12 19:24:09 -0700 | 
| commit | efced1e06e475c6e7c0b1ba0eaedf7046c01cde5 (patch) | |
| tree | 5f7ca67de1d6b742496e830676c95f823d724c1d /gitweb | |
| parent | 86183fbb09ffe5d46eae1cf52023a1b5f8fbc047 (diff) | |
| parent | 82f930deadc9a7990cbc0988255e6a5afd641f4b (diff) | |
| download | git-efced1e06e475c6e7c0b1ba0eaedf7046c01cde5.tar.gz | |
Merge branch 'lt/web'
Diffstat (limited to 'gitweb')
| -rw-r--r-- | gitweb/README | 30 | ||||
| -rw-r--r-- | gitweb/git-logo.png | bin | 0 -> 208 bytes | |||
| -rw-r--r-- | gitweb/gitweb.css | 14 | ||||
| -rwxr-xr-x | gitweb/gitweb.perl (renamed from gitweb/gitweb.cgi) | 2480 | 
4 files changed, 1229 insertions, 1295 deletions
| diff --git a/gitweb/README b/gitweb/README index 8d672762ea..27c6dac143 100644 --- a/gitweb/README +++ b/gitweb/README @@ -5,5 +5,33 @@ The one working on:  From the git version 1.4.0 gitweb is bundled with git. -Any comment/question/concern to: + +How to configure gitweb for your local system: + +You can specify the following configuration variables when building GIT: + * GITWEB_SITENAME +   Shown in the title of all generated pages, defaults to the servers name. + * GITWEB_PROJECTROOT +   The root directory for all projects shown by gitweb. + * GITWEB_LIST +   points to a directory to scan for projects (defaults to project root) +   or to a file for explicit listing of projects. + * GITWEB_HOMETEXT +   points to an .html file which is included on the gitweb project +   overview page. + * GITWEB_CSS +   Points to the location where you put gitweb.css on your web server. + * GITWEB_LOGO +   Points to the location where you put git-logo.png on your web server. + * GITWEB_CONFIG +   This file will be loaded using 'require'.  If the environment +   $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the +   environment variable will be loaded instead of the file +   specified when gitweb.cgi was created. + +Originally written by:    Kay Sievers <kay.sievers@vrfy.org> + +Any comment/question/concern to: +  Git mailing list <git@vger.kernel.org> + diff --git a/gitweb/git-logo.png b/gitweb/git-logo.pngBinary files differ new file mode 100644 index 0000000000..16ae8d5382 --- /dev/null +++ b/gitweb/git-logo.png diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index fffdb13d09..47c1ade87e 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -171,6 +171,10 @@ tr.dark {  	background-color: #f6f6f0;  } +tr.dark2 { +	background-color: #f6f6f0; +} +  tr.dark:hover {  	background-color: #edece6;  } @@ -181,12 +185,16 @@ td {  	vertical-align: top;  } -td.link { +td.link, td.selflink {  	padding: 2px 5px;  	font-family: sans-serif;  	font-size: 10px;  } +td.selflink { +	padding-right: 0px; +} +  td.sha1 {  	font-family: monospace;  } @@ -196,6 +204,10 @@ td.error {  	background-color: yellow;  } +td.current_head { +	text-decoration: underline; +} +  table.diff_tree span.file_status.new {  	color: #008000;  } diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.perl index e5fca63b9c..ae13e3e701 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.perl @@ -14,47 +14,42 @@ use CGI::Util qw(unescape);  use CGI::Carp qw(fatalsToBrowser);  use Encode;  use Fcntl ':mode'; +use File::Find qw();  binmode STDOUT, ':utf8';  our $cgi = new CGI; -our $version = "267"; +our $version = "++GIT_VERSION++";  our $my_url = $cgi->url();  our $my_uri = $cgi->url(-absolute => 1); -our $rss_link = "";  # core git executable to use  # this can just be "git" if your webserver has a sensible PATH -our $GIT = "/usr/bin/git"; +our $GIT = "++GIT_BINDIR++/git";  # absolute fs-path which will be prepended to the project path  #our $projectroot = "/pub/scm"; -our $projectroot = "/home/kay/public_html/pub/scm"; - -# version of the core git binary -our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; +our $projectroot = "++GITWEB_PROJECTROOT++";  # location for temporary files needed for diffs  our $git_temp = "/tmp/gitweb"; -if (! -d $git_temp) { -    mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); -}  # target of the home link on top of all pages  our $home_link = $my_uri;  # name of your site or organization to appear in page titles  # replace this with something more descriptive for clearer bookmarks -our $site_name = $ENV{'SERVER_NAME'} || "Untitled"; +our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";  # html text to include at home page -our $home_text = "indextext.html"; +our $home_text = "++GITWEB_HOMETEXT++";  # URI of default stylesheet -our $stylesheet = "gitweb.css"; +our $stylesheet = "++GITWEB_CSS++"; +# URI of GIT logo +our $logo = "++GITWEB_LOGO++";  # source of projects list -#our $projects_list = $projectroot; -our $projects_list = "index/index.aux"; +our $projects_list = "++GITWEB_LIST++";  # default blob_plain mimetype and default charset for text/plain blob  our $default_blob_plain_mimetype = 'text/plain'; @@ -64,47 +59,46 @@ our $default_text_plain_charset  = undef;  # (relative to the current git repository)  our $mimetypes_file = undef; +our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; +require $GITWEB_CONFIG if -e $GITWEB_CONFIG; + +# version of the core git binary +our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; + +$projects_list ||= $projectroot; +if (! -d $git_temp) { +	mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp"); +} + +# ======================================================================  # input validation and dispatch  our $action = $cgi->param('a');  if (defined $action) {  	if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { -		undef $action; -		die_error(undef, "Invalid action parameter."); +		die_error(undef, "Invalid action parameter");  	} -	if ($action eq "git-logo.png") { -		git_logo(); -		exit; -	} elsif ($action eq "opml") { +	# action which does not check rest of parameters +	if ($action eq "opml") {  		git_opml();  		exit;  	}  } -our $order = $cgi->param('o'); -if (defined $order) { -	if ($order =~ m/[^0-9a-zA-Z_]/) { -		undef $order; -		die_error(undef, "Invalid order parameter."); -	} -} -  our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});  if (defined $project) { -	$project =~ s|^/||; $project =~ s|/$||; -	$project = validate_input($project); -	if (!defined($project)) { -		die_error(undef, "Invalid project parameter."); +	$project =~ s|^/||; +	$project =~ s|/$||; +} +if (defined $project && $project) { +	if (!validate_input($project)) { +		die_error(undef, "Invalid project parameter");  	}  	if (!(-d "$projectroot/$project")) { -		undef $project; -		die_error(undef, "No such directory."); +		die_error(undef, "No such directory");  	}  	if (!(-e "$projectroot/$project/HEAD")) { -		undef $project; -		die_error(undef, "No such project."); +		die_error(undef, "No such project");  	} -	$rss_link = "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" . -		    "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>";  	$ENV{'GIT_DIR'} = "$projectroot/$project";  } else {  	git_project_list(); @@ -113,53 +107,79 @@ if (defined $project) {  our $file_name = $cgi->param('f');  if (defined $file_name) { -	$file_name = validate_input($file_name); -	if (!defined($file_name)) { -		die_error(undef, "Invalid file parameter."); +	if (!validate_input($file_name)) { +		die_error(undef, "Invalid file parameter");  	}  }  our $hash = $cgi->param('h');  if (defined $hash) { -	$hash = validate_input($hash); -	if (!defined($hash)) { -		die_error(undef, "Invalid hash parameter."); +	if (!validate_input($hash)) { +		die_error(undef, "Invalid hash parameter");  	}  }  our $hash_parent = $cgi->param('hp');  if (defined $hash_parent) { -	$hash_parent = validate_input($hash_parent); -	if (!defined($hash_parent)) { -		die_error(undef, "Invalid hash parent parameter."); +	if (!validate_input($hash_parent)) { +		die_error(undef, "Invalid hash parent parameter");  	}  }  our $hash_base = $cgi->param('hb');  if (defined $hash_base) { -	$hash_base = validate_input($hash_base); -	if (!defined($hash_base)) { -		die_error(undef, "Invalid hash base parameter."); +	if (!validate_input($hash_base)) { +		die_error(undef, "Invalid hash base parameter");  	}  }  our $page = $cgi->param('pg');  if (defined $page) {  	if ($page =~ m/[^0-9]$/) { -		undef $page; -		die_error(undef, "Invalid page parameter."); +		die_error(undef, "Invalid page parameter");  	}  }  our $searchtext = $cgi->param('s');  if (defined $searchtext) {  	if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { -		undef $searchtext; -		die_error(undef, "Invalid search parameter."); +		die_error(undef, "Invalid search parameter");  	}  	$searchtext = quotemeta $searchtext;  } +# dispatch +my %actions = ( +	"blame" => \&git_blame2, +	"blobdiff" => \&git_blobdiff, +	"blobdiff_plain" => \&git_blobdiff_plain, +	"blob" => \&git_blob, +	"blob_plain" => \&git_blob_plain, +	"commitdiff" => \&git_commitdiff, +	"commitdiff_plain" => \&git_commitdiff_plain, +	"commit" => \&git_commit, +	"heads" => \&git_heads, +	"history" => \&git_history, +	"log" => \&git_log, +	"rss" => \&git_rss, +	"search" => \&git_search, +	"shortlog" => \&git_shortlog, +	"summary" => \&git_summary, +	"tag" => \&git_tag, +	"tags" => \&git_tags, +	"tree" => \&git_tree, +); + +$action = 'summary' if (!defined($action)); +if (!defined($actions{$action})) { +	die_error(undef, "Unknown action"); +} +$actions{$action}->(); +exit; + +## ====================================================================== +## validation, quoting/unquoting and escaping +  sub validate_input {  	my $input = shift; @@ -175,66 +195,6 @@ sub validate_input {  	return $input;  } -if (!defined $action || $action eq "summary") { -	git_summary(); -	exit; -} elsif ($action eq "heads") { -	git_heads(); -	exit; -} elsif ($action eq "tags") { -	git_tags(); -	exit; -} elsif ($action eq "blob") { -	git_blob(); -	exit; -} elsif ($action eq "blob_plain") { -	git_blob_plain(); -	exit; -} elsif ($action eq "tree") { -	git_tree(); -	exit; -} elsif ($action eq "rss") { -	git_rss(); -	exit; -} elsif ($action eq "commit") { -	git_commit(); -	exit; -} elsif ($action eq "log") { -	git_log(); -	exit; -} elsif ($action eq "blobdiff") { -	git_blobdiff(); -	exit; -} elsif ($action eq "blobdiff_plain") { -	git_blobdiff_plain(); -	exit; -} elsif ($action eq "commitdiff") { -	git_commitdiff(); -	exit; -} elsif ($action eq "commitdiff_plain") { -	git_commitdiff_plain(); -	exit; -} elsif ($action eq "history") { -	git_history(); -	exit; -} elsif ($action eq "search") { -	git_search(); -	exit; -} elsif ($action eq "shortlog") { -	git_shortlog(); -	exit; -} elsif ($action eq "tag") { -	git_tag(); -	exit; -} elsif ($action eq "blame") { -	git_blame2(); -	exit; -} else { -	undef $action; -	die_error(undef, "Unknown action."); -	exit; -} -  # quote unsafe chars, but keep the slash, even when it's not  # correct, but quoted slashes look too horrible in bookmarks  sub esc_param { @@ -250,6 +210,7 @@ sub esc_html {  	my $str = shift;  	$str = decode("utf8", $str, Encode::FB_DEFAULT);  	$str = escapeHTML($str); +	$str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)  	return $str;  } @@ -263,6 +224,43 @@ sub unquote {  	return $str;  } +# escape tabs (convert tabs to spaces) +sub untabify { +	my $line = shift; + +	while ((my $pos = index($line, "\t")) != -1) { +		if (my $count = (8 - ($pos % 8))) { +			my $spaces = ' ' x $count; +			$line =~ s/\t/$spaces/; +		} +	} + +	return $line; +} + +## ---------------------------------------------------------------------- +## HTML aware string manipulation + +sub chop_str { +	my $str = shift; +	my $len = shift; +	my $add_len = shift || 10; + +	# allow only $len chars, but don't cut a word if it would fit in $add_len +	# if it doesn't fit, cut it if it's still longer than the dots we would add +	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; +	my $body = $1; +	my $tail = $2; +	if (length($tail) > 4) { +		$tail = " ..."; +		$body =~ s/&[^;]*$//; # remove chopped character entities +	} +	return "$body$tail"; +} + +## ---------------------------------------------------------------------- +## functions returning short strings +  # CSS class for given age value (in seconds)  sub age_class {  	my $age = shift; @@ -276,125 +274,108 @@ sub age_class {  	}  } -sub git_header_html { -	my $status = shift || "200 OK"; -	my $expires = shift; +# convert age in seconds to "nn units ago" string +sub age_string { +	my $age = shift; +	my $age_str; -	my $title = "$site_name git"; -	if (defined $project) { -		$title .= " - $project"; -		if (defined $action) { -			$title .= "/$action"; -			if (defined $file_name) { -				$title .= " - $file_name"; -				if ($action eq "tree" && $file_name !~ m|/$|) { -					$title .= "/"; -				} -			} -		} -	} -	my $content_type; -	# require explicit support from the UA if we are to send the page as -	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'. -	# we have to do this because MSIE sometimes globs '*/*', pretending to -	# support xhtml+xml but choking when it gets what it asked for. -	if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { -		$content_type = 'application/xhtml+xml'; +	if ($age > 60*60*24*365*2) { +		$age_str = (int $age/60/60/24/365); +		$age_str .= " years ago"; +	} elsif ($age > 60*60*24*(365/12)*2) { +		$age_str = int $age/60/60/24/(365/12); +		$age_str .= " months ago"; +	} elsif ($age > 60*60*24*7*2) { +		$age_str = int $age/60/60/24/7; +		$age_str .= " weeks ago"; +	} elsif ($age > 60*60*24*2) { +		$age_str = int $age/60/60/24; +		$age_str .= " days ago"; +	} elsif ($age > 60*60*2) { +		$age_str = int $age/60/60; +		$age_str .= " hours ago"; +	} elsif ($age > 60*2) { +		$age_str = int $age/60; +		$age_str .= " min ago"; +	} elsif ($age > 2) { +		$age_str = int $age; +		$age_str .= " sec ago";  	} else { -		$content_type = 'text/html'; +		$age_str .= " right now";  	} -	print $cgi->header(-type=>$content_type,  -charset => 'utf-8', -status=> $status, -expires => $expires); -	print <<EOF; -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> -<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> -<!-- git core binaries version $git_version --> -<head> -<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> -<meta name="robots" content="index, nofollow"/> -<title>$title</title> -<link rel="stylesheet" type="text/css" href="$stylesheet"/> -$rss_link -</head> -<body> -EOF -	print "<div class=\"page_header\">\n" . -	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . -	      "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . -	      "</a>\n"; -	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; -	if (defined $project) { -		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); -		if (defined $action) { -			print " / $action"; -		} -		print "\n"; -		if (!defined $searchtext) { -			$searchtext = ""; -		} -		my $search_hash; -		if (defined $hash_base) { -			$search_hash = $hash_base; -		} elsif (defined $hash) { -			$search_hash = $hash; +	return $age_str; +} + +# convert file mode in octal to symbolic file mode string +sub mode_str { +	my $mode = oct shift; + +	if (S_ISDIR($mode & S_IFMT)) { +		return 'drwxr-xr-x'; +	} elsif (S_ISLNK($mode)) { +		return 'lrwxrwxrwx'; +	} elsif (S_ISREG($mode)) { +		# git cares only about the executable bit +		if ($mode & S_IXUSR) { +			return '-rwxr-xr-x';  		} else { -			$search_hash = "HEAD"; -		} -		$cgi->param("a", "search"); -		$cgi->param("h", $search_hash); -		print $cgi->startform(-method => "get", -action => $my_uri) . -		      "<div class=\"search\">\n" . -		      $cgi->hidden(-name => "p") . "\n" . -		      $cgi->hidden(-name => "a") . "\n" . -		      $cgi->hidden(-name => "h") . "\n" . -		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . -		      "</div>" . -		      $cgi->end_form() . "\n"; +			return '-rw-r--r--'; +		}; +	} else { +		return '----------';  	} -	print "</div>\n";  } -sub git_footer_html { -	print "<div class=\"page_footer\">\n"; -	if (defined $project) { -		my $descr = git_read_description($project); -		if (defined $descr) { -			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; -		} -		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; +# convert file mode in octal to file type string +sub file_type { +	my $mode = oct shift; + +	if (S_ISDIR($mode & S_IFMT)) { +		return "directory"; +	} elsif (S_ISLNK($mode)) { +		return "symlink"; +	} elsif (S_ISREG($mode)) { +		return "file";  	} else { -		print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; +		return "unknown";  	} -	print "</div>\n" . -	      "</body>\n" . -	      "</html>";  } -sub die_error { -	my $status = shift || "403 Forbidden"; -	my $error = shift || "Malformed query, file missing or permission denied"; +## ---------------------------------------------------------------------- +## functions returning short HTML fragments, or transforming HTML fragments +## which don't beling to other sections -	git_header_html($status); -	print "<div class=\"page_body\">\n" . -	      "<br/><br/>\n" . -	      "$status - $error\n" . -	      "<br/>\n" . -	      "</div>\n"; -	git_footer_html(); -	exit; +# format line of commit message or tag comment +sub format_log_line_html { +	my $line = shift; + +	$line = esc_html($line); +	$line =~ s/ / /g; +	if ($line =~ m/([0-9a-fA-F]{40})/) { +		my $hash_text = $1; +		if (git_get_type($hash_text) eq "commit") { +			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); +			$line =~ s/$hash_text/$link/; +		} +	} +	return $line;  } -sub git_get_type { -	my $hash = shift; +# format marker of refs pointing to given object +sub git_get_referencing { +	my ($refs, $id) = @_; -	open my $fd, "-|", "$GIT cat-file -t $hash" or return; -	my $type = <$fd>; -	close $fd or return; -	chomp $type; -	return $type; +	if (defined $refs->{$id}) { +		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>'; +	} else { +		return ""; +	}  } +## ---------------------------------------------------------------------- +## git utility subroutines, invoking git commands + +# get HEAD ref of given project as hash  sub git_read_head {  	my $project = shift;  	my $oENV = $ENV{'GIT_DIR'}; @@ -413,6 +394,57 @@ sub git_read_head {  	return $retval;  } +# get type of given object +sub git_get_type { +	my $hash = shift; + +	open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; +	my $type = <$fd>; +	close $fd or return; +	chomp $type; +	return $type; +} + +sub git_get_project_config { +	my $key = shift; + +	return unless ($key); +	$key =~ s/^gitweb\.//; +	return if ($key =~ m/\W/); + +	my $val = qx($GIT repo-config --get gitweb.$key); +	return ($val); +} + +sub git_get_project_config_bool { +	my $val = git_get_project_config (@_); +	if ($val and $val =~ m/true|yes|on/) { +		return (1); +	} +	return; # implicit false +} + +# get hash of given path at given ref +sub git_get_hash_by_path { +	my $base = shift; +	my $path = shift || return undef; + +	my $tree = $base; + +	open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path +		or die_error(undef, "Open git-ls-tree failed"); +	my $line = <$fd>; +	close $fd or return undef; + +	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c' +	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; +	return $3; +} + +## ...................................................................... +## git utility functions, directly accessing git repository + +# assumes that PATH is not symref  sub git_read_hash {  	my $path = shift; @@ -435,12 +467,106 @@ sub git_read_description {  	return $descr;  } +sub git_read_projects { +	my @list; + +	if (-d $projects_list) { +		# search in directory +		my $dir = $projects_list; +		opendir my ($dh), $dir or return undef; +		while (my $dir = readdir($dh)) { +			if (-e "$projectroot/$dir/HEAD") { +				my $pr = { +					path => $dir, +				}; +				push @list, $pr +			} +		} +		closedir($dh); +	} elsif (-f $projects_list) { +		# read from file(url-encoded): +		# 'git%2Fgit.git Linus+Torvalds' +		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' +		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' +		open my ($fd), $projects_list or return undef; +		while (my $line = <$fd>) { +			chomp $line; +			my ($path, $owner) = split ' ', $line; +			$path = unescape($path); +			$owner = unescape($owner); +			if (!defined $path) { +				next; +			} +			if (-e "$projectroot/$path/HEAD") { +				my $pr = { +					path => $path, +					owner => decode("utf8", $owner, Encode::FB_DEFAULT), +				}; +				push @list, $pr +			} +		} +		close $fd; +	} +	@list = sort {$a->{'path'} cmp $b->{'path'}} @list; +	return @list; +} + +sub read_info_ref { +	my $type = shift || ""; +	my %refs; +	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11 +	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{} +	open my $fd, "$projectroot/$project/info/refs" or return; +	while (my $line = <$fd>) { +		chomp $line; +		# attention: for $type == "" it saves only last path part of ref name +		# e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb' +		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { +			if (defined $refs{$1}) { +				$refs{$1} .= " / $2"; +			} else { +				$refs{$1} = $2; +			} +		} +	} +	close $fd or return; +	return \%refs; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub date_str { +	my $epoch = shift; +	my $tz = shift || "-0000"; + +	my %date; +	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); +	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); +	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); +	$date{'hour'} = $hour; +	$date{'minute'} = $min; +	$date{'mday'} = $mday; +	$date{'day'} = $days[$wday]; +	$date{'month'} = $months[$mon]; +	$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; +	$date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + +	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; +	my $local = $epoch + ((int $1 + ($2/60)) * 3600); +	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); +	$date{'hour_local'} = $hour; +	$date{'minute_local'} = $min; +	$date{'tz_local'} = $tz; +	return %date; +} +  sub git_read_tag {  	my $tag_id = shift;  	my %tag;  	my @comment; -	open my $fd, "-|", "$GIT cat-file tag $tag_id" or return; +	open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return;  	$tag{'id'} = $tag_id;  	while (my $line = <$fd>) {  		chomp $line; @@ -470,37 +596,6 @@ sub git_read_tag {  	return %tag  } -sub age_string { -	my $age = shift; -	my $age_str; - -	if ($age > 60*60*24*365*2) { -		$age_str = (int $age/60/60/24/365); -		$age_str .= " years ago"; -	} elsif ($age > 60*60*24*(365/12)*2) { -		$age_str = int $age/60/60/24/(365/12); -		$age_str .= " months ago"; -	} elsif ($age > 60*60*24*7*2) { -		$age_str = int $age/60/60/24/7; -		$age_str .= " weeks ago"; -	} elsif ($age > 60*60*24*2) { -		$age_str = int $age/60/60/24; -		$age_str .= " days ago"; -	} elsif ($age > 60*60*2) { -		$age_str = int $age/60/60; -		$age_str .= " hours ago"; -	} elsif ($age > 60*2) { -		$age_str = int $age/60; -		$age_str .= " min ago"; -	} elsif ($age > 2) { -		$age_str = int $age; -		$age_str .= " sec ago"; -	} else { -		$age_str .= " right now"; -	} -	return $age_str; -} -  sub git_read_commit {  	my $commit_id = shift;  	my $commit_text = shift; @@ -512,7 +607,7 @@ sub git_read_commit {  		@commit_lines = @$commit_text;  	} else {  		$/ = "\0"; -		open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return; +		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return;  		@commit_lines = split '\n', <$fd>;  		close $fd or return;  		$/ = "\n"; @@ -595,6 +690,520 @@ sub git_read_commit {  	return %co;  } +## ...................................................................... +## parse to array of hashes functions + +sub git_read_refs { +	my $ref_dir = shift; +	my @reflist; + +	my @refs; +	my $pfxlen = length("$projectroot/$project/$ref_dir"); +	File::Find::find(sub { +		return if (/^\./); +		if (-f $_) { +			push @refs, substr($File::Find::name, $pfxlen + 1); +		} +	}, "$projectroot/$project/$ref_dir"); + +	foreach my $ref_file (@refs) { +		my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); +		my $type = git_get_type($ref_id) || next; +		my %ref_item; +		my %co; +		$ref_item{'type'} = $type; +		$ref_item{'id'} = $ref_id; +		$ref_item{'epoch'} = 0; +		$ref_item{'age'} = "unknown"; +		if ($type eq "tag") { +			my %tag = git_read_tag($ref_id); +			$ref_item{'comment'} = $tag{'comment'}; +			if ($tag{'type'} eq "commit") { +				%co = git_read_commit($tag{'object'}); +				$ref_item{'epoch'} = $co{'committer_epoch'}; +				$ref_item{'age'} = $co{'age_string'}; +			} elsif (defined($tag{'epoch'})) { +				my $age = time - $tag{'epoch'}; +				$ref_item{'epoch'} = $tag{'epoch'}; +				$ref_item{'age'} = age_string($age); +			} +			$ref_item{'reftype'} = $tag{'type'}; +			$ref_item{'name'} = $tag{'name'}; +			$ref_item{'refid'} = $tag{'object'}; +		} elsif ($type eq "commit"){ +			%co = git_read_commit($ref_id); +			$ref_item{'reftype'} = "commit"; +			$ref_item{'name'} = $ref_file; +			$ref_item{'title'} = $co{'title'}; +			$ref_item{'refid'} = $ref_id; +			$ref_item{'epoch'} = $co{'committer_epoch'}; +			$ref_item{'age'} = $co{'age_string'}; +		} else { +			$ref_item{'reftype'} = $type; +			$ref_item{'name'} = $ref_file; +			$ref_item{'refid'} = $ref_id; +		} + +		push @reflist, \%ref_item; +	} +	# sort tags by age +	@reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; +	return \@reflist; +} + +## ---------------------------------------------------------------------- +## filesystem-related functions + +sub get_file_owner { +	my $path = shift; + +	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); +	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); +	if (!defined $gcos) { +		return undef; +	} +	my $owner = $gcos; +	$owner =~ s/[,;].*$//; +	return decode("utf8", $owner, Encode::FB_DEFAULT); +} + +## ...................................................................... +## mimetype related functions + +sub mimetype_guess_file { +	my $filename = shift; +	my $mimemap = shift; +	-r $mimemap or return undef; + +	my %mimemap; +	open(MIME, $mimemap) or return undef; +	while (<MIME>) { +		my ($mime, $exts) = split(/\t+/); +		if (defined $exts) { +			my @exts = split(/\s+/, $exts); +			foreach my $ext (@exts) { +				$mimemap{$ext} = $mime; +			} +		} +	} +	close(MIME); + +	$filename =~ /\.(.*?)$/; +	return $mimemap{$1}; +} + +sub mimetype_guess { +	my $filename = shift; +	my $mime; +	$filename =~ /\./ or return undef; + +	if ($mimetypes_file) { +		my $file = $mimetypes_file; +		#$file =~ m#^/# or $file = "$projectroot/$path/$file"; +		$mime = mimetype_guess_file($filename, $file); +	} +	$mime ||= mimetype_guess_file($filename, '/etc/mime.types'); +	return $mime; +} + +sub git_blob_plain_mimetype { +	my $fd = shift; +	my $filename = shift; + +	if ($filename) { +		my $mime = mimetype_guess($filename); +		$mime and return $mime; +	} + +	# just in case +	return $default_blob_plain_mimetype unless $fd; + +	if (-T $fd) { +		return 'text/plain' . +		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); +	} elsif (! $filename) { +		return 'application/octet-stream'; +	} elsif ($filename =~ m/\.png$/i) { +		return 'image/png'; +	} elsif ($filename =~ m/\.gif$/i) { +		return 'image/gif'; +	} elsif ($filename =~ m/\.jpe?g$/i) { +		return 'image/jpeg'; +	} else { +		return 'application/octet-stream'; +	} +} + +## ====================================================================== +## functions printing HTML: header, footer, error page + +sub git_header_html { +	my $status = shift || "200 OK"; +	my $expires = shift; + +	my $title = "$site_name git"; +	if (defined $project) { +		$title .= " - $project"; +		if (defined $action) { +			$title .= "/$action"; +			if (defined $file_name) { +				$title .= " - $file_name"; +				if ($action eq "tree" && $file_name !~ m|/$|) { +					$title .= "/"; +				} +			} +		} +	} +	my $content_type; +	# require explicit support from the UA if we are to send the page as +	# 'application/xhtml+xml', otherwise send it as plain old 'text/html'. +	# we have to do this because MSIE sometimes globs '*/*', pretending to +	# support xhtml+xml but choking when it gets what it asked for. +	if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { +		$content_type = 'application/xhtml+xml'; +	} else { +		$content_type = 'text/html'; +	} +	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); +	print <<EOF; +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> +<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> +<!-- git core binaries version $git_version --> +<head> +<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> +<meta name="robots" content="index, nofollow"/> +<title>$title</title> +<link rel="stylesheet" type="text/css" href="$stylesheet"/> +EOF +	if (defined $project) { +		printf('<link rel="alternate" title="%s log" '. +		       'href="%s" type="application/rss+xml"/>'."\n", +		       esc_param($project), +		       esc_param("$my_uri?p=$project;a=rss")); +	} + +	print "</head>\n" . +	      "<body>\n" . +	      "<div class=\"page_header\">\n" . +	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . +	      "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . +	      "</a>\n"; +	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; +	if (defined $project) { +		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); +		if (defined $action) { +			print " / $action"; +		} +		print "\n"; +		if (!defined $searchtext) { +			$searchtext = ""; +		} +		my $search_hash; +		if (defined $hash_base) { +			$search_hash = $hash_base; +		} elsif (defined $hash) { +			$search_hash = $hash; +		} else { +			$search_hash = "HEAD"; +		} +		$cgi->param("a", "search"); +		$cgi->param("h", $search_hash); +		print $cgi->startform(-method => "get", -action => $my_uri) . +		      "<div class=\"search\">\n" . +		      $cgi->hidden(-name => "p") . "\n" . +		      $cgi->hidden(-name => "a") . "\n" . +		      $cgi->hidden(-name => "h") . "\n" . +		      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . +		      "</div>" . +		      $cgi->end_form() . "\n"; +	} +	print "</div>\n"; +} + +sub git_footer_html { +	print "<div class=\"page_footer\">\n"; +	if (defined $project) { +		my $descr = git_read_description($project); +		if (defined $descr) { +			print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; +		} +		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; +	} else { +		print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; +	} +	print "</div>\n" . +	      "</body>\n" . +	      "</html>"; +} + +sub die_error { +	my $status = shift || "403 Forbidden"; +	my $error = shift || "Malformed query, file missing or permission denied"; + +	git_header_html($status); +	print "<div class=\"page_body\">\n" . +	      "<br/><br/>\n" . +	      "$status - $error\n" . +	      "<br/>\n" . +	      "</div>\n"; +	git_footer_html(); +	exit; +} + +## ---------------------------------------------------------------------- +## functions printing or outputting HTML: navigation + +sub git_page_nav { +	my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; +	$extra = '' if !defined $extra; # pager or formats + +	my @navs = qw(summary shortlog log commit commitdiff tree); +	if ($suppress) { +		@navs = grep { $_ ne $suppress } @navs; +	} + +	my %arg = map { $_, ''} @navs; +	if (defined $head) { +		for (qw(commit commitdiff)) { +			$arg{$_} = ";h=$head"; +		} +		if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { +			for (qw(shortlog log)) { +				$arg{$_} = ";h=$head"; +			} +		} +	} +	$arg{tree} .= ";h=$treehead" if defined $treehead; +	$arg{tree} .= ";hb=$treebase" if defined $treebase; + +	print "<div class=\"page_nav\">\n" . +		(join " | ", +		 map { $_ eq $current +					 ? $_ +					 : $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$_$arg{$_}")}, "$_") +				 } +		 @navs); +	print "<br/>\n$extra<br/>\n" . +	      "</div>\n"; +} + +sub git_get_paging_nav { +	my ($action, $hash, $head, $page, $nrevs) = @_; +	my $paging_nav; + + +	if ($hash ne $head || $page) { +		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD"); +	} else { +		$paging_nav .= "HEAD"; +	} + +	if ($page > 0) { +		$paging_nav .= " ⋅ " . +			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)), +							 -accesskey => "p", -title => "Alt-p"}, "prev"); +	} else { +		$paging_nav .= " ⋅ prev"; +	} + +	if ($nrevs >= (100 * ($page+1)-1)) { +		$paging_nav .= " ⋅ " . +			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)), +							 -accesskey => "n", -title => "Alt-n"}, "next"); +	} else { +		$paging_nav .= " ⋅ next"; +	} + +	return $paging_nav; +} + +## ...................................................................... +## functions printing or outputting HTML: div + +sub git_header_div { +	my ($action, $title, $hash, $hash_base) = @_; +	my $rest = ''; + +	$rest .= ";h=$hash" if $hash; +	$rest .= ";hb=$hash_base" if $hash_base; + +	print "<div class=\"header\">\n" . +	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action$rest"), +	               -class => "title"}, $title ? $title : $action) . "\n" . +	      "</div>\n"; +} + +sub git_print_page_path { +	my $name = shift; +	my $type = shift; + +	if (!defined $name) { +		print "<div class=\"page_path\"><b>/</b></div>\n"; +	} elsif (defined $type && $type eq 'blob') { +		print "<div class=\"page_path\"><b>" . +			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n"; +	} else { +		print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; +	} +} + +## ...................................................................... +## functions printing large fragments of HTML + +sub git_shortlog_body { +	# uses global variable $project +	my ($revlist, $from, $to, $refs, $extra) = @_; +	$from = 0 unless defined $from; +	$to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + +	print "<table class=\"shortlog\" cellspacing=\"0\">\n"; +	my $alternate = 0; +	for (my $i = $from; $i <= $to; $i++) { +		my $commit = $revlist->[$i]; +		#my $ref = defined $refs ? git_get_referencing($refs, $commit) : ''; +		my $ref = git_get_referencing($refs, $commit); +		my %co = git_read_commit($commit); +		if ($alternate) { +			print "<tr class=\"dark\">\n"; +		} else { +			print "<tr class=\"light\">\n"; +		} +		$alternate ^= 1; +		# git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . +		print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . +		      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . +		      "<td>"; +		if (length($co{'title_short'}) < length($co{'title'})) { +			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), +			               -class => "list", -title => "$co{'title'}"}, +			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); +		} else { +			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), +			               -class => "list"}, +			      "<b>" . esc_html($co{'title'}) . "$ref</b>"); +		} +		print "</td>\n" . +		      "<td class=\"link\">" . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . +		      "</td>\n" . +		      "</tr>\n"; +	} +	if (defined $extra) { +		print "<tr>\n" . +		      "<td colspan=\"4\">$extra</td>\n" . +		      "</tr>\n"; +	} +	print "</table>\n"; +} + +sub git_tags_body { +	# uses global variable $project +	my ($taglist, $from, $to, $extra) = @_; +	$from = 0 unless defined $from; +	$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + +	print "<table class=\"tags\" cellspacing=\"0\">\n"; +	my $alternate = 0; +	for (my $i = $from; $i <= $to; $i++) { +		my $entry = $taglist->[$i]; +		my %tag = %$entry; +		my $comment_lines = $tag{'comment'}; +		my $comment = shift @$comment_lines; +		my $comment_short; +		if (defined $comment) { +			$comment_short = chop_str($comment, 30, 5); +		} +		if ($alternate) { +			print "<tr class=\"dark\">\n"; +		} else { +			print "<tr class=\"light\">\n"; +		} +		$alternate ^= 1; +		print "<td><i>$tag{'age'}</i></td>\n" . +		      "<td>" . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), +		               -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . +		      "</td>\n" . +		      "<td>"; +		if (defined $comment) { +			if (length($comment_short) < length($comment)) { +				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), +				               -class => "list", -title => $comment}, $comment_short); +			} else { +				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"), +				               -class => "list"}, $comment); +			} +		} +		print "</td>\n" . +		      "<td class=\"selflink\">"; +		if ($tag{'type'} eq "tag") { +			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag"); +		} else { +			print " "; +		} +		print "</td>\n" . +		      "<td class=\"link\">" . " | " . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); +		if ($tag{'reftype'} eq "commit") { +			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . +			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); +		} elsif ($tag{'reftype'} eq "blob") { +			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw"); +		} +		print "</td>\n" . +		      "</tr>"; +	} +	if (defined $extra) { +		print "<tr>\n" . +		      "<td colspan=\"5\">$extra</td>\n" . +		      "</tr>\n"; +	} +	print "</table>\n"; +} + +sub git_heads_body { +	# uses global variable $project +	my ($taglist, $head, $from, $to, $extra) = @_; +	$from = 0 unless defined $from; +	$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + +	print "<table class=\"heads\" cellspacing=\"0\">\n"; +	my $alternate = 0; +	for (my $i = $from; $i <= $to; $i++) { +		my $entry = $taglist->[$i]; +		my %tag = %$entry; +		my $curr = $tag{'id'} eq $head; +		if ($alternate) { +			print "<tr class=\"dark\">\n"; +		} else { +			print "<tr class=\"light\">\n"; +		} +		$alternate ^= 1; +		print "<td><i>$tag{'age'}</i></td>\n" . +		      ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), +		               -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . +		      "</td>\n" . +		      "<td class=\"link\">" . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . +		      "</td>\n" . +		      "</tr>"; +	} +	if (defined $extra) { +		print "<tr>\n" . +		      "<td colspan=\"3\">$extra</td>\n" . +		      "</tr>\n"; +	} +	print "</table>\n"; +} + +## ---------------------------------------------------------------------- +## functions printing large fragments, format as one of arguments +  sub git_diff_print {  	my $from = shift;  	my $from_name = shift; @@ -610,7 +1219,7 @@ sub git_diff_print {  	if (defined $from) {  		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";  		open my $fd2, "> $from_tmp"; -		open my $fd, "-|", "$GIT cat-file blob $from"; +		open my $fd, "-|", $GIT, "cat-file", "blob", $from;  		my @file = <$fd>;  		print $fd2 @file;  		close $fd2; @@ -621,7 +1230,7 @@ sub git_diff_print {  	if (defined $to) {  		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";  		open my $fd2, "> $to_tmp"; -		open my $fd, "-|", "$GIT cat-file blob $to"; +		open my $fd, "-|", $GIT, "cat-file", "blob", $to;  		my @file = <$fd>;  		print $fd2 @file;  		close $fd2; @@ -635,7 +1244,7 @@ sub git_diff_print {  		$/ = "\n";  	} else {  		while (my $line = <$fd>) { -			chomp($line); +			chomp $line;  			my $char = substr($line, 0, 1);  			my $diff_class = "";  			if ($char eq '+') { @@ -648,12 +1257,7 @@ sub git_diff_print {  				# skip errors  				next;  			} -			while ((my $pos = index($line, "\t")) != -1) { -				if (my $count = (8 - (($pos-1) % 8))) { -					my $spaces = ' ' x $count; -					$line =~ s/\t/$spaces/; -				} -			} +			$line = untabify($line);  			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";  		}  	} @@ -667,196 +1271,21 @@ sub git_diff_print {  	}  } -sub mode_str { -	my $mode = oct shift; - -	if (S_ISDIR($mode & S_IFMT)) { -		return 'drwxr-xr-x'; -	} elsif (S_ISLNK($mode)) { -		return 'lrwxrwxrwx'; -	} elsif (S_ISREG($mode)) { -		# git cares only about the executable bit -		if ($mode & S_IXUSR) { -			return '-rwxr-xr-x'; -		} else { -			return '-rw-r--r--'; -		}; -	} else { -		return '----------'; -	} -} - -sub chop_str { -	my $str = shift; -	my $len = shift; -	my $add_len = shift || 10; - -	# allow only $len chars, but don't cut a word if it would fit in $add_len -	# if it doesn't fit, cut it if it's still longer than the dots we would add -	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; -	my $body = $1; -	my $tail = $2; -	if (length($tail) > 4) { -		$tail = " ..."; -	} -	return "$body$tail"; -} - -sub file_type { -	my $mode = oct shift; - -	if (S_ISDIR($mode & S_IFMT)) { -		return "directory"; -	} elsif (S_ISLNK($mode)) { -		return "symlink"; -	} elsif (S_ISREG($mode)) { -		return "file"; -	} else { -		return "unknown"; -	} -} - -sub format_log_line_html { -	my $line = shift; - -	$line = esc_html($line); -	$line =~ s/ / /g; -	if ($line =~ m/([0-9a-fA-F]{40})/) { -		my $hash_text = $1; -		if (git_get_type($hash_text) eq "commit") { -			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); -			$line =~ s/$hash_text/$link/; -		} -	} -	return $line; -} -sub date_str { -	my $epoch = shift; -	my $tz = shift || "-0000"; +## ====================================================================== +## ====================================================================== +## actions -	my %date; -	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); -	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); -	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); -	$date{'hour'} = $hour; -	$date{'minute'} = $min; -	$date{'mday'} = $mday; -	$date{'day'} = $days[$wday]; -	$date{'month'} = $months[$mon]; -	$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; -	$date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; - -	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; -	my $local = $epoch + ((int $1 + ($2/60)) * 3600); -	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); -	$date{'hour_local'} = $hour; -	$date{'minute_local'} = $min; -	$date{'tz_local'} = $tz; -	return %date; -} - -# git-logo (cached in browser for one day) -sub git_logo { -	binmode STDOUT, ':raw'; -	print $cgi->header(-type => 'image/png', -expires => '+1d'); -	# cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g' -	print	"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . -		"\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . -		"\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . -		"\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . -		"\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . -		"\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . -		"\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . -		"\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . -		"\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . -		"\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . -		"\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . -		"\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . -		"\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; -} - -sub get_file_owner { -	my $path = shift; - -	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); -	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); -	if (!defined $gcos) { -		return undef; -	} -	my $owner = $gcos; -	$owner =~ s/[,;].*$//; -	return decode("utf8", $owner, Encode::FB_DEFAULT); -} - -sub git_read_projects { -	my @list; - -	if (-d $projects_list) { -		# search in directory -		my $dir = $projects_list; -		opendir my ($dh), $dir or return undef; -		while (my $dir = readdir($dh)) { -			if (-e "$projectroot/$dir/HEAD") { -				my $pr = { -					path => $dir, -				}; -				push @list, $pr -			} -		} -		closedir($dh); -	} elsif (-f $projects_list) { -		# read from file(url-encoded): -		# 'git%2Fgit.git Linus+Torvalds' -		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' -		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' -		open my ($fd), $projects_list or return undef; -		while (my $line = <$fd>) { -			chomp $line; -			my ($path, $owner) = split ' ', $line; -			$path = unescape($path); -			$owner = unescape($owner); -			if (!defined $path) { -				next; -			} -			if (-e "$projectroot/$path/HEAD") { -				my $pr = { -					path => $path, -					owner => decode("utf8", $owner, Encode::FB_DEFAULT), -				}; -				push @list, $pr -			} -		} -		close $fd; +sub git_project_list { +	my $order = $cgi->param('o'); +	if (defined $order && $order !~ m/project|descr|owner|age/) { +		die_error(undef, "Unknown order parameter");  	} -	@list = sort {$a->{'path'} cmp $b->{'path'}} @list; -	return @list; -} - -sub git_get_project_config { -	my $key = shift; - -	return unless ($key); -	$key =~ s/^gitweb\.//; -	return if ($key =~ m/\W/); -	my $val = qx($GIT repo-config --get gitweb.$key); -	return ($val); -} - -sub git_get_project_config_bool { -	my $val = git_get_project_config (@_); -	if ($val and $val =~ m/true|yes|on/) { -		return (1); -	} -	return; # implicit false -} - -sub git_project_list {  	my @list = git_read_projects();  	my @projects;  	if (!@list) { -		die_error(undef, "No project found."); +		die_error(undef, "No projects found");  	}  	foreach my $pr (@list) {  		my $head = git_read_head($pr->{'path'}); @@ -878,6 +1307,7 @@ sub git_project_list {  		}  		push @projects, $pr;  	} +  	git_header_html();  	if (-f $home_text) {  		print "<div class=\"index_include\">\n"; @@ -888,29 +1318,42 @@ sub git_project_list {  	}  	print "<table class=\"project_list\">\n" .  	      "<tr>\n"; -	if (!defined($order) || (defined($order) && ($order eq "project"))) { +	$order ||= "project"; +	if ($order eq "project") {  		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;  		print "<th>Project</th>\n";  	} else { -		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n"; +		print "<th>" . +		      $cgi->a({-href => "$my_uri?" . esc_param("o=project"), +		               -class => "header"}, "Project") . +		      "</th>\n";  	} -	if (defined($order) && ($order eq "descr")) { +	if ($order eq "descr") {  		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;  		print "<th>Description</th>\n";  	} else { -		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n"; +		print "<th>" . +		      $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), +		               -class => "header"}, "Description") . +		      "</th>\n";  	} -	if (defined($order) && ($order eq "owner")) { +	if ($order eq "owner") {  		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;  		print "<th>Owner</th>\n";  	} else { -		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n"; +		print "<th>" . +		      $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), +		               -class => "header"}, "Owner") . +		      "</th>\n";  	} -	if (defined($order) && ($order eq "age")) { +	if ($order eq "age") {  		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;  		print "<th>Last Change</th>\n";  	} else { -		print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n"; +		print "<th>" . +		      $cgi->a({-href => "$my_uri?" . esc_param("o=age"), +		               -class => "header"}, "Last Change") . +		      "</th>\n";  	}  	print "<th></th>\n" .  	      "</tr>\n"; @@ -922,14 +1365,16 @@ sub git_project_list {  			print "<tr class=\"light\">\n";  		}  		$alternate ^= 1; -		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . -		      "<td>$pr->{'descr'}</td>\n" . +		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), +		                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . +		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .  		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n"; -		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" . +		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . +		      $pr->{'commit'}{'age_string'} . "</td>\n" .  		      "<td class=\"link\">" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary")   . " | " . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " . +		      $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") .  		      "</td>\n" .  		      "</tr>\n";  	} @@ -937,89 +1382,6 @@ sub git_project_list {  	git_footer_html();  } -sub read_info_ref { -	my $type = shift || ""; -	my %refs; -	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11 -	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{} -	open my $fd, "$projectroot/$project/info/refs" or return; -	while (my $line = <$fd>) { -		chomp($line); -		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { -			if (defined $refs{$1}) { -				$refs{$1} .= " / $2"; -			} else { -				$refs{$1} = $2; -			} -		} -	} -	close $fd or return; -	return \%refs; -} - -sub git_read_refs { -	my $ref_dir = shift; -	my @reflist; - -	my @refs; -	opendir my $dh, "$projectroot/$project/$ref_dir"; -	while (my $dir = readdir($dh)) { -		if ($dir =~ m/^\./) { -			next; -		} -		if (-d "$projectroot/$project/$ref_dir/$dir") { -			opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; -			my @subdirs = grep !m/^\./, readdir $dh2; -			closedir($dh2); -			foreach my $subdir (@subdirs) { -				push @refs, "$dir/$subdir" -			} -			next; -		} -		push @refs, $dir; -	} -	closedir($dh); -	foreach my $ref_file (@refs) { -		my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); -		my $type = git_get_type($ref_id) || next; -		my %ref_item; -		my %co; -		$ref_item{'type'} = $type; -		$ref_item{'id'} = $ref_id; -		$ref_item{'epoch'} = 0; -		$ref_item{'age'} = "unknown"; -		if ($type eq "tag") { -			my %tag = git_read_tag($ref_id); -			$ref_item{'comment'} = $tag{'comment'}; -			if ($tag{'type'} eq "commit") { -				%co = git_read_commit($tag{'object'}); -				$ref_item{'epoch'} = $co{'committer_epoch'}; -				$ref_item{'age'} = $co{'age_string'}; -			} elsif (defined($tag{'epoch'})) { -				my $age = time - $tag{'epoch'}; -				$ref_item{'epoch'} = $tag{'epoch'}; -				$ref_item{'age'} = age_string($age); -			} -			$ref_item{'reftype'} = $tag{'type'}; -			$ref_item{'name'} = $tag{'name'}; -			$ref_item{'refid'} = $tag{'object'}; -		} elsif ($type eq "commit"){ -			%co = git_read_commit($ref_id); -			$ref_item{'reftype'} = "commit"; -			$ref_item{'name'} = $ref_file; -			$ref_item{'title'} = $co{'title'}; -			$ref_item{'refid'} = $ref_id; -			$ref_item{'epoch'} = $co{'committer_epoch'}; -			$ref_item{'age'} = $co{'age_string'}; -		} - -		push @reflist, \%ref_item; -	} -	# sort tags by age -	@reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; -	return \@reflist; -} -  sub git_summary {  	my $descr = git_read_description($project) || "none";  	my $head = git_read_head($project); @@ -1047,188 +1409,46 @@ sub git_summary {  	my $refs = read_info_ref();  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -	      "summary". -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree")}, "tree") . -	      "<br/><br/>\n" . -	      "</div>\n"; +	git_page_nav('summary','', $head); +  	print "<div class=\"title\"> </div>\n";  	print "<table cellspacing=\"0\">\n" .  	      "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .  	      "<tr><td>owner</td><td>$owner</td></tr>\n" .  	      "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .  	      "</table>\n"; -	open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed."); -	my (@revlist) = map { chomp; $_ } <$fd>; + +	open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project) +		or die_error(undef, "Open git-rev-list failed"); +	my @revlist = map { chomp; $_ } <$fd>;  	close $fd; -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") . -	      "</div>\n"; -	my $i = 16; -	print "<table cellspacing=\"0\">\n"; -	my $alternate = 0; -	foreach my $commit (@revlist) { -		my %co = git_read_commit($commit); -		my %ad = date_str($co{'author_epoch'}); -		if ($alternate) { -			print "<tr class=\"dark\">\n"; -		} else { -			print "<tr class=\"light\">\n"; -		} -		$alternate ^= 1; -		if ($i-- > 0) { -			my $ref = ""; -			if (defined $refs->{$commit}) { -				$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; -			} -			print "<td><i>$co{'age_string'}</i></td>\n" . -			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . -			      "<td>"; -			if (length($co{'title_short'}) < length($co{'title'})) { -				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, -				      "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); -			} else { -				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, -				      "<b>" . esc_html($co{'title'}) . "$ref</b>"); -			} -			print "</td>\n" . -			      "<td class=\"link\">" . -			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . -			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . -			      "</td>\n" . -			      "</tr>"; -		} else { -			print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...") . "</td>\n" . -			"</tr>"; -			last; -		} -	} -	print "</table\n>"; +	git_header_div('shortlog'); +	git_shortlog_body(\@revlist, 0, 15, $refs, +	                  $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "..."));  	my $taglist = git_read_refs("refs/tags");  	if (defined @$taglist) { -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags"), -class => "title"}, "tags") . -		      "</div>\n"; -		my $i = 16; -		print "<table cellspacing=\"0\">\n"; -		my $alternate = 0; -		foreach my $entry (@$taglist) { -			my %tag = %$entry; -			my $comment_lines = $tag{'comment'}; -			my $comment = shift @$comment_lines; -			if (defined($comment)) { -				$comment = chop_str($comment, 30, 5); -			} -			if ($alternate) { -				print "<tr class=\"dark\">\n"; -			} else { -				print "<tr class=\"light\">\n"; -			} -			$alternate ^= 1; -			if ($i-- > 0) { -				print "<td><i>$tag{'age'}</i></td>\n" . -				      "<td>" . -				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"}, -				      "<b>" . esc_html($tag{'name'}) . "</b>") . -				      "</td>\n" . -				      "<td>"; -				if (defined($comment)) { -				      print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment)); -				} -				print "</td>\n" . -				      "<td class=\"link\">"; -				if ($tag{'type'} eq "tag") { -				      print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; -				} -				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); -				if ($tag{'reftype'} eq "commit") { -				      print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . -					    " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); -				} -				print "</td>\n" . -				      "</tr>"; -			} else { -				print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...") . "</td>\n" . -				"</tr>"; -				last; -			} -		} -		print "</table\n>"; +		git_header_div('tags'); +		git_tags_body($taglist, 0, 15, +		              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "..."));  	}  	my $headlist = git_read_refs("refs/heads");  	if (defined @$headlist) { -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads"), -class => "title"}, "heads") . -		      "</div>\n"; -		my $i = 16; -		print "<table cellspacing=\"0\">\n"; -		my $alternate = 0; -		foreach my $entry (@$headlist) { -			my %tag = %$entry; -			if ($alternate) { -				print "<tr class=\"dark\">\n"; -			} else { -				print "<tr class=\"light\">\n"; -			} -			$alternate ^= 1; -			if ($i-- > 0) { -				print "<td><i>$tag{'age'}</i></td>\n" . -				      "<td>" . -				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, -				      "<b>" . esc_html($tag{'name'}) . "</b>") . -				      "</td>\n" . -				      "<td class=\"link\">" . -				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . -				      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . -				      "</td>\n" . -				      "</tr>"; -			} else { -				print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...") . "</td>\n" . -				"</tr>"; -				last; -			} -		} -		print "</table\n>"; +		git_header_div('heads'); +		git_heads_body($headlist, $head, 0, 15, +		               $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));  	} -	git_footer_html(); -} - -sub git_print_page_path { -	my $name = shift; -	my $type = shift; -	if (!defined $name) { -		print "<div class=\"page_path\"><b>/</b></div>\n"; -	} elsif ($type =~ "blob") { -		print "<div class=\"page_path\"><b>" . -			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n"; -	} else { -		print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; -	} +	git_footer_html();  }  sub git_tag {  	my $head = git_read_head($project);  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . -	      "<br/>\n" . -	      "</div>\n"; +	git_page_nav('','', $head,undef,$head);  	my %tag = git_read_tag($hash); -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" . -	      "</div>\n"; +	git_header_div('commit', esc_html($tag{'name'}), $hash);  	print "<div class=\"title_text\">\n" .  	      "<table cellspacing=\"0\">\n" .  	      "<tr>\n" . @@ -1255,10 +1475,10 @@ sub git_tag {  sub git_blame2 {  	my $fd;  	my $ftype; -	die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); +	die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame'));  	die_error('404 Not Found', "File name not defined") if (!$file_name);  	$hash_base ||= git_read_head($project); -	die_error(undef, "Reading commit failed") unless ($hash_base); +	die_error(undef, "Couldn't find base commit") unless ($hash_base);  	my %co = git_read_commit($hash_base)  		or die_error(undef, "Reading commit failed");  	if (!defined $hash) { @@ -1267,26 +1487,18 @@ sub git_blame2 {  	}  	$ftype = git_get_type($hash);  	if ($ftype !~ "blob") { -		die_error("400 Bad Request", "object is not a blob"); +		die_error("400 Bad Request", "Object is not a blob");  	}  	open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) -		or die_error(undef, "Open failed"); +		or die_error(undef, "Open git-blame failed");  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; -	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n"; -	print "</div>\n". -		"<div>" . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . -		"</div>\n"; +	my $formats_nav = +		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . +		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); +	git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); +	git_header_div('commit', esc_html($co{'title'}), $hash_base);  	git_print_page_path($file_name, $ftype); -	my @rev_color = (qw(light dark)); +	my @rev_color = (qw(light2 dark2));  	my $num_colors = scalar(@rev_color);  	my $current_color = 0;  	my $last_rev; @@ -1321,33 +1533,25 @@ sub git_blame2 {  sub git_blame {  	my $fd; -	die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); -	die_error('404 Not Found', "What file will it be, master?") if (!$file_name); +	die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame')); +	die_error('404 Not Found', "File name not defined") if (!$file_name);  	$hash_base ||= git_read_head($project); -	die_error(undef, "Reading commit failed.") unless ($hash_base); +	die_error(undef, "Couldn't find base commit") unless ($hash_base);  	my %co = git_read_commit($hash_base) -		or die_error(undef, "Reading commit failed."); +		or die_error(undef, "Reading commit failed");  	if (!defined $hash) {  		$hash = git_get_hash_by_path($hash_base, $file_name, "blob") -			or die_error(undef, "Error lookup file."); +			or die_error(undef, "Error lookup file");  	}  	open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) -		or die_error(undef, "Open failed."); +		or die_error(undef, "Open git-annotate failed");  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; -	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . -		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n"; -	print "</div>\n". -		"<div>" . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . -		"</div>\n"; -	git_print_page_path($file_name); +	my $formats_nav = +		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . +		" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head"); +	git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); +	git_header_div('commit', esc_html($co{'title'}), $hash_base); +	git_print_page_path($file_name, 'blob');  	print "<div class=\"page_body\">\n";  	print <<HTML;  <table class="blame"> @@ -1393,13 +1597,8 @@ HTML  		$age_class  = age_class($age);  		$author     = esc_html ($author);  		$author     =~ s/ / /g; -		# escape tabs -		while ((my $pos = index($data, "\t")) != -1) { -			if (my $count = (8 - ($pos % 8))) { -				my $spaces = ' ' x $count; -				$data =~ s/\t/$spaces/; -			} -		} + +		$data = untabify($data);  		$data = esc_html ($data);  		print <<HTML; @@ -1421,207 +1620,42 @@ HTML  sub git_tags {  	my $head = git_read_head($project);  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . -	      "<br/>\n" . -	      "</div>\n"; +	git_page_nav('','', $head,undef,$head); +	git_header_div('summary', $project); +  	my $taglist = git_read_refs("refs/tags"); -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . -	      "</div>\n"; -	print "<table cellspacing=\"0\">\n"; -	my $alternate = 0;  	if (defined @$taglist) { -		foreach my $entry (@$taglist) { -			my %tag = %$entry; -			my $comment_lines = $tag{'comment'}; -			my $comment = shift @$comment_lines; -			if (defined($comment)) { -				$comment = chop_str($comment, 30, 5); -			} -			if ($alternate) { -				print "<tr class=\"dark\">\n"; -			} else { -				print "<tr class=\"light\">\n"; -			} -			$alternate ^= 1; -			print "<td><i>$tag{'age'}</i></td>\n" . -			      "<td>" . -			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"}, -			      "<b>" . esc_html($tag{'name'}) . "</b>") . -			      "</td>\n" . -			      "<td>"; -			if (defined($comment)) { -			      print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment); -			} -			print "</td>\n" . -			      "<td class=\"link\">"; -			if ($tag{'type'} eq "tag") { -			      print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; -			} -			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); -			if ($tag{'reftype'} eq "commit") { -			      print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . -				    " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); -			} -			print "</td>\n" . -			      "</tr>"; -		} +		git_tags_body($taglist);  	} -	print "</table\n>";  	git_footer_html();  }  sub git_heads {  	my $head = git_read_head($project);  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . -	      "<br/>\n" . -	      "</div>\n"; +	git_page_nav('','', $head,undef,$head); +	git_header_div('summary', $project); +  	my $taglist = git_read_refs("refs/heads"); -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . -	      "</div>\n"; -	print "<table cellspacing=\"0\">\n"; -	my $alternate = 0;  	if (defined @$taglist) { -		foreach my $entry (@$taglist) { -			my %tag = %$entry; -			if ($alternate) { -				print "<tr class=\"dark\">\n"; -			} else { -				print "<tr class=\"light\">\n"; -			} -			$alternate ^= 1; -			print "<td><i>$tag{'age'}</i></td>\n" . -			      "<td>" . -			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . -			      "</td>\n" . -			      "<td class=\"link\">" . -			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . -			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . -			      "</td>\n" . -			      "</tr>"; -		} +		git_heads_body($taglist, $head);  	} -	print "</table\n>";  	git_footer_html();  } -sub git_get_hash_by_path { -	my $base = shift; -	my $path = shift || return undef; - -	my $tree = $base; -	my @parts = split '/', $path; -	while (my $part = shift @parts) { -		open my $fd, "-|", "$GIT ls-tree $tree" or die_error(undef, "Open git-ls-tree failed."); -		my (@entries) = map { chomp; $_ } <$fd>; -		close $fd or return undef; -		foreach my $line (@entries) { -			#'100644	blob	0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c' -			$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; -			my $t_mode = $1; -			my $t_type = $2; -			my $t_hash = $3; -			my $t_name = validate_input(unquote($4)); -			if ($t_name eq $part) { -				if (!(@parts)) { -					return $t_hash; -				} -				if ($t_type eq "tree") { -					$tree = $t_hash; -				} -				last; -			} -		} -	} -} - -sub mimetype_guess_file { -	my $filename = shift; -	my $mimemap = shift; -	-r $mimemap or return undef; - -	my %mimemap; -	open(MIME, $mimemap) or return undef; -	while (<MIME>) { -		my ($mime, $exts) = split(/\t+/); -		my @exts = split(/\s+/, $exts); -		foreach my $ext (@exts) { -			$mimemap{$ext} = $mime; -		} -	} -	close(MIME); - -	$filename =~ /\.(.*?)$/; -	return $mimemap{$1}; -} - -sub mimetype_guess { -	my $filename = shift; -	my $mime; -	$filename =~ /\./ or return undef; - -	if ($mimetypes_file) { -		my $file = $mimetypes_file; -		#$file =~ m#^/# or $file = "$projectroot/$path/$file"; -		$mime = mimetype_guess_file($filename, $file); -	} -	$mime ||= mimetype_guess_file($filename, '/etc/mime.types'); -	return $mime; -} - -sub git_blob_plain_mimetype { -	my $fd = shift; -	my $filename = shift; - -	if ($filename) { -		my $mime = mimetype_guess($filename); -		$mime and return $mime; -	} - -	# just in case -	return $default_blob_plain_mimetype unless $fd; - -	if (-T $fd) { -		return 'text/plain' . -		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); -	} elsif (! $filename) { -		return 'application/octet-stream'; -	} elsif ($filename =~ m/\.png$/i) { -		return 'image/png'; -	} elsif ($filename =~ m/\.gif$/i) { -		return 'image/gif'; -	} elsif ($filename =~ m/\.jpe?g$/i) { -		return 'image/jpeg'; -	} else { -		return 'application/octet-stream'; -	} -} -  sub git_blob_plain {  	if (!defined $hash) { -                if (defined $file_name) { -                        my $base = $hash_base || git_read_head($project); -                        $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); -                } else { -                        die_error(undef, "No file name defined."); -                } -        } +		if (defined $file_name) { +			my $base = $hash_base || git_read_head($project); +			$hash = git_get_hash_by_path($base, $file_name, "blob") +				or die_error(undef, "Error lookup file"); +		} else { +			die_error(undef, "No file name defined"); +		} +	}  	my $type = shift; -	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash"); +	open my $fd, "-|", $GIT, "cat-file", "blob", $hash +		or die_error(undef, "Couldn't cat $file_name, $hash");  	$type ||= git_blob_plain_mimetype($fd, $file_name); @@ -1644,42 +1678,37 @@ sub git_blob_plain {  sub git_blob {  	if (!defined $hash) { -                if (defined $file_name) { -                        my $base = $hash_base || git_read_head($project); -                        $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); -                } else { -                        die_error(undef, "No file name defined."); -                } -        } +		if (defined $file_name) { +			my $base = $hash_base || git_read_head($project); +			$hash = git_get_hash_by_path($base, $file_name, "blob") +				or die_error(undef, "Error lookup file"); +		} else { +			die_error(undef, "No file name defined"); +		} +	}  	my $have_blame = git_get_project_config_bool ('blame'); -	open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed."); +	open my $fd, "-|", $GIT, "cat-file", "blob", $hash +		or die_error(undef, "Couldn't cat $file_name, $hash");  	my $mimetype = git_blob_plain_mimetype($fd, $file_name);  	if ($mimetype !~ m/^text\//) {  		close $fd;  		return git_blob_plain($mimetype);  	}  	git_header_html(); +	my $formats_nav = '';  	if (defined $hash_base && (my %co = git_read_commit($hash_base))) { -		print "<div class=\"page_nav\">\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";  		if (defined $file_name) {  			if ($have_blame) { -				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") .  " | "; +				$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | ";  			} -			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . -			" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "<br/>\n"; +			$formats_nav .= +				$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . +				" | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head");  		} else { -			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n"; +			$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");  		} -		print "</div>\n". -		       "<div>" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . -		      "</div>\n"; +		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); +		git_header_div('commit', esc_html($co{'title'}), $hash_base);  	} else {  		print "<div class=\"page_nav\">\n" .  		      "<br/><br/></div>\n" . @@ -1691,12 +1720,7 @@ sub git_blob {  	while (my $line = <$fd>) {  		chomp $line;  		$nr++; -		while ((my $pos = index($line, "\t")) != -1) { -			if (my $count = (8 - ($pos % 8))) { -				my $spaces = ' ' x $count; -				$line =~ s/\t/$spaces/; -			} -		} +		$line = untabify($line);  		printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line);  	}  	close $fd or print "Reading blob failed.\n"; @@ -1716,33 +1740,22 @@ sub git_tree {  		}  	}  	$/ = "\0"; -	open my $fd, "-|", "$GIT ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed."); -	chomp (my (@entries) = <$fd>); -	close $fd or die_error(undef, "Reading tree failed."); +	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash +		or die_error(undef, "Open git-ls-tree failed"); +	my @entries = map { chomp; $_ } <$fd>; +	close $fd or die_error(undef, "Reading tree failed");  	$/ = "\n";  	my $refs = read_info_ref(); -	my $ref = ""; -	if (defined $refs->{$hash_base}) { -		$ref = " <span class=\"tag\">" . esc_html($refs->{$hash_base}) . "</span>"; -	} +	my $ref = git_get_referencing($refs, $hash_base);  	git_header_html();  	my $base_key = "";  	my $base = ""; +	my $have_blame = git_get_project_config_bool ('blame');  	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {  		$base_key = ";hb=$hash_base"; -		print "<div class=\"page_nav\">\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash_base")}, "shortlog") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash_base")}, "log") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . -		      " | tree" . -		      "<br/><br/>\n" . -		      "</div>\n"; -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . -		      "</div>\n"; +		git_page_nav('tree','', $hash_base); +		git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);  	} else {  		print "<div class=\"page_nav\">\n";  		print "<br/><br/></div>\n"; @@ -1751,7 +1764,7 @@ sub git_tree {  	if (defined $file_name) {  		$base = esc_html("$file_name/");  	} -	git_print_page_path($file_name); +	git_print_page_path($file_name, 'tree');  	print "<div class=\"page_body\">\n";  	print "<table cellspacing=\"0\">\n";  	my $alternate = 0; @@ -1774,9 +1787,11 @@ sub git_tree {  			      $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)) .  			      "</td>\n" .  			      "<td class=\"link\">" . -			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") . -#			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") . -			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . +			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob"); +			if ($have_blame) { +				print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame"); +			} +			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") .  			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") .  			      "</td>\n";  		} elsif ($t_type eq "tree") { @@ -1795,97 +1810,6 @@ sub git_tree {  	git_footer_html();  } -sub git_rss { -	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ -	open my $fd, "-|", "$GIT rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed."); -	my (@revlist) = map { chomp; $_ } <$fd>; -	close $fd or die_error(undef, "Reading rev-list failed."); -	print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); -	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". -	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; -	print "<channel>\n"; -	print "<title>$project</title>\n". -	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". -	      "<description>$project log</description>\n". -	      "<language>en</language>\n"; - -	for (my $i = 0; $i <= $#revlist; $i++) { -		my $commit = $revlist[$i]; -		my %co = git_read_commit($commit); -		# we read 150, we always show 30 and the ones more recent than 48 hours -		if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { -			last; -		} -		my %cd = date_str($co{'committer_epoch'}); -		open $fd, "-|", "$GIT diff-tree -r $co{'parent'} $co{'id'}" or next; -		my @difftree = map { chomp; $_ } <$fd>; -		close $fd or next; -		print "<item>\n" . -		      "<title>" . -		      sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . -		      "</title>\n" . -		      "<author>" . esc_html($co{'author'}) . "</author>\n" . -		      "<pubDate>$cd{'rfc2822'}</pubDate>\n" . -		      "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . -		      "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . -		      "<description>" . esc_html($co{'title'}) . "</description>\n" . -		      "<content:encoded>" . -		      "<![CDATA[\n"; -		my $comment = $co{'comment'}; -		foreach my $line (@$comment) { -			$line = decode("utf8", $line, Encode::FB_DEFAULT); -			print "$line<br/>\n"; -		} -		print "<br/>\n"; -		foreach my $line (@difftree) { -			if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { -				next; -			} -			my $file = validate_input(unquote($7)); -			$file = decode("utf8", $file, Encode::FB_DEFAULT); -			print "$file<br/>\n"; -		} -		print "]]>\n" . -		      "</content:encoded>\n" . -		      "</item>\n"; -	} -	print "</channel></rss>"; -} - -sub git_opml { -	my @list = git_read_projects(); - -	print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); -	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". -	      "<opml version=\"1.0\">\n". -	      "<head>". -	      "  <title>$site_name Git OPML Export</title>\n". -	      "</head>\n". -	      "<body>\n". -	      "<outline text=\"git RSS feeds\">\n"; - -	foreach my $pr (@list) { -		my %proj = %$pr; -		my $head = git_read_head($proj{'path'}); -		if (!defined $head) { -			next; -		} -		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; -		my %co = git_read_commit($head); -		if (!%co) { -			next; -		} - -		my $path = esc_html(chop_str($proj{'path'}, 25, 5)); -		my $rss  = "$my_url?p=$proj{'path'};a=rss"; -		my $html = "$my_url?p=$proj{'path'};a=summary"; -		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; -	} -	print "</outline>\n". -	      "</body>\n". -	      "</opml>\n"; -} -  sub git_log {  	my $head = git_read_head($project);  	if (!defined $hash) { @@ -1895,59 +1819,34 @@ sub git_log {  		$page = 0;  	}  	my $refs = read_info_ref(); -	git_header_html(); -	print "<div class=\"page_nav\">\n"; -	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . -	      " | log" . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";  	my $limit = sprintf("--max-count=%i", (100 * ($page+1))); -	open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); -	my (@revlist) = map { chomp; $_ } <$fd>; +	open my $fd, "-|", $GIT, "rev-list", $limit, $hash +		or die_error(undef, "Open git-rev-list failed"); +	my @revlist = map { chomp; $_ } <$fd>;  	close $fd; -	if ($hash ne $head || $page) { -		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD"); -	} else { -		print "HEAD"; -	} -	if ($page > 0) { -		print " ⋅ " . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); -	} else { -		print " ⋅ prev"; -	} -	if ($#revlist >= (100 * ($page+1)-1)) { -		print " ⋅ " . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); -	} else { -		print " ⋅ next"; -	} -	print "<br/>\n" . -	      "</div>\n"; +	my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist); + +	git_header_html(); +	git_page_nav('log','', $hash,undef,undef, $paging_nav); +  	if (!@revlist) { -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . -		      "</div>\n";  		my %co = git_read_commit($hash); + +		git_header_div('summary', $project);  		print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";  	}  	for (my $i = ($page * 100); $i <= $#revlist; $i++) {  		my $commit = $revlist[$i]; -		my $ref = ""; -		if (defined $refs->{$commit}) { -			$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; -		} +		my $ref = git_get_referencing($refs, $commit);  		my %co = git_read_commit($commit);  		next if !%co;  		my %ad = date_str($co{'author_epoch'}); -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "title"}, -		      "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref) . "\n"; -		print "</div>\n"; +		git_header_div('commit', +									 "<span class=\"age\">$co{'age_string'}</span>" . +									 esc_html($co{'title'}) . $ref, +									 $commit);  		print "<div class=\"title_text\">\n" .  		      "<div class=\"log_link\">\n" .  		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . @@ -1984,21 +1883,19 @@ sub git_log {  sub git_commit {  	my %co = git_read_commit($hash);  	if (!%co) { -		die_error(undef, "Unknown commit object."); +		die_error(undef, "Unknown commit object");  	}  	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});  	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); -	my @difftree; -	my $root = "";  	my $parent = $co{'parent'};  	if (!defined $parent) { -		$root = " --root"; -		$parent = ""; +		$parent = "--root";  	} -	open my $fd, "-|", "$GIT diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed."); -	@difftree = map { chomp; $_ } <$fd>; -	close $fd or die_error(undef, "Reading diff-tree failed."); +	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash +		or die_error(undef, "Open git-diff-tree failed"); +	my @difftree = map { chomp; $_ } <$fd>; +	close $fd or die_error(undef, "Reading git-diff-tree failed");  	# non-textual hash id's can be cached  	my $expires; @@ -2006,35 +1903,21 @@ sub git_commit {  		$expires = "+1d";  	}  	my $refs = read_info_ref(); -	my $ref = ""; -	if (defined $refs->{$co{'id'}}) { -		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; -	} -	git_header_html(undef, $expires); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . -	      " | commit"; -	if (defined $co{'parent'}) { -		print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff"); -	} -	print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "\n" . -		"<br/>\n"; +	my $ref = git_get_referencing($refs, $co{'id'}); +	my $formats_nav = '';  	if (defined $file_name && defined $co{'parent'}) {  		my $parent = $co{'parent'}; -		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame") . "\n"; +		$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame");  	} -	print "<br/></div>\n"; +	git_header_html(undef, $expires); +	git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', +							 $hash, $co{'tree'}, $hash, +							 $formats_nav);  	if (defined $co{'parent'}) { -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . -		      "</div>\n"; +		git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);  	} else { -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . -		      "</div>\n"; +		git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);  	}  	print "<div class=\"title_text\">\n" .  	      "<table cellspacing=\"0\">\n"; @@ -2105,7 +1988,7 @@ sub git_commit {  	foreach my $line (@difftree) {  		# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M      ls-files.c'  		# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M      rev-tree.c' -		if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { +		if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {  			next;  		}  		my $from_mode = $1; @@ -2132,11 +2015,11 @@ sub git_commit {  			      "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n";  		} elsif ($status eq "D") {  			print "<td>" . -			      $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" . +			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" .  			      "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" .  			      "<td class=\"link\">" . -			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") . -			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . +			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") . +			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") .  			      "</td>\n"  		} elsif ($status eq "M" || $status eq "T") {  			my $mode_chnge = ""; @@ -2178,7 +2061,7 @@ sub git_commit {  			print "<td>" .  			      $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" .  			      "<td><span class=\"file_status moved\">[moved from " . -			      $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)) . +			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), -class => "list"}, esc_html($from_file)) .  			      " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" .  			      "<td class=\"link\">" .  			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); @@ -2197,19 +2080,10 @@ sub git_blobdiff {  	mkdir($git_temp, 0700);  	git_header_html();  	if (defined $hash_base && (my %co = git_read_commit($hash_base))) { -		print "<div class=\"page_nav\">\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . -		      "<br/>\n"; -		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . -		      "</div>\n"; -		print "<div>\n" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . -		      "</div>\n"; +		my $formats_nav = +			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); +		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); +		git_header_div('commit', esc_html($co{'title'}), $hash_base);  	} else {  		print "<div class=\"page_nav\">\n" .  		      "<br/><br/></div>\n" . @@ -2237,14 +2111,15 @@ sub git_commitdiff {  	mkdir($git_temp, 0700);  	my %co = git_read_commit($hash);  	if (!%co) { -		die_error(undef, "Unknown commit object."); +		die_error(undef, "Unknown commit object");  	}  	if (!defined $hash_parent) { -		$hash_parent = $co{'parent'}; +		$hash_parent = $co{'parent'} || '--root';  	} -	open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); -	my (@difftree) = map { chomp; $_ } <$fd>; -	close $fd or die_error(undef, "Reading diff-tree failed."); +	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash +		or die_error(undef, "Open git-diff-tree failed"); +	my @difftree = map { chomp; $_ } <$fd>; +	close $fd or die_error(undef, "Reading git-diff-tree failed");  	# non-textual hash id's can be cached  	my $expires; @@ -2252,23 +2127,12 @@ sub git_commitdiff {  		$expires = "+1d";  	}  	my $refs = read_info_ref(); -	my $ref = ""; -	if (defined $refs->{$co{'id'}}) { -		$ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; -	} +	my $ref = git_get_referencing($refs, $co{'id'}); +	my $formats_nav = +		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain");  	git_header_html(undef, $expires); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . -	      " | commitdiff" . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "<br/>\n"; -	print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . "\n" . -	      "</div>\n"; -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . -	      "</div>\n"; +	git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); +	git_header_div('commit', esc_html($co{'title'}) . $ref, $hash);  	print "<div class=\"page_body\">\n";  	my $comment = $co{'comment'};  	my $empty = 0; @@ -2297,7 +2161,9 @@ sub git_commitdiff {  	foreach my $line (@difftree) {  		# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M      ls-files.c'  		# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M      rev-tree.c' -		$line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; +		if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { +			next; +		}  		my $from_mode = $1;  		my $to_mode = $2;  		my $from_id = $3; @@ -2305,21 +2171,23 @@ sub git_commitdiff {  		my $status = $5;  		my $file = validate_input(unquote($6));  		if ($status eq "A") { -			print "<div class=\"diff_info\">" .  file_type($to_mode) . ":" . +			print "<div class=\"diff_info\">" . file_type($to_mode) . ":" .  			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" .  			      "</div>\n";  			git_diff_print(undef, "/dev/null", $to_id, "b/$file");  		} elsif ($status eq "D") {  			print "<div class=\"diff_info\">" . file_type($from_mode) . ":" . -			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" . +			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" .  			      "</div>\n";  			git_diff_print($from_id, "a/$file", undef, "/dev/null");  		} elsif ($status eq "M") {  			if ($from_id ne $to_id) {  				print "<div class=\"diff_info\">" . -				      file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . +				      file_type($from_mode) . ":" . +				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) .  				      " -> " . -				      file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); +				      file_type($to_mode) . ":" . +				      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id);  				print "</div>\n";  				git_diff_print($from_id, "a/$file",  $to_id, "b/$file");  			} @@ -2332,15 +2200,23 @@ sub git_commitdiff {  sub git_commitdiff_plain {  	mkdir($git_temp, 0700); -	open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); -	my (@difftree) = map { chomp; $_ } <$fd>; -	close $fd or die_error(undef, "Reading diff-tree failed."); +	my %co = git_read_commit($hash); +	if (!%co) { +		die_error(undef, "Unknown commit object"); +	} +	if (!defined $hash_parent) { +		$hash_parent = $co{'parent'} || '--root'; +	} +	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash +		or die_error(undef, "Open git-diff-tree failed"); +	my @difftree = map { chomp; $_ } <$fd>; +	close $fd or die_error(undef, "Reading diff-tree failed");  	# try to figure out the next tag after this commit  	my $tagname;  	my $refs = read_info_ref("tags"); -	open $fd, "-|", "$GIT rev-list HEAD"; -	chomp (my (@commits) = <$fd>); +	open $fd, "-|", $GIT, "rev-list", "HEAD"; +	my @commits = map { chomp; $_ } <$fd>;  	close $fd;  	foreach my $commit (@commits) {  		if (defined $refs->{$commit}) { @@ -2352,14 +2228,13 @@ sub git_commitdiff_plain {  	}  	print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); -	my %co = git_read_commit($hash);  	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});  	my $comment = $co{'comment'};  	print "From: $co{'author'}\n" .  	      "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".  	      "Subject: $co{'title'}\n";  	if (defined $tagname) { -	      print "X-Git-Tag: $tagname\n"; +		print "X-Git-Tag: $tagname\n";  	}  	print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .  	      "\n"; @@ -2370,7 +2245,9 @@ sub git_commitdiff_plain {  	print "---\n\n";  	foreach my $line (@difftree) { -		$line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; +		if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { +			next; +		}  		my $from_id = $3;  		my $to_id = $4;  		my $status = $5; @@ -2392,22 +2269,12 @@ sub git_history {  	my $ftype;  	my %co = git_read_commit($hash_base);  	if (!%co) { -		die_error(undef, "Unknown commit object."); +		die_error(undef, "Unknown commit object");  	}  	my $refs = read_info_ref();  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . -	      "<br/><br/>\n" . -	      "</div>\n"; -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . -	      "</div>\n"; +	git_page_nav('','', $hash_base,$co{'tree'},$hash_base); +	git_header_div('commit', esc_html($co{'title'}), $hash_base);  	if (!defined $hash && defined $file_name) {  		$hash = git_get_hash_by_path($hash_base, $file_name);  	} @@ -2417,7 +2284,7 @@ sub git_history {  	git_print_page_path($file_name, $ftype);  	open my $fd, "-|", -		"$GIT rev-list --full-history $hash_base -- \'$file_name\'"; +		$GIT, "rev-list", "--full-history", $hash_base, "--", $file_name;  	print "<table cellspacing=\"0\">\n";  	my $alternate = 0;  	while (my $line = <$fd>) { @@ -2427,10 +2294,7 @@ sub git_history {  			if (!%co) {  				next;  			} -			my $ref = ""; -			if (defined $refs->{$commit}) { -				$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; -			} +			my $ref = git_get_referencing($refs, $commit);  			if ($alternate) {  				print "<tr class=\"dark\">\n";  			} else { @@ -2444,7 +2308,7 @@ sub git_history {  			      "<td class=\"link\">" .  			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .  			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . -			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=$commit;f=$file_name")}, "blob"); +			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype);  			my $blob = git_get_hash_by_path($hash_base, $file_name);  			my $blob_parent = git_get_hash_by_path($commit, $file_name);  			if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { @@ -2463,14 +2327,14 @@ sub git_history {  sub git_search {  	if (!defined $searchtext) { -		die_error("", "Text field empty."); +		die_error(undef, "Text field empty");  	}  	if (!defined $hash) {  		$hash = git_read_head($project);  	}  	my %co = git_read_commit($hash);  	if (!%co) { -		die_error(undef, "Unknown commit object."); +		die_error(undef, "Unknown commit object");  	}  	# pickaxe may take all resources of your box and run for several minutes  	# with every query - so decide by yourself how public you make this feature :) @@ -2487,24 +2351,14 @@ sub git_search {  		$pickaxe_search = 1;  	}  	git_header_html(); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary;h=$hash")}, "summary") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . -	      "<br/><br/>\n" . -	      "</div>\n"; +	git_page_nav('','', $hash,$co{'tree'},$hash); +	git_header_div('commit', esc_html($co{'title'}), $hash); -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . -	      "</div>\n";  	print "<table cellspacing=\"0\">\n";  	my $alternate = 0;  	if ($commit_search) {  		$/ = "\0"; -		open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next; +		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next;  		while (my $commit_text = <$fd>) {  			if (!grep m/$searchtext/i, $commit_text) {  				next; @@ -2615,82 +2469,122 @@ sub git_shortlog {  		$page = 0;  	}  	my $refs = read_info_ref(); -	git_header_html(); -	print "<div class=\"page_nav\">\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . -	      " | shortlog" . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . -	      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n";  	my $limit = sprintf("--max-count=%i", (100 * ($page+1))); -	open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); -	my (@revlist) = map { chomp; $_ } <$fd>; +	open my $fd, "-|", $GIT, "rev-list", $limit, $hash +		or die_error(undef, "Open git-rev-list failed"); +	my @revlist = map { chomp; $_ } <$fd>;  	close $fd; -	if ($hash ne $head || $page) { -		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD"); -	} else { -		print "HEAD"; -	} -	if ($page > 0) { -		print " ⋅ " . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); -	} else { -		print " ⋅ prev"; -	} +	my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist); +	my $next_link = '';  	if ($#revlist >= (100 * ($page+1)-1)) { -		print " ⋅ " . -		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); -	} else { -		print " ⋅ next"; +		$next_link = +			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), +			         -title => "Alt-n"}, "next");  	} -	print "<br/>\n" . -	      "</div>\n"; -	print "<div>\n" . -	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . -	      "</div>\n"; -	print "<table cellspacing=\"0\">\n"; -	my $alternate = 0; -	for (my $i = ($page * 100); $i <= $#revlist; $i++) { + + +	git_header_html(); +	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); +	git_header_div('summary', $project); + +	git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + +	git_footer_html(); +} + +## ...................................................................... +## feeds (RSS, OPML) + +sub git_rss { +	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ +	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project) +		or die_error(undef, "Open git-rev-list failed"); +	my @revlist = map { chomp; $_ } <$fd>; +	close $fd or die_error(undef, "Reading git-rev-list failed"); +	print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); +	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". +	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; +	print "<channel>\n"; +	print "<title>$project</title>\n". +	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". +	      "<description>$project log</description>\n". +	      "<language>en</language>\n"; + +	for (my $i = 0; $i <= $#revlist; $i++) {  		my $commit = $revlist[$i]; -		my $ref = ""; -		if (defined $refs->{$commit}) { -			$ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; -		}  		my %co = git_read_commit($commit); -		my %ad = date_str($co{'author_epoch'}); -		if ($alternate) { -			print "<tr class=\"dark\">\n"; -		} else { -			print "<tr class=\"light\">\n"; +		# we read 150, we always show 30 and the ones more recent than 48 hours +		if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { +			last;  		} -		$alternate ^= 1; -		print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . -		      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . -		      "<td>"; -		if (length($co{'title_short'}) < length($co{'title'})) { -			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, -			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); -		} else { -			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, -			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); +		my %cd = date_str($co{'committer_epoch'}); +		open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; +		my @difftree = map { chomp; $_ } <$fd>; +		close $fd or next; +		print "<item>\n" . +		      "<title>" . +		      sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . +		      "</title>\n" . +		      "<author>" . esc_html($co{'author'}) . "</author>\n" . +		      "<pubDate>$cd{'rfc2822'}</pubDate>\n" . +		      "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . +		      "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . +		      "<description>" . esc_html($co{'title'}) . "</description>\n" . +		      "<content:encoded>" . +		      "<![CDATA[\n"; +		my $comment = $co{'comment'}; +		foreach my $line (@$comment) { +			$line = decode("utf8", $line, Encode::FB_DEFAULT); +			print "$line<br/>\n";  		} -		print "</td>\n" . -		      "<td class=\"link\">" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . -		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . -		      "</td>\n" . -		      "</tr>"; +		print "<br/>\n"; +		foreach my $line (@difftree) { +			if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { +				next; +			} +			my $file = validate_input(unquote($7)); +			$file = decode("utf8", $file, Encode::FB_DEFAULT); +			print "$file<br/>\n"; +		} +		print "]]>\n" . +		      "</content:encoded>\n" . +		      "</item>\n";  	} -	if ($#revlist >= (100 * ($page+1)-1)) { -		print "<tr>\n" . -		      "<td>" . -		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -title => "Alt-n"}, "next") . -		      "</td>\n" . -		      "</tr>\n"; +	print "</channel></rss>"; +} + +sub git_opml { +	my @list = git_read_projects(); + +	print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); +	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". +	      "<opml version=\"1.0\">\n". +	      "<head>". +	      "  <title>$site_name Git OPML Export</title>\n". +	      "</head>\n". +	      "<body>\n". +	      "<outline text=\"git RSS feeds\">\n"; + +	foreach my $pr (@list) { +		my %proj = %$pr; +		my $head = git_read_head($proj{'path'}); +		if (!defined $head) { +			next; +		} +		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; +		my %co = git_read_commit($head); +		if (!%co) { +			next; +		} + +		my $path = esc_html(chop_str($proj{'path'}, 25, 5)); +		my $rss  = "$my_url?p=$proj{'path'};a=rss"; +		my $html = "$my_url?p=$proj{'path'};a=summary"; +		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";  	} -	print "</table\n>"; -	git_footer_html(); +	print "</outline>\n". +	      "</body>\n". +	      "</opml>\n";  } | 
