gitweb.perl 78.4 KB
Newer Older
K
Kay Sievers 已提交
1 2
#!/usr/bin/perl

K
v220  
Kay Sievers 已提交
3
# gitweb - simple web interface to track changes in git repositories
K
v004  
Kay Sievers 已提交
4
#
K
Kay Sievers 已提交
5 6
# (C) 2005-2006, Kay Sievers <kay.sievers@vrfy.org>
# (C) 2005, Christian Gierke
K
v005  
Kay Sievers 已提交
7
#
K
Kay Sievers 已提交
8
# This program is licensed under the GPLv2
K
Kay Sievers 已提交
9 10 11

use strict;
use warnings;
K
v203  
Kay Sievers 已提交
12
use CGI qw(:standard :escapeHTML -nosticky);
K
v148  
Kay Sievers 已提交
13
use CGI::Util qw(unescape);
K
Kay Sievers 已提交
14
use CGI::Carp qw(fatalsToBrowser);
15
use Encode;
K
v107  
Kay Sievers 已提交
16
use Fcntl ':mode';
17
use File::Find qw();
K
Kay Sievers 已提交
18
binmode STDOUT, ':utf8';
K
Kay Sievers 已提交
19

20
our $cgi = new CGI;
21
our $version = "++GIT_VERSION++";
22 23 24
our $my_url = $cgi->url();
our $my_uri = $cgi->url(-absolute => 1);
our $rss_link = "";
K
v025  
Kay Sievers 已提交
25

26 27
# core git executable to use
# this can just be "git" if your webserver has a sensible PATH
28
our $GIT = "++GIT_BINDIR++/git";
29

K
v107  
Kay Sievers 已提交
30
# absolute fs-path which will be prepended to the project path
31
#our $projectroot = "/pub/scm";
32
our $projectroot = "++GITWEB_PROJECTROOT++";
K
v107  
Kay Sievers 已提交
33 34

# location for temporary files needed for diffs
35
our $git_temp = "/tmp/gitweb";
K
v088  
Kay Sievers 已提交
36

K
v107  
Kay Sievers 已提交
37
# target of the home link on top of all pages
38
our $home_link = $my_uri;
K
v107  
Kay Sievers 已提交
39

40 41
# name of your site or organization to appear in page titles
# replace this with something more descriptive for clearer bookmarks
42
our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
43

K
v136  
Kay Sievers 已提交
44
# html text to include at home page
45
our $home_text = "++GITWEB_HOMETEXT++";
K
v136  
Kay Sievers 已提交
46

J
Jakub Narebski 已提交
47
# URI of default stylesheet
48
our $stylesheet = "++GITWEB_CSS++";
M
Martin Waitz 已提交
49
# URI of GIT logo
50
our $logo = "++GITWEB_LOGO++";
J
Jakub Narebski 已提交
51

K
v118  
Kay Sievers 已提交
52
# source of projects list
53
our $projects_list = "++GITWEB_LIST++";
K
v107  
Kay Sievers 已提交
54

55
# default blob_plain mimetype and default charset for text/plain blob
56 57
our $default_blob_plain_mimetype = 'text/plain';
our $default_text_plain_charset  = undef;
58

59 60
# file to use for guessing MIME types before trying /etc/mime.types
# (relative to the current git repository)
61
our $mimetypes_file = undef;
62

63
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
64 65 66 67 68 69 70
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) {
71
	mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp");
72 73
}

74
# ======================================================================
K
v118  
Kay Sievers 已提交
75
# input validation and dispatch
76
our $action = $cgi->param('a');
K
v118  
Kay Sievers 已提交
77
if (defined $action) {
78
	if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
K
v118  
Kay Sievers 已提交
79 80
		undef $action;
		die_error(undef, "Invalid action parameter.");
K
v107  
Kay Sievers 已提交
81
	}
82
	# action which does not check rest of parameters
M
Martin Waitz 已提交
83
	if ($action eq "opml") {
K
v220  
Kay Sievers 已提交
84 85
		git_opml();
		exit;
K
v118  
Kay Sievers 已提交
86
	}
K
v107  
Kay Sievers 已提交
87
}
K
v014  
Kay Sievers 已提交
88

89
our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
K
v107  
Kay Sievers 已提交
90
if (defined $project) {
91
	$project =~ s|^/||; $project =~ s|/$||;
92 93 94
	$project = validate_input($project);
	if (!defined($project)) {
		die_error(undef, "Invalid project parameter.");
K
v070  
Kay Sievers 已提交
95 96
	}
	if (!(-d "$projectroot/$project")) {
K
v107  
Kay Sievers 已提交
97
		undef $project;
K
v118  
Kay Sievers 已提交
98
		die_error(undef, "No such directory.");
K
v107  
Kay Sievers 已提交
99 100 101
	}
	if (!(-e "$projectroot/$project/HEAD")) {
		undef $project;
K
v118  
Kay Sievers 已提交
102
		die_error(undef, "No such project.");
K
v070  
Kay Sievers 已提交
103
	}
104
	$rss_link = "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" .
J
Jakub Narebski 已提交
105
	            "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>";
K
v227  
Kay Sievers 已提交
106
	$ENV{'GIT_DIR'} = "$projectroot/$project";
K
v118  
Kay Sievers 已提交
107
} else {
K
v142  
Kay Sievers 已提交
108
	git_project_list();
K
v118  
Kay Sievers 已提交
109
	exit;
K
v055  
Kay Sievers 已提交
110
}
K
v085  
Kay Sievers 已提交
111

112
our $file_name = $cgi->param('f');
K
v107  
Kay Sievers 已提交
113
if (defined $file_name) {
114 115 116
	$file_name = validate_input($file_name);
	if (!defined($file_name)) {
		die_error(undef, "Invalid file parameter.");
K
v107  
Kay Sievers 已提交
117
	}
K
v055  
Kay Sievers 已提交
118
}
K
v085  
Kay Sievers 已提交
119

120
our $hash = $cgi->param('h');
K
v227  
Kay Sievers 已提交
121
if (defined $hash) {
122 123 124
	$hash = validate_input($hash);
	if (!defined($hash)) {
		die_error(undef, "Invalid hash parameter.");
K
v227  
Kay Sievers 已提交
125
	}
K
v055  
Kay Sievers 已提交
126
}
K
v085  
Kay Sievers 已提交
127

128
our $hash_parent = $cgi->param('hp');
129 130 131 132 133
if (defined $hash_parent) {
	$hash_parent = validate_input($hash_parent);
	if (!defined($hash_parent)) {
		die_error(undef, "Invalid hash parent parameter.");
	}
K
v118  
Kay Sievers 已提交
134 135
}

136
our $hash_base = $cgi->param('hb');
137 138 139 140 141
if (defined $hash_base) {
	$hash_base = validate_input($hash_base);
	if (!defined($hash_base)) {
		die_error(undef, "Invalid hash base parameter.");
	}
K
v055  
Kay Sievers 已提交
142
}
K
v085  
Kay Sievers 已提交
143

144
our $page = $cgi->param('pg');
K
v206  
Kay Sievers 已提交
145
if (defined $page) {
146
	if ($page =~ m/[^0-9]$/) {
K
v206  
Kay Sievers 已提交
147 148
		undef $page;
		die_error(undef, "Invalid page parameter.");
K
v107  
Kay Sievers 已提交
149
	}
K
v053  
Kay Sievers 已提交
150
}
K
v005  
Kay Sievers 已提交
151

152
our $searchtext = $cgi->param('s');
K
v203  
Kay Sievers 已提交
153 154 155 156 157 158 159 160
if (defined $searchtext) {
	if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
		undef $searchtext;
		die_error(undef, "Invalid search parameter.");
	}
	$searchtext = quotemeta $searchtext;
}

161
# dispatch
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
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})) {
K
v118  
Kay Sievers 已提交
185 186 187
	undef $action;
	die_error(undef, "Unknown action.");
}
188 189
$actions{$action}->();
exit;
K
v118  
Kay Sievers 已提交
190

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
## ======================================================================
## validation, quoting/unquoting and escaping

sub validate_input {
	my $input = shift;

	if ($input =~ m/^[0-9a-fA-F]{40}$/) {
		return $input;
	}
	if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
		return undef;
	}
	if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
		return undef;
	}
	return $input;
}

209 210 211
# quote unsafe chars, but keep the slash, even when it's not
# correct, but quoted slashes look too horrible in bookmarks
sub esc_param {
K
Kay Sievers 已提交
212
	my $str = shift;
213
	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
214
	$str =~ s/\+/%2B/g;
K
Kay Sievers 已提交
215
	$str =~ s/ /\+/g;
K
Kay Sievers 已提交
216 217 218
	return $str;
}

219
# replace invalid utf8 character with SUBSTITUTION sequence
220 221 222
sub esc_html {
	my $str = shift;
	$str = decode("utf8", $str, Encode::FB_DEFAULT);
K
Kay Sievers 已提交
223
	$str = escapeHTML($str);
224
	$str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
225 226 227
	return $str;
}

228 229 230 231 232 233 234 235 236 237
# git may return quoted and escaped filenames
sub unquote {
	my $str = shift;
	if ($str =~ m/^"(.*)"$/) {
		$str = $1;
		$str =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
	}
	return $str;
}

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
## ----------------------------------------------------------------------
## 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

261 262 263 264 265 266 267 268 269 270 271 272 273
# CSS class for given age value (in seconds)
sub age_class {
	my $age = shift;

	if ($age < 60*60*2) {
		return "age0";
	} elsif ($age < 60*60*24*2) {
		return "age1";
	} else {
		return "age2";
	}
}

274 275 276 277
# convert age in seconds to "nn units ago" string
sub age_string {
	my $age = shift;
	my $age_str;
K
v055  
Kay Sievers 已提交
278

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
	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";
300
	} else {
301
		$age_str .= " right now";
K
v000  
Kay Sievers 已提交
302
	}
303
	return $age_str;
K
Kay Sievers 已提交
304 305
}

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
# convert file mode in octal to symbolic file mode string
sub mode_str {
	my $mode = oct shift;

	if (S_ISDIR($mode & S_IFMT)) {
		return 'drwxr-xr-x';
	} elsif (S_ISLNK($mode)) {
		return 'lrwxrwxrwx';
	} elsif (S_ISREG($mode)) {
		# git cares only about the executable bit
		if ($mode & S_IXUSR) {
			return '-rwxr-xr-x';
		} else {
			return '-rw-r--r--';
		};
K
v220  
Kay Sievers 已提交
321
	} else {
322
		return '----------';
K
v048  
Kay Sievers 已提交
323
	}
K
Kay Sievers 已提交
324 325
}

326 327 328
# convert file mode in octal to file type string
sub file_type {
	my $mode = oct shift;
K
v064  
Kay Sievers 已提交
329

330 331 332 333 334 335 336 337 338
	if (S_ISDIR($mode & S_IFMT)) {
		return "directory";
	} elsif (S_ISLNK($mode)) {
		return "symlink";
	} elsif (S_ISREG($mode)) {
		return "file";
	} else {
		return "unknown";
	}
K
v055  
Kay Sievers 已提交
339 340
}

341 342 343
## ----------------------------------------------------------------------
## functions returning short HTML fragments, or transforming HTML fragments
## which don't beling to other sections
344

345 346 347
# format line of commit message or tag comment
sub format_log_line_html {
	my $line = shift;
348

349 350 351 352 353 354 355
	$line = esc_html($line);
	$line =~ s/ /&nbsp;/g;
	if ($line =~ m/([0-9a-fA-F]{40})/) {
		my $hash_text = $1;
		if (git_get_type($hash_text) eq "commit") {
			my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text);
			$line =~ s/$hash_text/$link/;
356 357
		}
	}
358
	return $line;
359 360
}

361 362 363
# format marker of refs pointing to given object
sub git_get_referencing {
	my ($refs, $id) = @_;
364

365 366 367 368 369
	if (defined $refs->{$id}) {
		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
	} else {
		return "";
	}
370 371
}

372 373
## ----------------------------------------------------------------------
## git utility subroutines, invoking git commands
K
v125  
Kay Sievers 已提交
374

375
# get HEAD ref of given project as hash
376 377 378 379 380
sub git_read_head {
	my $project = shift;
	my $oENV = $ENV{'GIT_DIR'};
	my $retval = undef;
	$ENV{'GIT_DIR'} = "$projectroot/$project";
381
	if (open my $fd, "-|", $GIT, "rev-parse", "--verify", "HEAD") {
382 383
		my $head = <$fd>;
		close $fd;
K
Kay Sievers 已提交
384 385
		if (defined $head && $head =~ /^([0-9a-fA-F]{40})$/) {
			$retval = $1;
386 387
		}
	}
K
Kay Sievers 已提交
388 389 390
	if (defined $oENV) {
		$ENV{'GIT_DIR'} = $oENV;
	}
391 392 393
	return $retval;
}

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
# 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
K
v142  
Kay Sievers 已提交
445
sub git_read_hash {
K
v041  
Kay Sievers 已提交
446
	my $path = shift;
K
v118  
Kay Sievers 已提交
447

K
v203  
Kay Sievers 已提交
448
	open my $fd, "$projectroot/$path" or return undef;
K
v021  
Kay Sievers 已提交
449 450 451
	my $head = <$fd>;
	close $fd;
	chomp $head;
K
v107  
Kay Sievers 已提交
452 453 454 455 456
	if ($head =~ m/^[0-9a-fA-F]{40}$/) {
		return $head;
	}
}

K
v118  
Kay Sievers 已提交
457
sub git_read_description {
K
v107  
Kay Sievers 已提交
458
	my $path = shift;
K
v118  
Kay Sievers 已提交
459

K
v203  
Kay Sievers 已提交
460
	open my $fd, "$projectroot/$path/description" or return undef;
K
v107  
Kay Sievers 已提交
461 462 463 464
	my $descr = <$fd>;
	close $fd;
	chomp $descr;
	return $descr;
K
v021  
Kay Sievers 已提交
465 466
}

467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
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;
}

K
v142  
Kay Sievers 已提交
561 562 563
sub git_read_tag {
	my $tag_id = shift;
	my %tag;
K
v235  
Kay Sievers 已提交
564
	my @comment;
K
v142  
Kay Sievers 已提交
565

566
	open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return;
K
v235  
Kay Sievers 已提交
567
	$tag{'id'} = $tag_id;
K
v142  
Kay Sievers 已提交
568 569 570 571
	while (my $line = <$fd>) {
		chomp $line;
		if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
			$tag{'object'} = $1;
K
v163  
Kay Sievers 已提交
572
		} elsif ($line =~ m/^type (.+)$/) {
K
v142  
Kay Sievers 已提交
573
			$tag{'type'} = $1;
K
v163  
Kay Sievers 已提交
574
		} elsif ($line =~ m/^tag (.+)$/) {
K
v142  
Kay Sievers 已提交
575
			$tag{'name'} = $1;
K
v235  
Kay Sievers 已提交
576 577 578 579 580 581 582 583 584
		} elsif ($line =~ m/^tagger (.*) ([0-9]+) (.*)$/) {
			$tag{'author'} = $1;
			$tag{'epoch'} = $2;
			$tag{'tz'} = $3;
		} elsif ($line =~ m/--BEGIN/) {
			push @comment, $line;
			last;
		} elsif ($line eq "") {
			last;
K
v142  
Kay Sievers 已提交
585 586
		}
	}
K
v235  
Kay Sievers 已提交
587 588
	push @comment, <$fd>;
	$tag{'comment'} = \@comment;
K
v203  
Kay Sievers 已提交
589
	close $fd or return;
K
v142  
Kay Sievers 已提交
590 591 592 593 594 595
	if (!defined $tag{'name'}) {
		return
	};
	return %tag
}

K
v118  
Kay Sievers 已提交
596
sub git_read_commit {
K
v203  
Kay Sievers 已提交
597 598 599 600
	my $commit_id = shift;
	my $commit_text = shift;

	my @commit_lines;
K
v021  
Kay Sievers 已提交
601 602
	my %co;

K
v203  
Kay Sievers 已提交
603 604 605
	if (defined $commit_text) {
		@commit_lines = @$commit_text;
	} else {
606
		$/ = "\0";
607
		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return;
608
		@commit_lines = split '\n', <$fd>;
K
v203  
Kay Sievers 已提交
609
		close $fd or return;
610 611
		$/ = "\n";
		pop @commit_lines;
K
v203  
Kay Sievers 已提交
612
	}
613 614 615 616 617 618 619
	my $header = shift @commit_lines;
	if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
		return;
	}
	($co{'id'}, my @parents) = split ' ', $header;
	$co{'parents'} = \@parents;
	$co{'parent'} = $parents[0];
K
v203  
Kay Sievers 已提交
620
	while (my $line = shift @commit_lines) {
K
v107  
Kay Sievers 已提交
621
		last if $line eq "\n";
K
v163  
Kay Sievers 已提交
622
		if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
K
v021  
Kay Sievers 已提交
623
			$co{'tree'} = $1;
K
v035  
Kay Sievers 已提交
624
		} elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
K
v021  
Kay Sievers 已提交
625
			$co{'author'} = $1;
K
v049  
Kay Sievers 已提交
626 627
			$co{'author_epoch'} = $2;
			$co{'author_tz'} = $3;
K
v164  
Kay Sievers 已提交
628 629 630 631 632
			if ($co{'author'} =~ m/^([^<]+) </) {
				$co{'author_name'} = $1;
			} else {
				$co{'author_name'} = $co{'author'};
			}
K
v041  
Kay Sievers 已提交
633 634
		} elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
			$co{'committer'} = $1;
K
v049  
Kay Sievers 已提交
635 636
			$co{'committer_epoch'} = $2;
			$co{'committer_tz'} = $3;
K
v042  
Kay Sievers 已提交
637 638
			$co{'committer_name'} = $co{'committer'};
			$co{'committer_name'} =~ s/ <.*//;
K
v021  
Kay Sievers 已提交
639 640
		}
	}
K
v142  
Kay Sievers 已提交
641
	if (!defined $co{'tree'}) {
642
		return;
K
v142  
Kay Sievers 已提交
643
	};
644

K
v203  
Kay Sievers 已提交
645
	foreach my $title (@commit_lines) {
646
		$title =~ s/^    //;
K
v203  
Kay Sievers 已提交
647
		if ($title ne "") {
K
v241  
Kay Sievers 已提交
648
			$co{'title'} = chop_str($title, 80, 5);
K
v203  
Kay Sievers 已提交
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
			# remove leading stuff of merges to make the interesting part visible
			if (length($title) > 50) {
				$title =~ s/^Automatic //;
				$title =~ s/^merge (of|with) /Merge ... /i;
				if (length($title) > 50) {
					$title =~ s/(http|rsync):\/\///;
				}
				if (length($title) > 50) {
					$title =~ s/(master|www|rsync)\.//;
				}
				if (length($title) > 50) {
					$title =~ s/kernel.org:?//;
				}
				if (length($title) > 50) {
					$title =~ s/\/pub\/scm//;
				}
			}
K
v241  
Kay Sievers 已提交
666
			$co{'title_short'} = chop_str($title, 50, 5);
K
v203  
Kay Sievers 已提交
667 668 669
			last;
		}
	}
670 671 672 673 674
	# remove added spaces
	foreach my $line (@commit_lines) {
		$line =~ s/^    //;
	}
	$co{'comment'} = \@commit_lines;
K
v062  
Kay Sievers 已提交
675 676 677

	my $age = time - $co{'committer_epoch'};
	$co{'age'} = $age;
K
v236  
Kay Sievers 已提交
678
	$co{'age_string'} = age_string($age);
K
v225  
Kay Sievers 已提交
679 680
	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($co{'committer_epoch'});
	if ($age > 60*60*24*7*2) {
K
v232  
Kay Sievers 已提交
681
		$co{'age_string_date'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
K
v225  
Kay Sievers 已提交
682 683 684
		$co{'age_string_age'} = $co{'age_string'};
	} else {
		$co{'age_string_date'} = $co{'age_string'};
K
v232  
Kay Sievers 已提交
685
		$co{'age_string_age'} = sprintf "%4i-%02u-%02i", 1900 + $year, $mon+1, $mday;
K
v225  
Kay Sievers 已提交
686
	}
K
v021  
Kay Sievers 已提交
687 688 689
	return %co;
}

690 691
## ......................................................................
## parse to array of hashes functions
K
v000  
Kay Sievers 已提交
692

693 694 695
sub git_read_refs {
	my $ref_dir = shift;
	my @reflist;
K
v000  
Kay Sievers 已提交
696

697
	my @refs;
698 699 700 701 702
	my $pfxlen = length("$projectroot/$project/$ref_dir");
	File::Find::find(sub {
		return if (/^\./);
		if (-f $_) {
			push @refs, substr($File::Find::name, $pfxlen + 1);
703
		}
704 705
	}, "$projectroot/$project/$ref_dir");

706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
	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);
K
v203  
Kay Sievers 已提交
726
			}
727 728 729 730 731 732 733 734 735 736 737
			$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'};
K
v041  
Kay Sievers 已提交
738
		} else {
739 740 741
			$ref_item{'reftype'} = $type;
			$ref_item{'name'} = $ref_file;
			$ref_item{'refid'} = $ref_id;
K
v234  
Kay Sievers 已提交
742
		}
K
v042  
Kay Sievers 已提交
743

744 745 746 747 748
		push @reflist, \%ref_item;
	}
	# sort tags by age
	@reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
	return \@reflist;
K
v041  
Kay Sievers 已提交
749 750
}

751 752
## ----------------------------------------------------------------------
## filesystem-related functions
K
v035  
Kay Sievers 已提交
753

K
v133  
Kay Sievers 已提交
754 755 756 757 758 759 760 761 762 763
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/[,;].*$//;
764
	return decode("utf8", $owner, Encode::FB_DEFAULT);
K
v133  
Kay Sievers 已提交
765 766
}

767 768
## ......................................................................
## mimetype related functions
K
v118  
Kay Sievers 已提交
769

770 771 772 773 774 775 776 777 778
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+/);
779 780 781 782 783
		if (defined $exts) {
			my @exts = split(/\s+/, $exts);
			foreach my $ext (@exts) {
				$mimemap{$ext} = $mime;
			}
K
v118  
Kay Sievers 已提交
784 785
		}
	}
786
	close(MIME);
K
v118  
Kay Sievers 已提交
787

788 789 790
	$filename =~ /\.(.*?)$/;
	return $mimemap{$1};
}
791

792 793 794 795
sub mimetype_guess {
	my $filename = shift;
	my $mime;
	$filename =~ /\./ or return undef;
796

797 798 799 800 801 802 803
	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;
804 805
}

806 807 808
sub git_blob_plain_mimetype {
	my $fd = shift;
	my $filename = shift;
809

810 811 812
	if ($filename) {
		my $mime = mimetype_guess($filename);
		$mime and return $mime;
813
	}
814 815 816 817 818 819 820 821 822 823 824 825 826 827 828

	# 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';
829
	} else {
830
		return 'application/octet-stream';
831
	}
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
}

## ======================================================================
## 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 .= "/";
				}
			}
		}
853
	}
854 855 856 857 858 859 860
	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';
861
	} else {
862
		$content_type = 'text/html';
863
	}
864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
	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\">" .
M
Martin Waitz 已提交
882
	      "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898
	      "</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;
K
v160  
Kay Sievers 已提交
899
		} else {
900
			$search_hash = "HEAD";
K
v160  
Kay Sievers 已提交
901
		}
902 903 904 905 906 907 908 909 910 911
		$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";
K
v107  
Kay Sievers 已提交
912
	}
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941
	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";
K
v107  
Kay Sievers 已提交
942
	git_footer_html();
943
	exit;
K
Kay Sievers 已提交
944 945
}

946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965
## ----------------------------------------------------------------------
## 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";
K
Kay Sievers 已提交
966
			}
K
Kay Sievers 已提交
967 968
		}
	}
969 970 971 972 973 974 975 976 977 978 979 980
	$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";
K
Kay Sievers 已提交
981 982
}

983 984 985
sub git_get_paging_nav {
	my ($action, $hash, $head, $page, $nrevs) = @_;
	my $paging_nav;
986

987 988 989

	if ($hash ne $head || $page) {
		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
990
	} else {
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
		$paging_nav .= "HEAD";
	}

	if ($page > 0) {
		$paging_nav .= " &sdot; " .
			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page-1)),
							 -accesskey => "p", -title => "Alt-p"}, "prev");
	} else {
		$paging_nav .= " &sdot; prev";
	}

	if ($nrevs >= (100 * ($page+1)-1)) {
		$paging_nav .= " &sdot; " .
			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action;h=$hash;pg=" . ($page+1)),
							 -accesskey => "n", -title => "Alt-n"}, "next");
	} else {
		$paging_nav .= " &sdot; next";
1008
	}
1009 1010

	return $paging_nav;
1011 1012
}

1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
## ......................................................................
## 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";
}
K
v142  
Kay Sievers 已提交
1028

1029 1030 1031
sub git_print_page_path {
	my $name = shift;
	my $type = shift;
K
v142  
Kay Sievers 已提交
1032

1033 1034
	if (!defined $name) {
		print "<div class=\"page_path\"><b>/</b></div>\n";
1035
	} elsif (defined $type && $type eq 'blob') {
1036 1037 1038 1039
		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";
K
v142  
Kay Sievers 已提交
1040 1041 1042
	}
}

1043 1044 1045
## ......................................................................
## functions printing large fragments of HTML

1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
sub git_shortlog_body {
	# uses global variable $project
	my ($revlist, $from, $to, $refs, $extra) = @_;
	$from = 0 unless defined $from;
	$to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);

	print "<table class=\"shortlog\" cellspacing=\"0\">\n";
	my $alternate = 0;
	for (my $i = $from; $i <= $to; $i++) {
		my $commit = $revlist->[$i];
		#my $ref = defined $refs ? git_get_referencing($refs, $commit) : '';
		my $ref = git_get_referencing($refs, $commit);
		my %co = git_read_commit($commit);
		my %ad = date_str($co{'author_epoch'});
		if ($alternate) {
			print "<tr class=\"dark\">\n";
		} else {
			print "<tr class=\"light\">\n";
		}
		$alternate ^= 1;
		# git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
		print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
		      "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
		      "<td>";
		if (length($co{'title_short'}) < length($co{'title'})) {
			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
			               -class => "list", -title => "$co{'title'}"},
			      "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
		} else {
			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
			               -class => "list"},
			      "<b>" . esc_html($co{'title'}) . "$ref</b>");
		}
		print "</td>\n" .
		      "<td class=\"link\">" .
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . " | " .
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
		      "</td>\n" .
		      "</tr>\n";
	}
	if (defined $extra) {
		print "<tr>\n" .
		      "<td colspan=\"4\">$extra</td>\n" .
		      "</tr>\n";
	}
	print "</table>\n";
}

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
sub git_tags_body {
	# uses global variable $project
	my ($taglist, $from, $to, $extra) = @_;
	$from = 0 unless defined $from;
	$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);

	print "<table class=\"tags\" cellspacing=\"0\">\n";
	my $alternate = 0;
	for (my $i = $from; $i <= $to; $i++) {
		my $entry = $taglist->[$i];
		my %tag = %$entry;
		my $comment_lines = $tag{'comment'};
		my $comment = shift @$comment_lines;
		my $comment_short;
		if (defined $comment) {
			$comment_short = chop_str($comment, 30, 5);
		}
		if ($alternate) {
			print "<tr class=\"dark\">\n";
		} else {
			print "<tr class=\"light\">\n";
		}
		$alternate ^= 1;
		print "<td><i>$tag{'age'}</i></td>\n" .
		      "<td>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"),
		               -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
		      "</td>\n" .
		      "<td>";
		if (defined $comment) {
			if (length($comment_short) < length($comment)) {
				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
				               -class => "list", -title => $comment}, $comment_short);
			} else {
				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
				               -class => "list"}, $comment);
			}
		}
		print "</td>\n" .
		      "<td class=\"selflink\">";
		if ($tag{'type'} eq "tag") {
			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag");
		} else {
			print "&nbsp;";
		}
		print "</td>\n" .
		      "<td class=\"link\">" . " | " .
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
		if ($tag{'reftype'} eq "commit") {
			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
		} elsif ($tag{'reftype'} eq "blob") {
			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw");
		}
		print "</td>\n" .
		      "</tr>";
	}
	if (defined $extra) {
		print "<tr>\n" .
		      "<td colspan=\"5\">$extra</td>\n" .
		      "</tr>\n";
	}
	print "</table>\n";
}

sub git_heads_body {
	# uses global variable $project
	my ($taglist, $head, $from, $to, $extra) = @_;
	$from = 0 unless defined $from;
	$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);

	print "<table class=\"heads\" cellspacing=\"0\">\n";
	my $alternate = 0;
	for (my $i = $from; $i <= $to; $i++) {
		my $entry = $taglist->[$i];
		my %tag = %$entry;
		my $curr = $tag{'id'} eq $head;
		if ($alternate) {
			print "<tr class=\"dark\">\n";
		} else {
			print "<tr class=\"light\">\n";
		}
		$alternate ^= 1;
		print "<td><i>$tag{'age'}</i></td>\n" .
		      ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") .
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"),
		               -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
		      "</td>\n" .
		      "<td class=\"link\">" .
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " .
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
		      "</td>\n" .
		      "</tr>";
	}
	if (defined $extra) {
		print "<tr>\n" .
		      "<td colspan=\"3\">$extra</td>\n" .
		      "</tr>\n";
	}
	print "</table>\n";
}

## ----------------------------------------------------------------------
## functions printing large fragments, format as one of arguments

sub git_diff_print {
	my $from = shift;
	my $from_name = shift;
	my $to = shift;
	my $to_name = shift;
	my $format = shift || "html";

	my $from_tmp = "/dev/null";
	my $to_tmp = "/dev/null";
	my $pid = $$;

	# create tmp from-file
	if (defined $from) {
		$from_tmp = "$git_temp/gitweb_" . $$ . "_from";
		open my $fd2, "> $from_tmp";
		open my $fd, "-|", $GIT, "cat-file", "blob", $from;
		my @file = <$fd>;
		print $fd2 @file;
		close $fd2;
		close $fd;
	}

	# create tmp to-file
	if (defined $to) {
		$to_tmp = "$git_temp/gitweb_" . $$ . "_to";
		open my $fd2, "> $to_tmp";
		open my $fd, "-|", $GIT, "cat-file", "blob", $to;
		my @file = <$fd>;
		print $fd2 @file;
		close $fd2;
		close $fd;
	}

	open my $fd, "-|", "/usr/bin/diff -u -p -L \'$from_name\' -L \'$to_name\' $from_tmp $to_tmp";
	if ($format eq "plain") {
		undef $/;
		print <$fd>;
		$/ = "\n";
	} else {
		while (my $line = <$fd>) {
			chomp $line;
			my $char = substr($line, 0, 1);
			my $diff_class = "";
			if ($char eq '+') {
				$diff_class = " add";
			} elsif ($char eq "-") {
				$diff_class = " rem";
			} elsif ($char eq "@") {
				$diff_class = " chunk_header";
			} elsif ($char eq "\\") {
				# skip errors
				next;
			}
			while ((my $pos = index($line, "\t")) != -1) {
				if (my $count = (8 - (($pos-1) % 8))) {
					my $spaces = ' ' x $count;
					$line =~ s/\t/$spaces/;
				}
			}
			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
		}
	}
	close $fd;

	if (defined $from) {
		unlink($from_tmp);
	}
	if (defined $to) {
		unlink($to_tmp);
	}
}


## ======================================================================
## ======================================================================
## actions

sub git_project_list {
1277 1278 1279 1280 1281
	my $order = $cgi->param('o');
	if (defined $order && $order !~ m/project|descr|owner|age/) {
		die_error(undef, "Invalid order parameter '$order'.");
	}

1282 1283 1284
	my @list = git_read_projects();
	my @projects;
	if (!@list) {
1285
		die_error(undef, "No projects found.");
1286 1287 1288 1289 1290
	}
	foreach my $pr (@list) {
		my $head = git_read_head($pr->{'path'});
		if (!defined $head) {
			next;
1291
		}
1292 1293 1294 1295
		$ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
		my %co = git_read_commit($head);
		if (!%co) {
			next;
1296
		}
1297 1298 1299 1300
		$pr->{'commit'} = \%co;
		if (!defined $pr->{'descr'}) {
			my $descr = git_read_description($pr->{'path'}) || "";
			$pr->{'descr'} = chop_str($descr, 25, 5);
1301
		}
1302 1303
		if (!defined $pr->{'owner'}) {
			$pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
1304
		}
1305
		push @projects, $pr;
1306
	}
1307

1308 1309 1310 1311 1312 1313 1314
	git_header_html();
	if (-f $home_text) {
		print "<div class=\"index_include\">\n";
		open (my $fd, $home_text);
		print <$fd>;
		close $fd;
		print "</div>\n";
1315
	}
1316 1317
	print "<table class=\"project_list\">\n" .
	      "<tr>\n";
1318 1319
	$order ||= "project";
	if ($order eq "project") {
1320 1321 1322
		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
		print "<th>Project</th>\n";
	} else {
1323 1324 1325 1326
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=project"),
		               -class => "header"}, "Project") .
		      "</th>\n";
1327
	}
1328
	if ($order eq "descr") {
1329 1330 1331
		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
		print "<th>Description</th>\n";
	} else {
1332 1333 1334 1335
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=descr"),
		               -class => "header"}, "Description") .
		      "</th>\n";
1336
	}
1337
	if ($order eq "owner") {
1338 1339 1340
		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
		print "<th>Owner</th>\n";
	} else {
1341 1342 1343 1344
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=owner"),
		               -class => "header"}, "Owner") .
		      "</th>\n";
1345
	}
1346
	if ($order eq "age") {
1347 1348 1349
		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
		print "<th>Last Change</th>\n";
	} else {
1350 1351 1352 1353
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=age"),
		               -class => "header"}, "Last Change") .
		      "</th>\n";
1354 1355 1356
	}
	print "<th></th>\n" .
	      "</tr>\n";
1357
	my $alternate = 0;
1358
	foreach my $pr (@projects) {
1359 1360 1361 1362 1363 1364
		if ($alternate) {
			print "<tr class=\"dark\">\n";
		} else {
			print "<tr class=\"light\">\n";
		}
		$alternate ^= 1;
1365 1366
		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"),
		                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
1367 1368
		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
1369 1370
		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
		      $pr->{'commit'}{'age_string'} . "</td>\n" .
1371
		      "<td class=\"link\">" .
1372 1373 1374
		      $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") .
1375 1376 1377 1378
		      "</td>\n" .
		      "</tr>\n";
	}
	print "</table>\n";
1379
	git_footer_html();
1380 1381
}

K
v142  
Kay Sievers 已提交
1382 1383
sub git_summary {
	my $descr = git_read_description($project) || "none";
1384
	my $head = git_read_head($project);
K
v142  
Kay Sievers 已提交
1385 1386 1387 1388 1389 1390 1391 1392
	my %co = git_read_commit($head);
	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});

	my $owner;
	if (-f $projects_list) {
		open (my $fd , $projects_list);
		while (my $line = <$fd>) {
			chomp $line;
K
v148  
Kay Sievers 已提交
1393 1394 1395
			my ($pr, $ow) = split ' ', $line;
			$pr = unescape($pr);
			$ow = unescape($ow);
K
v142  
Kay Sievers 已提交
1396
			if ($pr eq $project) {
1397
				$owner = decode("utf8", $ow, Encode::FB_DEFAULT);
K
v142  
Kay Sievers 已提交
1398 1399 1400 1401 1402 1403 1404 1405 1406
				last;
			}
		}
		close $fd;
	}
	if (!defined $owner) {
		$owner = get_file_owner("$projectroot/$project");
	}

K
Kay Sievers 已提交
1407
	my $refs = read_info_ref();
K
v142  
Kay Sievers 已提交
1408
	git_header_html();
1409
	git_page_nav('summary','', $head);
1410

K
v203  
Kay Sievers 已提交
1411
	print "<div class=\"title\">&nbsp;</div>\n";
K
v160  
Kay Sievers 已提交
1412
	print "<table cellspacing=\"0\">\n" .
1413
	      "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
K
v142  
Kay Sievers 已提交
1414 1415
	      "<tr><td>owner</td><td>$owner</td></tr>\n" .
	      "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
K
v160  
Kay Sievers 已提交
1416
	      "</table>\n";
1417

1418
	open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project)
1419
		or die_error(undef, "Open git-rev-list failed.");
1420
	my @revlist = map { chomp; $_ } <$fd>;
K
v142  
Kay Sievers 已提交
1421
	close $fd;
1422
	git_header_div('shortlog');
1423 1424
	git_shortlog_body(\@revlist, 0, 15, $refs,
	                  $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "..."));
K
v142  
Kay Sievers 已提交
1425

K
v150  
Kay Sievers 已提交
1426
	my $taglist = git_read_refs("refs/tags");
K
v142  
Kay Sievers 已提交
1427
	if (defined @$taglist) {
1428
		git_header_div('tags');
1429 1430
		git_tags_body($taglist, 0, 15,
		              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "..."));
K
v142  
Kay Sievers 已提交
1431
	}
K
v150  
Kay Sievers 已提交
1432

K
Kay Sievers 已提交
1433 1434
	my $headlist = git_read_refs("refs/heads");
	if (defined @$headlist) {
1435
		git_header_div('heads');
1436
		git_heads_body($headlist, $head, 0, 15,
1437
		               $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
K
v150  
Kay Sievers 已提交
1438
	}
1439

K
v142  
Kay Sievers 已提交
1440 1441 1442
	git_footer_html();
}

K
v235  
Kay Sievers 已提交
1443
sub git_tag {
1444
	my $head = git_read_head($project);
K
v235  
Kay Sievers 已提交
1445
	git_header_html();
1446
	git_page_nav('','', $head,undef,$head);
K
v235  
Kay Sievers 已提交
1447
	my %tag = git_read_tag($hash);
1448
	git_header_div('commit', esc_html($tag{'name'}), $hash);
K
v235  
Kay Sievers 已提交
1449 1450
	print "<div class=\"title_text\">\n" .
	      "<table cellspacing=\"0\">\n" .
1451 1452
	      "<tr>\n" .
	      "<td>object</td>\n" .
1453 1454
	      "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" .
	      "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" .
1455
	      "</tr>\n";
K
v235  
Kay Sievers 已提交
1456 1457
	if (defined($tag{'author'})) {
		my %ad = date_str($tag{'epoch'}, $tag{'tz'});
1458
		print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
K
v235  
Kay Sievers 已提交
1459 1460 1461 1462 1463 1464 1465
		print "<tr><td></td><td>" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "</td></tr>\n";
	}
	print "</table>\n\n" .
	      "</div>\n";
	print "<div class=\"page_body\">";
	my $comment = $tag{'comment'};
	foreach my $line (@$comment) {
1466
		print esc_html($line) . "<br/>\n";
K
v235  
Kay Sievers 已提交
1467 1468 1469 1470 1471
	}
	print "</div>\n";
	git_footer_html();
}

1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489
sub git_blame2 {
	my $fd;
	my $ftype;
	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);
	my %co = git_read_commit($hash_base)
		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 looking up file");
	}
	$ftype = git_get_type($hash);
	if ($ftype !~ "blob") {
		die_error("400 Bad Request", "object is not a blob");
	}
	open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base)
1490
		or die_error(undef, "Open git-blame failed.");
1491
	git_header_html();
1492 1493 1494 1495
	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);
1496
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
1497
	git_print_page_path($file_name, $ftype);
1498 1499 1500 1501
	my @rev_color = (qw(light dark));
	my $num_colors = scalar(@rev_color);
	my $current_color = 0;
	my $last_rev;
1502 1503 1504
	print "<div class=\"page_body\">\n";
	print "<table class=\"blame\">\n";
	print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n";
1505 1506 1507
	while (<$fd>) {
		/^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
		my $full_rev = $1;
1508
		my $rev = substr($full_rev, 0, 8);
1509 1510
		my $lineno = $2;
		my $data = $3;
1511

1512 1513 1514 1515 1516 1517 1518
		if (!defined $last_rev) {
			$last_rev = $full_rev;
		} elsif ($last_rev ne $full_rev) {
			$last_rev = $full_rev;
			$current_color = ++$current_color % $num_colors;
		}
		print "<tr class=\"$rev_color[$current_color]\">\n";
1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530
		print "<td class=\"sha1\">" .
			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n";
		print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n";
		print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
		print "</tr>\n";
	}
	print "</table>\n";
	print "</div>";
	close $fd or print "Reading blob failed\n";
	git_footer_html();
}

1531 1532
sub git_blame {
	my $fd;
1533
	die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame'));
1534 1535 1536 1537 1538 1539 1540 1541 1542
	die_error('404 Not Found', "What file will it be, master?") if (!$file_name);
	$hash_base ||= git_read_head($project);
	die_error(undef, "Reading commit failed.") unless ($hash_base);
	my %co = git_read_commit($hash_base)
		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.");
	}
1543
	open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
1544
		or die_error(undef, "Open git-annotate failed.");
1545
	git_header_html();
1546 1547 1548 1549
	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);
1550
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
1551
	git_print_page_path($file_name, 'blob');
1552 1553
	print "<div class=\"page_body\">\n";
	print <<HTML;
1554
<table class="blame">
1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574
  <tr>
    <th>Commit</th>
    <th>Age</th>
    <th>Author</th>
    <th>Line</th>
    <th>Data</th>
  </tr>
HTML
	my @line_class = (qw(light dark));
	my $line_class_len = scalar (@line_class);
	my $line_class_num = $#line_class;
	while (my $line = <$fd>) {
		my $long_rev;
		my $short_rev;
		my $author;
		my $time;
		my $lineno;
		my $data;
		my $age;
		my $age_str;
1575
		my $age_class;
1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586

		chomp $line;
		$line_class_num = ($line_class_num + 1) % $line_class_len;

		if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) {
			$long_rev = $1;
			$author   = $2;
			$time     = $3;
			$lineno   = $4;
			$data     = $5;
		} else {
1587
			print qq(  <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
1588 1589 1590 1591 1592 1593
			next;
		}
		$short_rev  = substr ($long_rev, 0, 8);
		$age        = time () - $time;
		$age_str    = age_string ($age);
		$age_str    =~ s/ /&nbsp;/g;
1594
		$age_class  = age_class($age);
1595 1596 1597 1598 1599 1600 1601 1602
		$author     = esc_html ($author);
		$author     =~ s/ /&nbsp;/g;
		# escape tabs
		while ((my $pos = index($data, "\t")) != -1) {
			if (my $count = (8 - ($pos % 8))) {
				my $spaces = ' ' x $count;
				$data =~ s/\t/$spaces/;
			}
1603 1604
		}
		$data = esc_html ($data);
1605

1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619
		print <<HTML;
  <tr class="$line_class[$line_class_num]">
    <td class="sha1"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td>
    <td class="$age_class">$age_str</td>
    <td>$author</td>
    <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
    <td class="pre">$data</td>
  </tr>
HTML
	} # while (my $line = <$fd>)
	print "</table>\n\n";
	close $fd or print "Reading blob failed.\n";
	print "</div>";
	git_footer_html();
1620 1621
}

1622 1623 1624 1625 1626
sub git_tags {
	my $head = git_read_head($project);
	git_header_html();
	git_page_nav('','', $head,undef,$head);
	git_header_div('summary', $project);
1627

1628 1629 1630
	my $taglist = git_read_refs("refs/tags");
	if (defined @$taglist) {
		git_tags_body($taglist);
1631
	}
1632
	git_footer_html();
1633 1634
}

1635 1636 1637 1638 1639
sub git_heads {
	my $head = git_read_head($project);
	git_header_html();
	git_page_nav('','', $head,undef,$head);
	git_header_div('summary', $project);
1640

1641 1642 1643 1644
	my $taglist = git_read_refs("refs/heads");
	my $alternate = 0;
	if (defined @$taglist) {
		git_heads_body($taglist, $head);
1645
	}
1646
	git_footer_html();
1647 1648
}

K
v203  
Kay Sievers 已提交
1649
sub git_blob_plain {
1650
	if (!defined $hash) {
J
Jakub Narebski 已提交
1651 1652 1653 1654 1655 1656 1657 1658
		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.");
		}
	}
1659
	my $type = shift;
1660
	open my $fd, "-|", $GIT, "cat-file", "blob", $hash
1661
		or die_error(undef, "Couldn't cat $file_name, $hash");
1662 1663

	$type ||= git_blob_plain_mimetype($fd, $file_name);
1664 1665 1666

	# save as filename, even when no $file_name is given
	my $save_as = "$hash";
1667 1668
	if (defined $file_name) {
		$save_as = $file_name;
1669 1670
	} elsif ($type =~ m/^text\//) {
		$save_as .= '.txt';
1671
	}
1672 1673

	print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\"");
K
v203  
Kay Sievers 已提交
1674
	undef $/;
1675
	binmode STDOUT, ':raw';
K
v203  
Kay Sievers 已提交
1676
	print <$fd>;
1677
	binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
K
v203  
Kay Sievers 已提交
1678 1679 1680 1681
	$/ = "\n";
	close $fd;
}

1682
sub git_blob {
1683
	if (!defined $hash) {
J
Jakub Narebski 已提交
1684 1685 1686 1687 1688 1689 1690 1691
		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.");
		}
	}
1692
	my $have_blame = git_get_project_config_bool ('blame');
1693 1694
	open my $fd, "-|", $GIT, "cat-file", "blob", $hash
		or die_error(undef, "Couldn't cat $file_name, $hash.");
1695 1696 1697 1698 1699 1700
	my $mimetype = git_blob_plain_mimetype($fd, $file_name);
	if ($mimetype !~ m/^text\//) {
		close $fd;
		return git_blob_plain($mimetype);
	}
	git_header_html();
1701
	my $formats_nav = '';
1702 1703 1704
	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
		if (defined $file_name) {
			if ($have_blame) {
1705
				$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | ";
1706
			}
1707 1708 1709
			$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");
1710
		} else {
1711
			$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");
1712
		}
1713
		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
1714
		git_header_div('commit', esc_html($co{'title'}), $hash_base);
1715 1716 1717 1718 1719
	} else {
		print "<div class=\"page_nav\">\n" .
		      "<br/><br/></div>\n" .
		      "<div class=\"title\">$hash</div>\n";
	}
1720
	git_print_page_path($file_name, "blob");
1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738
	print "<div class=\"page_body\">\n";
	my $nr;
	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/;
			}
		}
		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";
	print "</div>";
	git_footer_html();
}

K
v118  
Kay Sievers 已提交
1739
sub git_tree {
K
v107  
Kay Sievers 已提交
1740
	if (!defined $hash) {
1741
		$hash = git_read_head($project);
K
v118  
Kay Sievers 已提交
1742
		if (defined $file_name) {
1743
			my $base = $hash_base || $hash;
K
v118  
Kay Sievers 已提交
1744 1745
			$hash = git_get_hash_by_path($base, $file_name, "tree");
		}
K
v157  
Kay Sievers 已提交
1746
		if (!defined $hash_base) {
1747
			$hash_base = $hash;
K
v157  
Kay Sievers 已提交
1748
		}
K
v145  
Kay Sievers 已提交
1749
	}
1750
	$/ = "\0";
1751 1752
	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash
		or die_error(undef, "Open git-ls-tree failed.");
1753
	my @entries = map { chomp; $_ } <$fd>;
K
v203  
Kay Sievers 已提交
1754
	close $fd or die_error(undef, "Reading tree failed.");
1755
	$/ = "\n";
K
v077  
Kay Sievers 已提交
1756

K
Kay Sievers 已提交
1757
	my $refs = read_info_ref();
1758
	my $ref = git_get_referencing($refs, $hash_base);
K
v021  
Kay Sievers 已提交
1759
	git_header_html();
K
v118  
Kay Sievers 已提交
1760 1761
	my $base_key = "";
	my $base = "";
1762
	my $have_blame = git_get_project_config_bool ('blame');
K
v118  
Kay Sievers 已提交
1763 1764
	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
		$base_key = ";hb=$hash_base";
1765
		git_page_nav('tree','', $hash_base);
1766
		git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
K
v077  
Kay Sievers 已提交
1767 1768 1769 1770 1771
	} else {
		print "<div class=\"page_nav\">\n";
		print "<br/><br/></div>\n";
		print "<div class=\"title\">$hash</div>\n";
	}
K
v118  
Kay Sievers 已提交
1772
	if (defined $file_name) {
1773
		$base = esc_html("$file_name/");
K
v118  
Kay Sievers 已提交
1774
	}
1775
	git_print_page_path($file_name, 'tree');
K
v043  
Kay Sievers 已提交
1776
	print "<div class=\"page_body\">\n";
K
v125  
Kay Sievers 已提交
1777
	print "<table cellspacing=\"0\">\n";
K
v160  
Kay Sievers 已提交
1778
	my $alternate = 0;
K
Kay Sievers 已提交
1779
	foreach my $line (@entries) {
K
v003  
Kay Sievers 已提交
1780
		#'100644	blob	0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
K
v203  
Kay Sievers 已提交
1781
		$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
K
v031  
Kay Sievers 已提交
1782
		my $t_mode = $1;
K
Kay Sievers 已提交
1783 1784
		my $t_type = $2;
		my $t_hash = $3;
1785
		my $t_name = validate_input($4);
K
v160  
Kay Sievers 已提交
1786
		if ($alternate) {
K
v220  
Kay Sievers 已提交
1787
			print "<tr class=\"dark\">\n";
K
v160  
Kay Sievers 已提交
1788
		} else {
K
v220  
Kay Sievers 已提交
1789
			print "<tr class=\"light\">\n";
K
v160  
Kay Sievers 已提交
1790 1791
		}
		$alternate ^= 1;
1792
		print "<td class=\"mode\">" . mode_str($t_mode) . "</td>\n";
K
Kay Sievers 已提交
1793
		if ($t_type eq "blob") {
K
v157  
Kay Sievers 已提交
1794
			print "<td class=\"list\">" .
1795
			      $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)) .
K
v220  
Kay Sievers 已提交
1796 1797
			      "</td>\n" .
			      "<td class=\"link\">" .
1798 1799 1800 1801 1802
			      $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") .
1803
			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") .
K
v125  
Kay Sievers 已提交
1804
			      "</td>\n";
K
Kay Sievers 已提交
1805
		} elsif ($t_type eq "tree") {
K
v157  
Kay Sievers 已提交
1806
			print "<td class=\"list\">" .
1807
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) .
K
v203  
Kay Sievers 已提交
1808
			      "</td>\n" .
K
v220  
Kay Sievers 已提交
1809
			      "<td class=\"link\">" .
1810
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") .
1811
			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") .
K
v220  
Kay Sievers 已提交
1812
			      "</td>\n";
K
Kay Sievers 已提交
1813
		}
K
v125  
Kay Sievers 已提交
1814
		print "</tr>\n";
K
Kay Sievers 已提交
1815
	}
K
v125  
Kay Sievers 已提交
1816 1817
	print "</table>\n" .
	      "</div>";
K
v021  
Kay Sievers 已提交
1818
	git_footer_html();
K
v118  
Kay Sievers 已提交
1819 1820 1821
}

sub git_log {
1822
	my $head = git_read_head($project);
K
v150  
Kay Sievers 已提交
1823
	if (!defined $hash) {
K
v203  
Kay Sievers 已提交
1824
		$hash = $head;
K
v150  
Kay Sievers 已提交
1825
	}
K
v206  
Kay Sievers 已提交
1826 1827
	if (!defined $page) {
		$page = 0;
K
v107  
Kay Sievers 已提交
1828
	}
K
Kay Sievers 已提交
1829
	my $refs = read_info_ref();
K
v206  
Kay Sievers 已提交
1830 1831

	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
1832 1833
	open my $fd, "-|", $GIT, "rev-list", $limit, $hash
		or die_error(undef, "Open git-rev-list failed.");
1834
	my @revlist = map { chomp; $_ } <$fd>;
K
v206  
Kay Sievers 已提交
1835 1836
	close $fd;

1837
	my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist);
1838 1839 1840 1841

	git_header_html();
	git_page_nav('log','', $hash,undef,undef, $paging_nav);

K
v107  
Kay Sievers 已提交
1842
	if (!@revlist) {
K
v150  
Kay Sievers 已提交
1843
		my %co = git_read_commit($hash);
1844 1845

		git_header_div('summary', $project);
K
v145  
Kay Sievers 已提交
1846
		print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
K
Kay Sievers 已提交
1847
	}
K
v220  
Kay Sievers 已提交
1848 1849
	for (my $i = ($page * 100); $i <= $#revlist; $i++) {
		my $commit = $revlist[$i];
1850
		my $ref = git_get_referencing($refs, $commit);
K
v118  
Kay Sievers 已提交
1851
		my %co = git_read_commit($commit);
K
v107  
Kay Sievers 已提交
1852
		next if !%co;
K
v088  
Kay Sievers 已提交
1853
		my %ad = date_str($co{'author_epoch'});
1854 1855 1856 1857
		git_header_div('commit',
									 "<span class=\"age\">$co{'age_string'}</span>" .
									 esc_html($co{'title'}) . $ref,
									 $commit);
K
v088  
Kay Sievers 已提交
1858 1859
		print "<div class=\"title_text\">\n" .
		      "<div class=\"log_link\">\n" .
1860 1861
		      $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") .
K
v121  
Kay Sievers 已提交
1862
		      "<br/>\n" .
K
v088  
Kay Sievers 已提交
1863
		      "</div>\n" .
1864
		      "<i>" . esc_html($co{'author_name'}) .  " [$ad{'rfc2822'}]</i><br/>\n" .
K
v088  
Kay Sievers 已提交
1865 1866 1867
		      "</div>\n" .
		      "<div class=\"log_body\">\n";
		my $comment = $co{'comment'};
K
v118  
Kay Sievers 已提交
1868
		my $empty = 0;
K
v088  
Kay Sievers 已提交
1869
		foreach my $line (@$comment) {
K
v157  
Kay Sievers 已提交
1870
			if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
K
v118  
Kay Sievers 已提交
1871 1872 1873 1874 1875 1876 1877 1878 1879 1880
				next;
			}
			if ($line eq "") {
				if ($empty) {
					next;
				}
				$empty = 1;
			} else {
				$empty = 0;
			}
K
v234  
Kay Sievers 已提交
1881
			print format_log_line_html($line) . "<br/>\n";
K
v088  
Kay Sievers 已提交
1882
		}
K
v118  
Kay Sievers 已提交
1883 1884 1885 1886
		if (!$empty) {
			print "<br/>\n";
		}
		print "</div>\n";
K
v021  
Kay Sievers 已提交
1887
	}
K
v088  
Kay Sievers 已提交
1888
	git_footer_html();
K
v118  
Kay Sievers 已提交
1889 1890 1891 1892
}

sub git_commit {
	my %co = git_read_commit($hash);
K
v088  
Kay Sievers 已提交
1893
	if (!%co) {
K
v118  
Kay Sievers 已提交
1894
		die_error(undef, "Unknown commit object.");
K
v077  
Kay Sievers 已提交
1895
	}
K
v049  
Kay Sievers 已提交
1896 1897
	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
K
Kay Sievers 已提交
1898

K
v235  
Kay Sievers 已提交
1899 1900
	my $parent = $co{'parent'};
	if (!defined $parent) {
1901
		$parent = "--root";
K
v085  
Kay Sievers 已提交
1902
	}
1903
	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash
1904
		or die_error(undef, "Open git-diff-tree failed.");
1905
	my @difftree = map { chomp; $_ } <$fd>;
1906
	close $fd or die_error(undef, "Reading git-diff-tree failed.");
1907 1908 1909 1910 1911 1912

	# non-textual hash id's can be cached
	my $expires;
	if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
		$expires = "+1d";
	}
K
Kay Sievers 已提交
1913
	my $refs = read_info_ref();
1914
	my $ref = git_get_referencing($refs, $co{'id'});
1915
	my $formats_nav = '';
1916 1917
	if (defined $file_name && defined $co{'parent'}) {
		my $parent = $co{'parent'};
1918
		$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame");
1919
	}
1920
	git_header_html(undef, $expires);
1921 1922 1923
	git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
							 $hash, $co{'tree'}, $hash,
							 $formats_nav);
1924

K
v107  
Kay Sievers 已提交
1925
	if (defined $co{'parent'}) {
1926
		git_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
K
v107  
Kay Sievers 已提交
1927
	} else {
1928
		git_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash);
K
v107  
Kay Sievers 已提交
1929
	}
K
v085  
Kay Sievers 已提交
1930
	print "<div class=\"title_text\">\n" .
K
v107  
Kay Sievers 已提交
1931
	      "<table cellspacing=\"0\">\n";
1932
	print "<tr><td>author</td><td>" . esc_html($co{'author'}) . "</td></tr>\n".
K
v160  
Kay Sievers 已提交
1933 1934
	      "<tr>" .
	      "<td></td><td> $ad{'rfc2822'}";
K
v080  
Kay Sievers 已提交
1935
	if ($ad{'hour_local'} < 6) {
1936
		printf(" (<span class=\"atnight\">%02d:%02d</span> %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
K
v107  
Kay Sievers 已提交
1937 1938 1939
	} else {
		printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
	}
K
v160  
Kay Sievers 已提交
1940 1941
	print "</td>" .
	      "</tr>\n";
1942
	print "<tr><td>committer</td><td>" . esc_html($co{'committer'}) . "</td></tr>\n";
K
v145  
Kay Sievers 已提交
1943
	print "<tr><td></td><td> $cd{'rfc2822'}" . sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "</td></tr>\n";
1944
	print "<tr><td>commit</td><td class=\"sha1\">$co{'id'}</td></tr>\n";
K
v160  
Kay Sievers 已提交
1945 1946
	print "<tr>" .
	      "<td>tree</td>" .
1947
	      "<td class=\"sha1\">" .
1948
	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) .
K
v203  
Kay Sievers 已提交
1949
	      "</td>" .
1950
	      "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") .
K
v160  
Kay Sievers 已提交
1951 1952
	      "</td>" .
	      "</tr>\n";
1953
	my $parents = $co{'parents'};
K
v025  
Kay Sievers 已提交
1954
	foreach my $par (@$parents) {
K
v160  
Kay Sievers 已提交
1955 1956
		print "<tr>" .
		      "<td>parent</td>" .
1957
		      "<td class=\"sha1\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "</td>" .
K
v160  
Kay Sievers 已提交
1958
		      "<td class=\"link\">" .
1959 1960
		      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") .
		      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") .
K
v160  
Kay Sievers 已提交
1961 1962
		      "</td>" .
		      "</tr>\n";
K
v025  
Kay Sievers 已提交
1963
	}
J
Jakub Narebski 已提交
1964
	print "</table>".
K
v107  
Kay Sievers 已提交
1965
	      "</div>\n";
K
v043  
Kay Sievers 已提交
1966
	print "<div class=\"page_body\">\n";
K
v025  
Kay Sievers 已提交
1967
	my $comment = $co{'comment'};
K
v118  
Kay Sievers 已提交
1968 1969
	my $empty = 0;
	my $signed = 0;
K
v025  
Kay Sievers 已提交
1970
	foreach my $line (@$comment) {
K
v118  
Kay Sievers 已提交
1971 1972 1973 1974 1975 1976 1977 1978 1979
		# print only one empty line
		if ($line eq "") {
			if ($empty || $signed) {
				next;
			}
			$empty = 1;
		} else {
			$empty = 0;
		}
K
v157  
Kay Sievers 已提交
1980
		if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
K
v118  
Kay Sievers 已提交
1981
			$signed = 1;
1982
			print "<span class=\"signoff\">" . esc_html($line) . "</span><br/>\n";
K
v025  
Kay Sievers 已提交
1983
		} else {
K
v118  
Kay Sievers 已提交
1984
			$signed = 0;
K
v234  
Kay Sievers 已提交
1985
			print format_log_line_html($line) . "<br/>\n";
K
v025  
Kay Sievers 已提交
1986 1987
		}
	}
K
v080  
Kay Sievers 已提交
1988
	print "</div>\n";
K
v118  
Kay Sievers 已提交
1989
	print "<div class=\"list_head\">\n";
K
v085  
Kay Sievers 已提交
1990
	if ($#difftree > 10) {
K
v118  
Kay Sievers 已提交
1991
		print(($#difftree + 1) . " files changed:\n");
K
v085  
Kay Sievers 已提交
1992
	}
K
v118  
Kay Sievers 已提交
1993
	print "</div>\n";
1994
	print "<table class=\"diff_tree\">\n";
K
v160  
Kay Sievers 已提交
1995
	my $alternate = 0;
K
Kay Sievers 已提交
1996
	foreach my $line (@difftree) {
K
v203  
Kay Sievers 已提交
1997 1998
		# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M      ls-files.c'
		# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M      rev-tree.c'
K
v235  
Kay Sievers 已提交
1999 2000 2001
		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;
		}
K
v203  
Kay Sievers 已提交
2002 2003 2004 2005 2006
		my $from_mode = $1;
		my $to_mode = $2;
		my $from_id = $3;
		my $to_id = $4;
		my $status = $5;
K
v206  
Kay Sievers 已提交
2007
		my $similarity = $6;
2008
		my $file = validate_input(unquote($7));
K
v160  
Kay Sievers 已提交
2009
		if ($alternate) {
K
v220  
Kay Sievers 已提交
2010
			print "<tr class=\"dark\">\n";
K
v160  
Kay Sievers 已提交
2011
		} else {
K
v220  
Kay Sievers 已提交
2012
			print "<tr class=\"light\">\n";
K
v160  
Kay Sievers 已提交
2013 2014
		}
		$alternate ^= 1;
K
v233  
Kay Sievers 已提交
2015
		if ($status eq "A") {
K
v157  
Kay Sievers 已提交
2016
			my $mode_chng = "";
K
v203  
Kay Sievers 已提交
2017 2018
			if (S_ISREG(oct $to_mode)) {
				$mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777);
K
v157  
Kay Sievers 已提交
2019 2020
			}
			print "<td>" .
2021
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" .
2022
			      "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" .
2023
			      "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n";
K
v203  
Kay Sievers 已提交
2024
		} elsif ($status eq "D") {
K
v157  
Kay Sievers 已提交
2025
			print "<td>" .
2026
			      $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" .
2027
			      "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" .
K
v157  
Kay Sievers 已提交
2028
			      "<td class=\"link\">" .
2029
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") .
2030
			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") .
K
v157  
Kay Sievers 已提交
2031
			      "</td>\n"
K
v203  
Kay Sievers 已提交
2032
		} elsif ($status eq "M" || $status eq "T") {
K
v157  
Kay Sievers 已提交
2033 2034
			my $mode_chnge = "";
			if ($from_mode != $to_mode) {
2035
				$mode_chnge = " <span class=\"file_status mode_chnge\">[changed";
K
v157  
Kay Sievers 已提交
2036 2037
				if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) {
					$mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode);
K
v107  
Kay Sievers 已提交
2038
				}
K
v157  
Kay Sievers 已提交
2039 2040 2041 2042 2043
				if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) {
					if (S_ISREG($from_mode) && S_ISREG($to_mode)) {
						$mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777);
					} elsif (S_ISREG($to_mode)) {
						$mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777);
K
v107  
Kay Sievers 已提交
2044
					}
K
v070  
Kay Sievers 已提交
2045
				}
K
v157  
Kay Sievers 已提交
2046 2047 2048 2049
				$mode_chnge .= "]</span>\n";
			}
			print "<td>";
			if ($to_id ne $from_id) {
2050
				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file));
K
v157  
Kay Sievers 已提交
2051
			} else {
2052
				print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file));
K
Kay Sievers 已提交
2053
			}
K
v157  
Kay Sievers 已提交
2054 2055 2056
			print "</td>\n" .
			      "<td>$mode_chnge</td>\n" .
			      "<td class=\"link\">";
2057
			print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob");
K
v157  
Kay Sievers 已提交
2058
			if ($to_id ne $from_id) {
2059
				print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff");
K
v157  
Kay Sievers 已提交
2060
			}
2061
			print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n";
K
v157  
Kay Sievers 已提交
2062
			print "</td>\n";
K
v205  
Kay Sievers 已提交
2063 2064 2065 2066 2067 2068 2069
		} elsif ($status eq "R") {
			my ($from_file, $to_file) = split "\t", $file;
			my $mode_chng = "";
			if ($from_mode != $to_mode) {
				$mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777);
			}
			print "<td>" .
2070
			      $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" .
2071
			      "<td><span class=\"file_status moved\">[moved from " .
2072
			      $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)) .
K
v206  
Kay Sievers 已提交
2073
			      " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" .
K
v205  
Kay Sievers 已提交
2074
			      "<td class=\"link\">" .
2075
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob");
K
v205  
Kay Sievers 已提交
2076
			if ($to_id ne $from_id) {
2077
				print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff");
K
v205  
Kay Sievers 已提交
2078 2079
			}
			print "</td>\n";
K
Kay Sievers 已提交
2080
		}
K
v157  
Kay Sievers 已提交
2081
		print "</tr>\n";
K
Kay Sievers 已提交
2082
	}
K
v160  
Kay Sievers 已提交
2083
	print "</table>\n";
K
v021  
Kay Sievers 已提交
2084
	git_footer_html();
K
v118  
Kay Sievers 已提交
2085 2086 2087
}

sub git_blobdiff {
K
v203  
Kay Sievers 已提交
2088
	mkdir($git_temp, 0700);
K
v021  
Kay Sievers 已提交
2089
	git_header_html();
K
v118  
Kay Sievers 已提交
2090
	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
2091 2092 2093
		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);
2094
		git_header_div('commit', esc_html($co{'title'}), $hash_base);
K
v118  
Kay Sievers 已提交
2095 2096 2097 2098 2099
	} else {
		print "<div class=\"page_nav\">\n" .
		      "<br/><br/></div>\n" .
		      "<div class=\"title\">$hash vs $hash_parent</div>\n";
	}
2100
	git_print_page_path($file_name, "blob");
K
v070  
Kay Sievers 已提交
2101
	print "<div class=\"page_body\">\n" .
K
v133  
Kay Sievers 已提交
2102
	      "<div class=\"diff_info\">blob:" .
2103
	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) .
K
v089  
Kay Sievers 已提交
2104
	      " -> blob:" .
2105
	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) .
K
v133  
Kay Sievers 已提交
2106
	      "</div>\n";
K
v203  
Kay Sievers 已提交
2107
	git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash);
K
v133  
Kay Sievers 已提交
2108
	print "</div>";
K
v021  
Kay Sievers 已提交
2109
	git_footer_html();
K
v118  
Kay Sievers 已提交
2110 2111
}

K
v203  
Kay Sievers 已提交
2112 2113 2114 2115 2116 2117
sub git_blobdiff_plain {
	mkdir($git_temp, 0700);
	print $cgi->header(-type => "text/plain", -charset => 'utf-8');
	git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain");
}

K
v118  
Kay Sievers 已提交
2118
sub git_commitdiff {
K
v203  
Kay Sievers 已提交
2119
	mkdir($git_temp, 0700);
K
v118  
Kay Sievers 已提交
2120
	my %co = git_read_commit($hash);
K
v088  
Kay Sievers 已提交
2121
	if (!%co) {
K
v118  
Kay Sievers 已提交
2122
		die_error(undef, "Unknown commit object.");
K
v077  
Kay Sievers 已提交
2123
	}
K
v160  
Kay Sievers 已提交
2124 2125 2126
	if (!defined $hash_parent) {
		$hash_parent = $co{'parent'};
	}
2127
	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
2128
		or die_error(undef, "Open git-diff-tree failed.");
2129
	my @difftree = map { chomp; $_ } <$fd>;
K
v203  
Kay Sievers 已提交
2130
	close $fd or die_error(undef, "Reading diff-tree failed.");
K
Kay Sievers 已提交
2131

2132 2133 2134 2135 2136
	# non-textual hash id's can be cached
	my $expires;
	if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
		$expires = "+1d";
	}
K
Kay Sievers 已提交
2137
	my $refs = read_info_ref();
2138
	my $ref = git_get_referencing($refs, $co{'id'});
2139 2140
	my $formats_nav =
		$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain");
2141
	git_header_html(undef, $expires);
2142
	git_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
2143
	git_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
K
v133  
Kay Sievers 已提交
2144
	print "<div class=\"page_body\">\n";
K
v150  
Kay Sievers 已提交
2145 2146 2147
	my $comment = $co{'comment'};
	my $empty = 0;
	my $signed = 0;
K
v152  
Kay Sievers 已提交
2148
	my @log = @$comment;
K
v154  
Kay Sievers 已提交
2149
	# remove first and empty lines after that
K
v152  
Kay Sievers 已提交
2150
	shift @log;
K
v154  
Kay Sievers 已提交
2151 2152 2153
	while (defined $log[0] && $log[0] eq "") {
		shift @log;
	}
K
v152  
Kay Sievers 已提交
2154
	foreach my $line (@log) {
K
v157  
Kay Sievers 已提交
2155
		if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
K
v150  
Kay Sievers 已提交
2156 2157 2158 2159 2160 2161 2162 2163 2164 2165
			next;
		}
		if ($line eq "") {
			if ($empty) {
				next;
			}
			$empty = 1;
		} else {
			$empty = 0;
		}
K
v234  
Kay Sievers 已提交
2166
		print format_log_line_html($line) . "<br/>\n";
K
v150  
Kay Sievers 已提交
2167 2168
	}
	print "<br/>\n";
K
v000  
Kay Sievers 已提交
2169
	foreach my $line (@difftree) {
K
v203  
Kay Sievers 已提交
2170 2171 2172 2173 2174 2175 2176 2177
		# ':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(.*)$/;
		my $from_mode = $1;
		my $to_mode = $2;
		my $from_id = $3;
		my $to_id = $4;
		my $status = $5;
2178
		my $file = validate_input(unquote($6));
K
v233  
Kay Sievers 已提交
2179
		if ($status eq "A") {
J
Jakub Narebski 已提交
2180
			print "<div class=\"diff_info\">" . file_type($to_mode) . ":" .
2181
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" .
K
v203  
Kay Sievers 已提交
2182 2183 2184 2185
			      "</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) . ":" .
2186
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" .
K
v203  
Kay Sievers 已提交
2187 2188 2189 2190 2191
			      "</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\">" .
2192
				      file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) .
K
v203  
Kay Sievers 已提交
2193
				      " -> " .
2194
				      file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id);
K
v203  
Kay Sievers 已提交
2195 2196
				print "</div>\n";
				git_diff_print($from_id, "a/$file",  $to_id, "b/$file");
K
v000  
Kay Sievers 已提交
2197 2198
			}
		}
K
Kay Sievers 已提交
2199
	}
K
v133  
Kay Sievers 已提交
2200 2201
	print "<br/>\n" .
	      "</div>";
K
v021  
Kay Sievers 已提交
2202
	git_footer_html();
K
v118  
Kay Sievers 已提交
2203 2204
}

K
v203  
Kay Sievers 已提交
2205 2206
sub git_commitdiff_plain {
	mkdir($git_temp, 0700);
2207
	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
2208
		or die_error(undef, "Open git-diff-tree failed.");
2209
	my @difftree = map { chomp; $_ } <$fd>;
K
v203  
Kay Sievers 已提交
2210 2211
	close $fd or die_error(undef, "Reading diff-tree failed.");

K
v232  
Kay Sievers 已提交
2212 2213
	# try to figure out the next tag after this commit
	my $tagname;
K
Kay Sievers 已提交
2214
	my $refs = read_info_ref("tags");
2215
	open $fd, "-|", $GIT, "rev-list", "HEAD";
2216
	my @commits = map { chomp; $_ } <$fd>;
K
Kay Sievers 已提交
2217 2218 2219 2220
	close $fd;
	foreach my $commit (@commits) {
		if (defined $refs->{$commit}) {
			$tagname = $refs->{$commit}
K
v232  
Kay Sievers 已提交
2221 2222 2223 2224 2225 2226
		}
		if ($commit eq $hash) {
			last;
		}
	}

2227
	print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\"");
K
v220  
Kay Sievers 已提交
2228 2229 2230
	my %co = git_read_commit($hash);
	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
	my $comment = $co{'comment'};
K
v232  
Kay Sievers 已提交
2231
	print "From: $co{'author'}\n" .
K
v220  
Kay Sievers 已提交
2232
	      "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
K
v232  
Kay Sievers 已提交
2233 2234
	      "Subject: $co{'title'}\n";
	if (defined $tagname) {
J
Jakub Narebski 已提交
2235
		print "X-Git-Tag: $tagname\n";
K
v232  
Kay Sievers 已提交
2236 2237
	}
	print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
K
v220  
Kay Sievers 已提交
2238
	      "\n";
K
v232  
Kay Sievers 已提交
2239

K
v220  
Kay Sievers 已提交
2240
	foreach my $line (@$comment) {;
2241
		print "$line\n";
K
v220  
Kay Sievers 已提交
2242
	}
K
v232  
Kay Sievers 已提交
2243 2244
	print "---\n\n";

K
v203  
Kay Sievers 已提交
2245 2246 2247 2248 2249 2250
	foreach my $line (@difftree) {
		$line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/;
		my $from_id = $3;
		my $to_id = $4;
		my $status = $5;
		my $file = $6;
K
v233  
Kay Sievers 已提交
2251
		if ($status eq "A") {
K
v203  
Kay Sievers 已提交
2252 2253 2254 2255 2256 2257 2258 2259 2260
			git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain");
		} elsif ($status eq "D") {
			git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain");
		} elsif ($status eq "M") {
			git_diff_print($from_id, "a/$file",  $to_id, "b/$file", "plain");
		}
	}
}

K
v118  
Kay Sievers 已提交
2261
sub git_history {
2262 2263
	if (!defined $hash_base) {
		$hash_base = git_read_head($project);
K
v118  
Kay Sievers 已提交
2264
	}
2265
	my $ftype;
2266
	my %co = git_read_commit($hash_base);
K
v118  
Kay Sievers 已提交
2267 2268
	if (!%co) {
		die_error(undef, "Unknown commit object.");
K
v062  
Kay Sievers 已提交
2269
	}
K
Kay Sievers 已提交
2270
	my $refs = read_info_ref();
K
v057  
Kay Sievers 已提交
2271
	git_header_html();
2272
	git_page_nav('','', $hash_base,$co{'tree'},$hash_base);
2273
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
2274 2275 2276
	if (!defined $hash && defined $file_name) {
		$hash = git_get_hash_by_path($hash_base, $file_name);
	}
2277
	if (defined $hash) {
2278
		$ftype = git_get_type($hash);
2279
	}
2280
	git_print_page_path($file_name, $ftype);
K
v157  
Kay Sievers 已提交
2281

2282
	open my $fd, "-|",
2283
		$GIT, "rev-list", "--full-history", $hash_base, "--", $file_name;
K
v160  
Kay Sievers 已提交
2284 2285
	print "<table cellspacing=\"0\">\n";
	my $alternate = 0;
K
v107  
Kay Sievers 已提交
2286
	while (my $line = <$fd>) {
K
v229  
Kay Sievers 已提交
2287
		if ($line =~ m/^([0-9a-fA-F]{40})/){
2288
			my $commit = $1;
K
v118  
Kay Sievers 已提交
2289
			my %co = git_read_commit($commit);
K
v107  
Kay Sievers 已提交
2290 2291 2292
			if (!%co) {
				next;
			}
2293
			my $ref = git_get_referencing($refs, $commit);
K
v160  
Kay Sievers 已提交
2294
			if ($alternate) {
K
v220  
Kay Sievers 已提交
2295
				print "<tr class=\"dark\">\n";
K
v160  
Kay Sievers 已提交
2296
			} else {
K
v220  
Kay Sievers 已提交
2297
				print "<tr class=\"light\">\n";
K
v160  
Kay Sievers 已提交
2298 2299
			}
			$alternate ^= 1;
K
v225  
Kay Sievers 已提交
2300
			print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
2301
			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
2302
			      "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" .
K
Kay Sievers 已提交
2303
			      esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" .
K
v157  
Kay Sievers 已提交
2304
			      "<td class=\"link\">" .
2305 2306
			      $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") .
2307
			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$ftype;hb=$commit;f=$file_name")}, $ftype);
2308
			my $blob = git_get_hash_by_path($hash_base, $file_name);
K
v125  
Kay Sievers 已提交
2309 2310
			my $blob_parent = git_get_hash_by_path($commit, $file_name);
			if (defined $blob && defined $blob_parent && $blob ne $blob_parent) {
K
v220  
Kay Sievers 已提交
2311
				print " | " .
2312
				$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")},
K
v220  
Kay Sievers 已提交
2313
				"diff to current");
K
v125  
Kay Sievers 已提交
2314
			}
K
v157  
Kay Sievers 已提交
2315 2316
			print "</td>\n" .
			      "</tr>\n";
K
v062  
Kay Sievers 已提交
2317
		}
K
v057  
Kay Sievers 已提交
2318
	}
K
v160  
Kay Sievers 已提交
2319
	print "</table>\n";
K
v107  
Kay Sievers 已提交
2320
	close $fd;
K
v057  
Kay Sievers 已提交
2321
	git_footer_html();
K
Kay Sievers 已提交
2322
}
K
v203  
Kay Sievers 已提交
2323 2324 2325

sub git_search {
	if (!defined $searchtext) {
2326
		die_error(undef, "Text field empty.");
K
v203  
Kay Sievers 已提交
2327 2328
	}
	if (!defined $hash) {
2329
		$hash = git_read_head($project);
K
v203  
Kay Sievers 已提交
2330 2331 2332 2333 2334
	}
	my %co = git_read_commit($hash);
	if (!%co) {
		die_error(undef, "Unknown commit object.");
	}
K
v220  
Kay Sievers 已提交
2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348
	# 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 :)
	my $commit_search = 1;
	my $author_search = 0;
	my $committer_search = 0;
	my $pickaxe_search = 0;
	if ($searchtext =~ s/^author\\://i) {
		$author_search = 1;
	} elsif ($searchtext =~ s/^committer\\://i) {
		$committer_search = 1;
	} elsif ($searchtext =~ s/^pickaxe\\://i) {
		$commit_search = 0;
		$pickaxe_search = 1;
	}
K
v203  
Kay Sievers 已提交
2349
	git_header_html();
2350
	git_page_nav('','', $hash,$co{'tree'},$hash);
2351
	git_header_div('commit', esc_html($co{'title'}), $hash);
K
v203  
Kay Sievers 已提交
2352 2353 2354

	print "<table cellspacing=\"0\">\n";
	my $alternate = 0;
K
v220  
Kay Sievers 已提交
2355 2356
	if ($commit_search) {
		$/ = "\0";
2357
		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next;
K
v220  
Kay Sievers 已提交
2358 2359 2360
		while (my $commit_text = <$fd>) {
			if (!grep m/$searchtext/i, $commit_text) {
				next;
K
v203  
Kay Sievers 已提交
2361
			}
K
v220  
Kay Sievers 已提交
2362 2363
			if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
				next;
K
v203  
Kay Sievers 已提交
2364
			}
K
v220  
Kay Sievers 已提交
2365
			if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
K
v203  
Kay Sievers 已提交
2366 2367
				next;
			}
K
v220  
Kay Sievers 已提交
2368
			my @commit_lines = split "\n", $commit_text;
2369
			my %co = git_read_commit(undef, \@commit_lines);
K
v220  
Kay Sievers 已提交
2370 2371 2372 2373 2374 2375 2376 2377 2378
			if (!%co) {
				next;
			}
			if ($alternate) {
				print "<tr class=\"dark\">\n";
			} else {
				print "<tr class=\"light\">\n";
			}
			$alternate ^= 1;
K
v225  
Kay Sievers 已提交
2379
			print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
2380
			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
K
v220  
Kay Sievers 已提交
2381
			      "<td>" .
2382
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" . esc_html(chop_str($co{'title'}, 50)) . "</b><br/>");
K
v220  
Kay Sievers 已提交
2383 2384 2385
			my $comment = $co{'comment'};
			foreach my $line (@$comment) {
				if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
2386
					my $lead = esc_html($1) || "";
K
v220  
Kay Sievers 已提交
2387
					$lead = chop_str($lead, 30, 10);
2388 2389
					my $match = esc_html($2) || "";
					my $trail = esc_html($3) || "";
K
v220  
Kay Sievers 已提交
2390
					$trail = chop_str($trail, 30, 10);
2391
					my $text = "$lead<span class=\"match\">$match</span>$trail";
K
v220  
Kay Sievers 已提交
2392
					print chop_str($text, 80, 5) . "<br/>\n";
K
v203  
Kay Sievers 已提交
2393
				}
K
v220  
Kay Sievers 已提交
2394 2395 2396
			}
			print "</td>\n" .
			      "<td class=\"link\">" .
2397 2398
			      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") .
			      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree");
K
v220  
Kay Sievers 已提交
2399 2400 2401 2402 2403 2404 2405 2406
			print "</td>\n" .
			      "</tr>\n";
		}
		close $fd;
	}

	if ($pickaxe_search) {
		$/ = "\n";
2407
		open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'";
K
v220  
Kay Sievers 已提交
2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418
		undef %co;
		my @files;
		while (my $line = <$fd>) {
			if (%co && $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
				my %set;
				$set{'file'} = $6;
				$set{'from_id'} = $3;
				$set{'to_id'} = $4;
				$set{'id'} = $set{'to_id'};
				if ($set{'id'} =~ m/0{40}/) {
					$set{'id'} = $set{'from_id'};
K
v203  
Kay Sievers 已提交
2419
				}
K
v220  
Kay Sievers 已提交
2420 2421 2422 2423
				if ($set{'id'} =~ m/0{40}/) {
					next;
				}
				push @files, \%set;
2424
			} elsif ($line =~ m/^([0-9a-fA-F]{40})$/){
K
v220  
Kay Sievers 已提交
2425 2426 2427 2428 2429 2430 2431
				if (%co) {
					if ($alternate) {
						print "<tr class=\"dark\">\n";
					} else {
						print "<tr class=\"light\">\n";
					}
					$alternate ^= 1;
K
v225  
Kay Sievers 已提交
2432
					print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
2433
					      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
K
v220  
Kay Sievers 已提交
2434
					      "<td>" .
2435
					      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" .
2436
					      esc_html(chop_str($co{'title'}, 50)) . "</b><br/>");
K
v220  
Kay Sievers 已提交
2437 2438
					while (my $setref = shift @files) {
						my %set = %$setref;
2439
						print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"},
2440
						      "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") .
K
v220  
Kay Sievers 已提交
2441 2442 2443 2444
						      "<br/>\n";
					}
					print "</td>\n" .
					      "<td class=\"link\">" .
2445 2446
					      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}")}, "commit") .
					      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$co{'id'}")}, "tree");
K
v220  
Kay Sievers 已提交
2447 2448 2449 2450
					print "</td>\n" .
					      "</tr>\n";
				}
				%co = git_read_commit($1);
K
v203  
Kay Sievers 已提交
2451 2452
			}
		}
K
v220  
Kay Sievers 已提交
2453
		close $fd;
K
v203  
Kay Sievers 已提交
2454 2455 2456 2457 2458 2459
	}
	print "</table>\n";
	git_footer_html();
}

sub git_shortlog {
2460
	my $head = git_read_head($project);
K
v203  
Kay Sievers 已提交
2461 2462 2463
	if (!defined $hash) {
		$hash = $head;
	}
K
v206  
Kay Sievers 已提交
2464 2465 2466
	if (!defined $page) {
		$page = 0;
	}
K
Kay Sievers 已提交
2467
	my $refs = read_info_ref();
K
v206  
Kay Sievers 已提交
2468 2469

	my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
2470 2471
	open my $fd, "-|", $GIT, "rev-list", $limit, $hash
		or die_error(undef, "Open git-rev-list failed.");
2472
	my @revlist = map { chomp; $_ } <$fd>;
K
v203  
Kay Sievers 已提交
2473
	close $fd;
K
v206  
Kay Sievers 已提交
2474

2475
	my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist);
2476 2477 2478 2479 2480 2481 2482
	my $next_link = '';
	if ($#revlist >= (100 * ($page+1)-1)) {
		$next_link =
			$cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)),
			         -title => "Alt-n"}, "next");
	}

2483 2484 2485

	git_header_html();
	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
2486
	git_header_div('summary', $project);
2487

2488 2489
	git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);

K
v203  
Kay Sievers 已提交
2490 2491
	git_footer_html();
}
2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586

## ......................................................................
## feeds (RSS, OPML)

sub git_rss {
	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
	open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project)
		or die_error(undef, "Open git-rev-list failed.");
	my @revlist = map { chomp; $_ } <$fd>;
	close $fd or die_error(undef, "Reading rev-list failed.");
	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
	      "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";
	print "<channel>\n";
	print "<title>$project</title>\n".
	      "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
	      "<description>$project log</description>\n".
	      "<language>en</language>\n";

	for (my $i = 0; $i <= $#revlist; $i++) {
		my $commit = $revlist[$i];
		my %co = git_read_commit($commit);
		# we read 150, we always show 30 and the ones more recent than 48 hours
		if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) {
			last;
		}
		my %cd = date_str($co{'committer_epoch'});
		open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next;
		my @difftree = map { chomp; $_ } <$fd>;
		close $fd or next;
		print "<item>\n" .
		      "<title>" .
		      sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
		      "</title>\n" .
		      "<author>" . esc_html($co{'author'}) . "</author>\n" .
		      "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
		      "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" .
		      "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" .
		      "<description>" . esc_html($co{'title'}) . "</description>\n" .
		      "<content:encoded>" .
		      "<![CDATA[\n";
		my $comment = $co{'comment'};
		foreach my $line (@$comment) {
			$line = decode("utf8", $line, Encode::FB_DEFAULT);
			print "$line<br/>\n";
		}
		print "<br/>\n";
		foreach my $line (@difftree) {
			if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
				next;
			}
			my $file = validate_input(unquote($7));
			$file = decode("utf8", $file, Encode::FB_DEFAULT);
			print "$file<br/>\n";
		}
		print "]]>\n" .
		      "</content:encoded>\n" .
		      "</item>\n";
	}
	print "</channel></rss>";
}

sub git_opml {
	my @list = git_read_projects();

	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
	print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
	      "<opml version=\"1.0\">\n".
	      "<head>".
	      "  <title>$site_name Git OPML Export</title>\n".
	      "</head>\n".
	      "<body>\n".
	      "<outline text=\"git RSS feeds\">\n";

	foreach my $pr (@list) {
		my %proj = %$pr;
		my $head = git_read_head($proj{'path'});
		if (!defined $head) {
			next;
		}
		$ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}";
		my %co = git_read_commit($head);
		if (!%co) {
			next;
		}

		my $path = esc_html(chop_str($proj{'path'}, 25, 5));
		my $rss  = "$my_url?p=$proj{'path'};a=rss";
		my $html = "$my_url?p=$proj{'path'};a=summary";
		print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n";
	}
	print "</outline>\n".
	      "</body>\n".
	      "</opml>\n";
}