gitweb.perl 79.2 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 35
# version of the core git binary
our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
36

K
v107  
Kay Sievers 已提交
37
# location for temporary files needed for diffs
38
our $git_temp = "/tmp/gitweb";
39
if (! -d $git_temp) {
J
Jakub Narebski 已提交
40
	mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
41
}
K
v088  
Kay Sievers 已提交
42

K
v107  
Kay Sievers 已提交
43
# target of the home link on top of all pages
44
our $home_link = $my_uri;
K
v107  
Kay Sievers 已提交
45

46 47
# name of your site or organization to appear in page titles
# replace this with something more descriptive for clearer bookmarks
48
our $site_name = "@@GITWEB_SITENAME@@" || $ENV{'SERVER_NAME'} || "Untitled";
49

K
v136  
Kay Sievers 已提交
50
# html text to include at home page
51
our $home_text = "@@GITWEB_HOMETEXT@@";
K
v136  
Kay Sievers 已提交
52

J
Jakub Narebski 已提交
53
# URI of default stylesheet
54
our $stylesheet = "@@GITWEB_CSS@@";
J
Jakub Narebski 已提交
55

K
v118  
Kay Sievers 已提交
56
# source of projects list
57
our $projects_list = "@@GITWEB_LIST@@" || "$projectroot";
K
v107  
Kay Sievers 已提交
58

59
# default blob_plain mimetype and default charset for text/plain blob
60 61
our $default_blob_plain_mimetype = 'text/plain';
our $default_text_plain_charset  = undef;
62

63 64
# file to use for guessing MIME types before trying /etc/mime.types
# (relative to the current git repository)
65
our $mimetypes_file = undef;
66

K
v118  
Kay Sievers 已提交
67
# input validation and dispatch
68
our $action = $cgi->param('a');
K
v118  
Kay Sievers 已提交
69
if (defined $action) {
70
	if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
K
v118  
Kay Sievers 已提交
71 72
		undef $action;
		die_error(undef, "Invalid action parameter.");
K
v107  
Kay Sievers 已提交
73
	}
K
v118  
Kay Sievers 已提交
74 75 76
	if ($action eq "git-logo.png") {
		git_logo();
		exit;
K
v220  
Kay Sievers 已提交
77 78 79
	} elsif ($action eq "opml") {
		git_opml();
		exit;
K
v118  
Kay Sievers 已提交
80
	}
K
v107  
Kay Sievers 已提交
81
}
K
v014  
Kay Sievers 已提交
82

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

106
our $file_name = $cgi->param('f');
K
v107  
Kay Sievers 已提交
107
if (defined $file_name) {
108 109 110
	$file_name = validate_input($file_name);
	if (!defined($file_name)) {
		die_error(undef, "Invalid file parameter.");
K
v107  
Kay Sievers 已提交
111
	}
K
v055  
Kay Sievers 已提交
112
}
K
v085  
Kay Sievers 已提交
113

114
our $hash = $cgi->param('h');
K
v227  
Kay Sievers 已提交
115
if (defined $hash) {
116 117 118
	$hash = validate_input($hash);
	if (!defined($hash)) {
		die_error(undef, "Invalid hash parameter.");
K
v227  
Kay Sievers 已提交
119
	}
K
v055  
Kay Sievers 已提交
120
}
K
v085  
Kay Sievers 已提交
121

122
our $hash_parent = $cgi->param('hp');
123 124 125 126 127
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 已提交
128 129
}

130
our $hash_base = $cgi->param('hb');
131 132 133 134 135
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 已提交
136
}
K
v085  
Kay Sievers 已提交
137

138
our $page = $cgi->param('pg');
K
v206  
Kay Sievers 已提交
139
if (defined $page) {
140
	if ($page =~ m/[^0-9]$/) {
K
v206  
Kay Sievers 已提交
141 142
		undef $page;
		die_error(undef, "Invalid page parameter.");
K
v107  
Kay Sievers 已提交
143
	}
K
v053  
Kay Sievers 已提交
144
}
K
v005  
Kay Sievers 已提交
145

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

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

185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
## ======================================================================
## 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;
}

203 204 205
# 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 已提交
206
	my $str = shift;
207
	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
208
	$str =~ s/\+/%2B/g;
K
Kay Sievers 已提交
209
	$str =~ s/ /\+/g;
K
Kay Sievers 已提交
210 211 212
	return $str;
}

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

222 223 224 225 226 227 228 229 230 231
# 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;
}

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
## ----------------------------------------------------------------------
## 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

255 256 257 258 259 260 261 262 263 264 265 266 267
# 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";
	}
}

268 269 270 271
# convert age in seconds to "nn units ago" string
sub age_string {
	my $age = shift;
	my $age_str;
K
v055  
Kay Sievers 已提交
272

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

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
# 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 已提交
315
	} else {
316
		return '----------';
K
v048  
Kay Sievers 已提交
317
	}
K
Kay Sievers 已提交
318 319
}

320 321 322
# convert file mode in octal to file type string
sub file_type {
	my $mode = oct shift;
K
v064  
Kay Sievers 已提交
323

324 325 326 327 328 329 330 331 332
	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 已提交
333 334
}

335 336 337
## ----------------------------------------------------------------------
## functions returning short HTML fragments, or transforming HTML fragments
## which don't beling to other sections
338

339 340 341
# format line of commit message or tag comment
sub format_log_line_html {
	my $line = shift;
342

343 344 345 346 347 348 349
	$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/;
350 351
		}
	}
352
	return $line;
353 354
}

355 356 357
# format marker of refs pointing to given object
sub git_get_referencing {
	my ($refs, $id) = @_;
358

359 360 361 362 363
	if (defined $refs->{$id}) {
		return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
	} else {
		return "";
	}
364 365
}

366 367
## ----------------------------------------------------------------------
## git utility subroutines, invoking git commands
K
v125  
Kay Sievers 已提交
368

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

388 389 390 391 392 393 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
# 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 已提交
439
sub git_read_hash {
K
v041  
Kay Sievers 已提交
440
	my $path = shift;
K
v118  
Kay Sievers 已提交
441

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

K
v118  
Kay Sievers 已提交
451
sub git_read_description {
K
v107  
Kay Sievers 已提交
452
	my $path = shift;
K
v118  
Kay Sievers 已提交
453

K
v203  
Kay Sievers 已提交
454
	open my $fd, "$projectroot/$path/description" or return undef;
K
v107  
Kay Sievers 已提交
455 456 457 458
	my $descr = <$fd>;
	close $fd;
	chomp $descr;
	return $descr;
K
v021  
Kay Sievers 已提交
459 460
}

461 462 463 464 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
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 已提交
555 556 557
sub git_read_tag {
	my $tag_id = shift;
	my %tag;
K
v235  
Kay Sievers 已提交
558
	my @comment;
K
v142  
Kay Sievers 已提交
559

560
	open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return;
K
v235  
Kay Sievers 已提交
561
	$tag{'id'} = $tag_id;
K
v142  
Kay Sievers 已提交
562 563 564 565
	while (my $line = <$fd>) {
		chomp $line;
		if ($line =~ m/^object ([0-9a-fA-F]{40})$/) {
			$tag{'object'} = $1;
K
v163  
Kay Sievers 已提交
566
		} elsif ($line =~ m/^type (.+)$/) {
K
v142  
Kay Sievers 已提交
567
			$tag{'type'} = $1;
K
v163  
Kay Sievers 已提交
568
		} elsif ($line =~ m/^tag (.+)$/) {
K
v142  
Kay Sievers 已提交
569
			$tag{'name'} = $1;
K
v235  
Kay Sievers 已提交
570 571 572 573 574 575 576 577 578
		} 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 已提交
579 580
		}
	}
K
v235  
Kay Sievers 已提交
581 582
	push @comment, <$fd>;
	$tag{'comment'} = \@comment;
K
v203  
Kay Sievers 已提交
583
	close $fd or return;
K
v142  
Kay Sievers 已提交
584 585 586 587 588 589
	if (!defined $tag{'name'}) {
		return
	};
	return %tag
}

K
v118  
Kay Sievers 已提交
590
sub git_read_commit {
K
v203  
Kay Sievers 已提交
591 592 593 594
	my $commit_id = shift;
	my $commit_text = shift;

	my @commit_lines;
K
v021  
Kay Sievers 已提交
595 596
	my %co;

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

K
v203  
Kay Sievers 已提交
639
	foreach my $title (@commit_lines) {
640
		$title =~ s/^    //;
K
v203  
Kay Sievers 已提交
641
		if ($title ne "") {
K
v241  
Kay Sievers 已提交
642
			$co{'title'} = chop_str($title, 80, 5);
K
v203  
Kay Sievers 已提交
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
			# 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 已提交
660
			$co{'title_short'} = chop_str($title, 50, 5);
K
v203  
Kay Sievers 已提交
661 662 663
			last;
		}
	}
664 665 666 667 668
	# remove added spaces
	foreach my $line (@commit_lines) {
		$line =~ s/^    //;
	}
	$co{'comment'} = \@commit_lines;
K
v062  
Kay Sievers 已提交
669 670 671

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

684 685
## ......................................................................
## parse to array of hashes functions
K
v000  
Kay Sievers 已提交
686

687 688 689
sub git_read_refs {
	my $ref_dir = shift;
	my @reflist;
K
v000  
Kay Sievers 已提交
690

691
	my @refs;
692 693 694 695 696
	my $pfxlen = length("$projectroot/$project/$ref_dir");
	File::Find::find(sub {
		return if (/^\./);
		if (-f $_) {
			push @refs, substr($File::Find::name, $pfxlen + 1);
697
		}
698 699
	}, "$projectroot/$project/$ref_dir");

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

738 739 740 741 742
		push @reflist, \%ref_item;
	}
	# sort tags by age
	@reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
	return \@reflist;
K
v041  
Kay Sievers 已提交
743 744
}

745 746
## ----------------------------------------------------------------------
## filesystem-related functions
K
v035  
Kay Sievers 已提交
747

K
v133  
Kay Sievers 已提交
748 749 750 751 752 753 754 755 756 757
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/[,;].*$//;
758
	return decode("utf8", $owner, Encode::FB_DEFAULT);
K
v133  
Kay Sievers 已提交
759 760
}

761 762
## ......................................................................
## mimetype related functions
K
v118  
Kay Sievers 已提交
763

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

782 783 784
	$filename =~ /\.(.*?)$/;
	return $mimemap{$1};
}
785

786 787 788 789
sub mimetype_guess {
	my $filename = shift;
	my $mime;
	$filename =~ /\./ or return undef;
790

791 792 793 794 795 796 797
	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;
798 799
}

800 801 802
sub git_blob_plain_mimetype {
	my $fd = shift;
	my $filename = shift;
803

804 805 806
	if ($filename) {
		my $mime = mimetype_guess($filename);
		$mime and return $mime;
807
	}
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822

	# 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';
823
	} else {
824
		return 'application/octet-stream';
825
	}
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846
}

## ======================================================================
## 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 .= "/";
				}
			}
		}
847
	}
848 849 850 851 852 853 854
	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';
855
	} else {
856
		$content_type = 'text/html';
857
	}
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
	print <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
<!-- git core binaries version $git_version -->
<head>
<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
<meta name="robots" content="index, nofollow"/>
<title>$title</title>
<link rel="stylesheet" type="text/css" href="$stylesheet"/>
$rss_link
</head>
<body>
EOF
	print "<div class=\"page_header\">\n" .
	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
	      "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
	      "</a>\n";
	print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
	if (defined $project) {
		print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project));
		if (defined $action) {
			print " / $action";
		}
		print "\n";
		if (!defined $searchtext) {
			$searchtext = "";
		}
		my $search_hash;
		if (defined $hash_base) {
			$search_hash = $hash_base;
		} elsif (defined $hash) {
			$search_hash = $hash;
K
v160  
Kay Sievers 已提交
893
		} else {
894
			$search_hash = "HEAD";
K
v160  
Kay Sievers 已提交
895
		}
896 897 898 899 900 901 902 903 904 905
		$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 已提交
906
	}
907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
	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 已提交
936
	git_footer_html();
937
	exit;
K
Kay Sievers 已提交
938 939
}

940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
## ----------------------------------------------------------------------
## 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 已提交
960
			}
K
Kay Sievers 已提交
961 962
		}
	}
963 964 965 966 967 968 969 970 971 972 973 974
	$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 已提交
975 976
}

977 978 979
sub git_get_paging_nav {
	my ($action, $hash, $head, $page, $nrevs) = @_;
	my $paging_nav;
980

981 982 983

	if ($hash ne $head || $page) {
		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
984
	} else {
985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001
		$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";
1002
	}
1003 1004

	return $paging_nav;
1005 1006
}

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
## ......................................................................
## 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 已提交
1022

1023 1024 1025
sub git_print_page_path {
	my $name = shift;
	my $type = shift;
K
v142  
Kay Sievers 已提交
1026

1027 1028
	if (!defined $name) {
		print "<div class=\"page_path\"><b>/</b></div>\n";
1029
	} elsif (defined $type && $type eq 'blob') {
1030 1031 1032 1033
		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 已提交
1034 1035 1036
	}
}

1037 1038 1039
## ......................................................................
## functions printing large fragments of HTML

1040 1041 1042 1043 1044 1045 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
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";
}

1088 1089 1090 1091 1092 1093 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 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288
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

# git-logo (cached in browser for one day)
sub git_logo {
	binmode STDOUT, ':raw';
	print $cgi->header(-type => 'image/png', -expires => '+1d');
	# cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g'
	print	"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
		"\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
		"\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
		"\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
		"\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
		"\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
		"\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
		"\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
		"\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
		"\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
		"\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
		"\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
		"\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
}
1289

1290
sub git_project_list {
1291 1292 1293 1294 1295
	my $order = $cgi->param('o');
	if (defined $order && $order !~ m/project|descr|owner|age/) {
		die_error(undef, "Invalid order parameter '$order'.");
	}

1296 1297 1298
	my @list = git_read_projects();
	my @projects;
	if (!@list) {
1299
		die_error(undef, "No projects found.");
1300 1301 1302 1303 1304
	}
	foreach my $pr (@list) {
		my $head = git_read_head($pr->{'path'});
		if (!defined $head) {
			next;
1305
		}
1306 1307 1308 1309
		$ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}";
		my %co = git_read_commit($head);
		if (!%co) {
			next;
1310
		}
1311 1312 1313 1314
		$pr->{'commit'} = \%co;
		if (!defined $pr->{'descr'}) {
			my $descr = git_read_description($pr->{'path'}) || "";
			$pr->{'descr'} = chop_str($descr, 25, 5);
1315
		}
1316 1317
		if (!defined $pr->{'owner'}) {
			$pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
1318
		}
1319
		push @projects, $pr;
1320
	}
1321

1322 1323 1324 1325 1326 1327 1328
	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";
1329
	}
1330 1331
	print "<table class=\"project_list\">\n" .
	      "<tr>\n";
1332 1333
	$order ||= "project";
	if ($order eq "project") {
1334 1335 1336
		@projects = sort {$a->{'path'} cmp $b->{'path'}} @projects;
		print "<th>Project</th>\n";
	} else {
1337 1338 1339 1340
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=project"),
		               -class => "header"}, "Project") .
		      "</th>\n";
1341
	}
1342
	if ($order eq "descr") {
1343 1344 1345
		@projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects;
		print "<th>Description</th>\n";
	} else {
1346 1347 1348 1349
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=descr"),
		               -class => "header"}, "Description") .
		      "</th>\n";
1350
	}
1351
	if ($order eq "owner") {
1352 1353 1354
		@projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects;
		print "<th>Owner</th>\n";
	} else {
1355 1356 1357 1358
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=owner"),
		               -class => "header"}, "Owner") .
		      "</th>\n";
1359
	}
1360
	if ($order eq "age") {
1361 1362 1363
		@projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects;
		print "<th>Last Change</th>\n";
	} else {
1364 1365 1366 1367
		print "<th>" .
		      $cgi->a({-href => "$my_uri?" . esc_param("o=age"),
		               -class => "header"}, "Last Change") .
		      "</th>\n";
1368 1369 1370
	}
	print "<th></th>\n" .
	      "</tr>\n";
1371
	my $alternate = 0;
1372
	foreach my $pr (@projects) {
1373 1374 1375 1376 1377 1378
		if ($alternate) {
			print "<tr class=\"dark\">\n";
		} else {
			print "<tr class=\"light\">\n";
		}
		$alternate ^= 1;
1379 1380
		print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"),
		                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
1381 1382
		      "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
		      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
1383 1384
		print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" .
		      $pr->{'commit'}{'age_string'} . "</td>\n" .
1385
		      "<td class=\"link\">" .
1386 1387 1388
		      $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") .
1389 1390 1391 1392
		      "</td>\n" .
		      "</tr>\n";
	}
	print "</table>\n";
1393
	git_footer_html();
1394 1395
}

K
v142  
Kay Sievers 已提交
1396 1397
sub git_summary {
	my $descr = git_read_description($project) || "none";
1398
	my $head = git_read_head($project);
K
v142  
Kay Sievers 已提交
1399 1400 1401 1402 1403 1404 1405 1406
	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 已提交
1407 1408 1409
			my ($pr, $ow) = split ' ', $line;
			$pr = unescape($pr);
			$ow = unescape($ow);
K
v142  
Kay Sievers 已提交
1410
			if ($pr eq $project) {
1411
				$owner = decode("utf8", $ow, Encode::FB_DEFAULT);
K
v142  
Kay Sievers 已提交
1412 1413 1414 1415 1416 1417 1418 1419 1420
				last;
			}
		}
		close $fd;
	}
	if (!defined $owner) {
		$owner = get_file_owner("$projectroot/$project");
	}

K
Kay Sievers 已提交
1421
	my $refs = read_info_ref();
K
v142  
Kay Sievers 已提交
1422
	git_header_html();
1423
	git_page_nav('summary','', $head);
1424

K
v203  
Kay Sievers 已提交
1425
	print "<div class=\"title\">&nbsp;</div>\n";
K
v160  
Kay Sievers 已提交
1426
	print "<table cellspacing=\"0\">\n" .
1427
	      "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
K
v142  
Kay Sievers 已提交
1428 1429
	      "<tr><td>owner</td><td>$owner</td></tr>\n" .
	      "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
K
v160  
Kay Sievers 已提交
1430
	      "</table>\n";
1431

1432
	open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project)
1433
		or die_error(undef, "Open git-rev-list failed.");
1434
	my @revlist = map { chomp; $_ } <$fd>;
K
v142  
Kay Sievers 已提交
1435
	close $fd;
1436
	git_header_div('shortlog');
1437 1438
	git_shortlog_body(\@revlist, 0, 15, $refs,
	                  $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "..."));
K
v142  
Kay Sievers 已提交
1439

K
v150  
Kay Sievers 已提交
1440
	my $taglist = git_read_refs("refs/tags");
K
v142  
Kay Sievers 已提交
1441
	if (defined @$taglist) {
1442
		git_header_div('tags');
1443 1444
		git_tags_body($taglist, 0, 15,
		              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "..."));
K
v142  
Kay Sievers 已提交
1445
	}
K
v150  
Kay Sievers 已提交
1446

K
Kay Sievers 已提交
1447 1448
	my $headlist = git_read_refs("refs/heads");
	if (defined @$headlist) {
1449
		git_header_div('heads');
1450
		git_heads_body($headlist, $head, 0, 15,
1451
		               $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
K
v150  
Kay Sievers 已提交
1452
	}
1453

K
v142  
Kay Sievers 已提交
1454 1455 1456
	git_footer_html();
}

K
v235  
Kay Sievers 已提交
1457
sub git_tag {
1458
	my $head = git_read_head($project);
K
v235  
Kay Sievers 已提交
1459
	git_header_html();
1460
	git_page_nav('','', $head,undef,$head);
K
v235  
Kay Sievers 已提交
1461
	my %tag = git_read_tag($hash);
1462
	git_header_div('commit', esc_html($tag{'name'}), $hash);
K
v235  
Kay Sievers 已提交
1463 1464
	print "<div class=\"title_text\">\n" .
	      "<table cellspacing=\"0\">\n" .
1465 1466
	      "<tr>\n" .
	      "<td>object</td>\n" .
1467 1468
	      "<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" .
1469
	      "</tr>\n";
K
v235  
Kay Sievers 已提交
1470 1471
	if (defined($tag{'author'})) {
		my %ad = date_str($tag{'epoch'}, $tag{'tz'});
1472
		print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
K
v235  
Kay Sievers 已提交
1473 1474 1475 1476 1477 1478 1479
		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) {
1480
		print esc_html($line) . "<br/>\n";
K
v235  
Kay Sievers 已提交
1481 1482 1483 1484 1485
	}
	print "</div>\n";
	git_footer_html();
}

1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503
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)
1504
		or die_error(undef, "Open git-blame failed.");
1505
	git_header_html();
1506 1507 1508 1509
	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);
1510
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
1511
	git_print_page_path($file_name, $ftype);
1512 1513 1514 1515
	my @rev_color = (qw(light dark));
	my $num_colors = scalar(@rev_color);
	my $current_color = 0;
	my $last_rev;
1516 1517 1518
	print "<div class=\"page_body\">\n";
	print "<table class=\"blame\">\n";
	print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n";
1519 1520 1521
	while (<$fd>) {
		/^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
		my $full_rev = $1;
1522
		my $rev = substr($full_rev, 0, 8);
1523 1524
		my $lineno = $2;
		my $data = $3;
1525

1526 1527 1528 1529 1530 1531 1532
		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";
1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544
		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();
}

1545 1546
sub git_blame {
	my $fd;
1547
	die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame'));
1548 1549 1550 1551 1552 1553 1554 1555 1556
	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.");
	}
1557
	open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
1558
		or die_error(undef, "Open git-annotate failed.");
1559
	git_header_html();
1560 1561 1562 1563
	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);
1564
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
1565
	git_print_page_path($file_name, 'blob');
1566 1567
	print "<div class=\"page_body\">\n";
	print <<HTML;
1568
<table class="blame">
1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588
  <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;
1589
		my $age_class;
1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600

		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 {
1601
			print qq(  <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
1602 1603 1604 1605 1606 1607
			next;
		}
		$short_rev  = substr ($long_rev, 0, 8);
		$age        = time () - $time;
		$age_str    = age_string ($age);
		$age_str    =~ s/ /&nbsp;/g;
1608
		$age_class  = age_class($age);
1609 1610 1611 1612 1613 1614 1615 1616
		$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/;
			}
1617 1618
		}
		$data = esc_html ($data);
1619

1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633
		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();
1634 1635
}

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

1642 1643 1644
	my $taglist = git_read_refs("refs/tags");
	if (defined @$taglist) {
		git_tags_body($taglist);
1645
	}
1646
	git_footer_html();
1647 1648
}

1649 1650 1651 1652 1653
sub git_heads {
	my $head = git_read_head($project);
	git_header_html();
	git_page_nav('','', $head,undef,$head);
	git_header_div('summary', $project);
1654

1655 1656 1657 1658
	my $taglist = git_read_refs("refs/heads");
	my $alternate = 0;
	if (defined @$taglist) {
		git_heads_body($taglist, $head);
1659
	}
1660
	git_footer_html();
1661 1662
}

K
v203  
Kay Sievers 已提交
1663
sub git_blob_plain {
1664
	if (!defined $hash) {
J
Jakub Narebski 已提交
1665 1666 1667 1668 1669 1670 1671 1672
		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.");
		}
	}
1673
	my $type = shift;
1674 1675
	open my $fd, "-|", $GIT, "cat-file", "blob", $hash
		or die_error("Couldn't cat $file_name, $hash");
1676 1677

	$type ||= git_blob_plain_mimetype($fd, $file_name);
1678 1679 1680

	# save as filename, even when no $file_name is given
	my $save_as = "$hash";
1681 1682
	if (defined $file_name) {
		$save_as = $file_name;
1683 1684
	} elsif ($type =~ m/^text\//) {
		$save_as .= '.txt';
1685
	}
1686 1687

	print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\"");
K
v203  
Kay Sievers 已提交
1688
	undef $/;
1689
	binmode STDOUT, ':raw';
K
v203  
Kay Sievers 已提交
1690
	print <$fd>;
1691
	binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
K
v203  
Kay Sievers 已提交
1692 1693 1694 1695
	$/ = "\n";
	close $fd;
}

1696
sub git_blob {
1697
	if (!defined $hash) {
J
Jakub Narebski 已提交
1698 1699 1700 1701 1702 1703 1704 1705
		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.");
		}
	}
1706
	my $have_blame = git_get_project_config_bool ('blame');
1707 1708
	open my $fd, "-|", $GIT, "cat-file", "blob", $hash
		or die_error(undef, "Couldn't cat $file_name, $hash.");
1709 1710 1711 1712 1713 1714
	my $mimetype = git_blob_plain_mimetype($fd, $file_name);
	if ($mimetype !~ m/^text\//) {
		close $fd;
		return git_blob_plain($mimetype);
	}
	git_header_html();
1715
	my $formats_nav = '';
1716 1717 1718
	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
		if (defined $file_name) {
			if ($have_blame) {
1719
				$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | ";
1720
			}
1721 1722 1723
			$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");
1724
		} else {
1725
			$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");
1726
		}
1727
		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
1728
		git_header_div('commit', esc_html($co{'title'}), $hash_base);
1729 1730 1731 1732 1733
	} else {
		print "<div class=\"page_nav\">\n" .
		      "<br/><br/></div>\n" .
		      "<div class=\"title\">$hash</div>\n";
	}
1734
	git_print_page_path($file_name, "blob");
1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752
	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 已提交
1753
sub git_tree {
K
v107  
Kay Sievers 已提交
1754
	if (!defined $hash) {
1755
		$hash = git_read_head($project);
K
v118  
Kay Sievers 已提交
1756
		if (defined $file_name) {
1757
			my $base = $hash_base || $hash;
K
v118  
Kay Sievers 已提交
1758 1759
			$hash = git_get_hash_by_path($base, $file_name, "tree");
		}
K
v157  
Kay Sievers 已提交
1760
		if (!defined $hash_base) {
1761
			$hash_base = $hash;
K
v157  
Kay Sievers 已提交
1762
		}
K
v145  
Kay Sievers 已提交
1763
	}
1764
	$/ = "\0";
1765 1766
	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash
		or die_error(undef, "Open git-ls-tree failed.");
1767
	my @entries = map { chomp; $_ } <$fd>;
K
v203  
Kay Sievers 已提交
1768
	close $fd or die_error(undef, "Reading tree failed.");
1769
	$/ = "\n";
K
v077  
Kay Sievers 已提交
1770

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

sub git_log {
1833
	my $head = git_read_head($project);
K
v150  
Kay Sievers 已提交
1834
	if (!defined $hash) {
K
v203  
Kay Sievers 已提交
1835
		$hash = $head;
K
v150  
Kay Sievers 已提交
1836
	}
K
v206  
Kay Sievers 已提交
1837 1838
	if (!defined $page) {
		$page = 0;
K
v107  
Kay Sievers 已提交
1839
	}
K
Kay Sievers 已提交
1840
	my $refs = read_info_ref();
K
v206  
Kay Sievers 已提交
1841 1842

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

1848
	my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist);
1849 1850 1851 1852

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

K
v107  
Kay Sievers 已提交
1853
	if (!@revlist) {
K
v150  
Kay Sievers 已提交
1854
		my %co = git_read_commit($hash);
1855 1856

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

sub git_commit {
	my %co = git_read_commit($hash);
K
v088  
Kay Sievers 已提交
1904
	if (!%co) {
K
v118  
Kay Sievers 已提交
1905
		die_error(undef, "Unknown commit object.");
K
v077  
Kay Sievers 已提交
1906
	}
K
v049  
Kay Sievers 已提交
1907 1908
	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
K
Kay Sievers 已提交
1909

K
v235  
Kay Sievers 已提交
1910 1911
	my $parent = $co{'parent'};
	if (!defined $parent) {
1912
		$parent = "--root";
K
v085  
Kay Sievers 已提交
1913
	}
1914
	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash
1915
		or die_error(undef, "Open git-diff-tree failed.");
1916
	my @difftree = map { chomp; $_ } <$fd>;
1917
	close $fd or die_error(undef, "Reading git-diff-tree failed.");
1918 1919 1920 1921 1922 1923

	# non-textual hash id's can be cached
	my $expires;
	if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
		$expires = "+1d";
	}
K
Kay Sievers 已提交
1924
	my $refs = read_info_ref();
1925
	my $ref = git_get_referencing($refs, $co{'id'});
1926
	my $formats_nav = '';
1927 1928
	if (defined $file_name && defined $co{'parent'}) {
		my $parent = $co{'parent'};
1929
		$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame");
1930
	}
1931
	git_header_html(undef, $expires);
1932 1933 1934
	git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
							 $hash, $co{'tree'}, $hash,
							 $formats_nav);
1935

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

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

K
v203  
Kay Sievers 已提交
2123 2124 2125 2126 2127 2128
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 已提交
2129
sub git_commitdiff {
K
v203  
Kay Sievers 已提交
2130
	mkdir($git_temp, 0700);
K
v118  
Kay Sievers 已提交
2131
	my %co = git_read_commit($hash);
K
v088  
Kay Sievers 已提交
2132
	if (!%co) {
K
v118  
Kay Sievers 已提交
2133
		die_error(undef, "Unknown commit object.");
K
v077  
Kay Sievers 已提交
2134
	}
K
v160  
Kay Sievers 已提交
2135 2136 2137
	if (!defined $hash_parent) {
		$hash_parent = $co{'parent'};
	}
2138
	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
2139
		or die_error(undef, "Open git-diff-tree failed.");
2140
	my @difftree = map { chomp; $_ } <$fd>;
K
v203  
Kay Sievers 已提交
2141
	close $fd or die_error(undef, "Reading diff-tree failed.");
K
Kay Sievers 已提交
2142

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

K
v203  
Kay Sievers 已提交
2216 2217
sub git_commitdiff_plain {
	mkdir($git_temp, 0700);
2218
	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
2219
		or die_error(undef, "Open git-diff-tree failed.");
2220
	my @difftree = map { chomp; $_ } <$fd>;
K
v203  
Kay Sievers 已提交
2221 2222
	close $fd or die_error(undef, "Reading diff-tree failed.");

K
v232  
Kay Sievers 已提交
2223 2224
	# try to figure out the next tag after this commit
	my $tagname;
K
Kay Sievers 已提交
2225
	my $refs = read_info_ref("tags");
2226
	open $fd, "-|", $GIT, "rev-list", "HEAD";
2227
	my @commits = map { chomp; $_ } <$fd>;
K
Kay Sievers 已提交
2228 2229 2230 2231
	close $fd;
	foreach my $commit (@commits) {
		if (defined $refs->{$commit}) {
			$tagname = $refs->{$commit}
K
v232  
Kay Sievers 已提交
2232 2233 2234 2235 2236 2237
		}
		if ($commit eq $hash) {
			last;
		}
	}

2238
	print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\"");
K
v220  
Kay Sievers 已提交
2239 2240 2241
	my %co = git_read_commit($hash);
	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
	my $comment = $co{'comment'};
K
v232  
Kay Sievers 已提交
2242
	print "From: $co{'author'}\n" .
K
v220  
Kay Sievers 已提交
2243
	      "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
K
v232  
Kay Sievers 已提交
2244 2245
	      "Subject: $co{'title'}\n";
	if (defined $tagname) {
J
Jakub Narebski 已提交
2246
		print "X-Git-Tag: $tagname\n";
K
v232  
Kay Sievers 已提交
2247 2248
	}
	print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
K
v220  
Kay Sievers 已提交
2249
	      "\n";
K
v232  
Kay Sievers 已提交
2250

K
v220  
Kay Sievers 已提交
2251
	foreach my $line (@$comment) {;
2252
		print "$line\n";
K
v220  
Kay Sievers 已提交
2253
	}
K
v232  
Kay Sievers 已提交
2254 2255
	print "---\n\n";

K
v203  
Kay Sievers 已提交
2256 2257 2258 2259 2260 2261
	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 已提交
2262
		if ($status eq "A") {
K
v203  
Kay Sievers 已提交
2263 2264 2265 2266 2267 2268 2269 2270 2271
			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 已提交
2272
sub git_history {
2273 2274
	if (!defined $hash_base) {
		$hash_base = git_read_head($project);
K
v118  
Kay Sievers 已提交
2275
	}
2276
	my $ftype;
2277
	my %co = git_read_commit($hash_base);
K
v118  
Kay Sievers 已提交
2278 2279
	if (!%co) {
		die_error(undef, "Unknown commit object.");
K
v062  
Kay Sievers 已提交
2280
	}
K
Kay Sievers 已提交
2281
	my $refs = read_info_ref();
K
v057  
Kay Sievers 已提交
2282
	git_header_html();
2283
	git_page_nav('','', $hash_base,$co{'tree'},$hash_base);
2284
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
2285 2286 2287
	if (!defined $hash && defined $file_name) {
		$hash = git_get_hash_by_path($hash_base, $file_name);
	}
2288
	if (defined $hash) {
2289
		$ftype = git_get_type($hash);
2290
	}
2291
	git_print_page_path($file_name, $ftype);
K
v157  
Kay Sievers 已提交
2292

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

sub git_search {
	if (!defined $searchtext) {
		die_error("", "Text field empty.");
	}
	if (!defined $hash) {
2340
		$hash = git_read_head($project);
K
v203  
Kay Sievers 已提交
2341 2342 2343 2344 2345
	}
	my %co = git_read_commit($hash);
	if (!%co) {
		die_error(undef, "Unknown commit object.");
	}
K
v220  
Kay Sievers 已提交
2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359
	# 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 已提交
2360
	git_header_html();
2361
	git_page_nav('','', $hash,$co{'tree'},$hash);
2362
	git_header_div('commit', esc_html($co{'title'}), $hash);
K
v203  
Kay Sievers 已提交
2363 2364 2365

	print "<table cellspacing=\"0\">\n";
	my $alternate = 0;
K
v220  
Kay Sievers 已提交
2366 2367
	if ($commit_search) {
		$/ = "\0";
2368
		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next;
K
v220  
Kay Sievers 已提交
2369 2370 2371
		while (my $commit_text = <$fd>) {
			if (!grep m/$searchtext/i, $commit_text) {
				next;
K
v203  
Kay Sievers 已提交
2372
			}
K
v220  
Kay Sievers 已提交
2373 2374
			if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
				next;
K
v203  
Kay Sievers 已提交
2375
			}
K
v220  
Kay Sievers 已提交
2376
			if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
K
v203  
Kay Sievers 已提交
2377 2378
				next;
			}
K
v220  
Kay Sievers 已提交
2379
			my @commit_lines = split "\n", $commit_text;
2380
			my %co = git_read_commit(undef, \@commit_lines);
K
v220  
Kay Sievers 已提交
2381 2382 2383 2384 2385 2386 2387 2388 2389
			if (!%co) {
				next;
			}
			if ($alternate) {
				print "<tr class=\"dark\">\n";
			} else {
				print "<tr class=\"light\">\n";
			}
			$alternate ^= 1;
K
v225  
Kay Sievers 已提交
2390
			print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
2391
			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
K
v220  
Kay Sievers 已提交
2392
			      "<td>" .
2393
			      $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 已提交
2394 2395 2396
			my $comment = $co{'comment'};
			foreach my $line (@$comment) {
				if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
2397
					my $lead = esc_html($1) || "";
K
v220  
Kay Sievers 已提交
2398
					$lead = chop_str($lead, 30, 10);
2399 2400
					my $match = esc_html($2) || "";
					my $trail = esc_html($3) || "";
K
v220  
Kay Sievers 已提交
2401
					$trail = chop_str($trail, 30, 10);
2402
					my $text = "$lead<span class=\"match\">$match</span>$trail";
K
v220  
Kay Sievers 已提交
2403
					print chop_str($text, 80, 5) . "<br/>\n";
K
v203  
Kay Sievers 已提交
2404
				}
K
v220  
Kay Sievers 已提交
2405 2406 2407
			}
			print "</td>\n" .
			      "<td class=\"link\">" .
2408 2409
			      $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 已提交
2410 2411 2412 2413 2414 2415 2416 2417
			print "</td>\n" .
			      "</tr>\n";
		}
		close $fd;
	}

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

sub git_shortlog {
2471
	my $head = git_read_head($project);
K
v203  
Kay Sievers 已提交
2472 2473 2474
	if (!defined $hash) {
		$hash = $head;
	}
K
v206  
Kay Sievers 已提交
2475 2476 2477
	if (!defined $page) {
		$page = 0;
	}
K
Kay Sievers 已提交
2478
	my $refs = read_info_ref();
K
v206  
Kay Sievers 已提交
2479 2480

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

2486
	my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist);
2487 2488 2489 2490 2491 2492 2493
	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");
	}

2494 2495 2496

	git_header_html();
	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
2497
	git_header_div('summary', $project);
2498

2499 2500
	git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);

K
v203  
Kay Sievers 已提交
2501 2502
	git_footer_html();
}
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 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597

## ......................................................................
## 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";
}