gitweb.perl 78.0 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
our $my_url = $cgi->url();
our $my_uri = $cgi->url(-absolute => 1);
K
v025  
Kay Sievers 已提交
24

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

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

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

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

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

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

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

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

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

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

62
our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
63 64 65 66 67 68 69
require $GITWEB_CONFIG if -e $GITWEB_CONFIG;

# version of the core git binary
our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";

$projects_list ||= $projectroot;
if (! -d $git_temp) {
70
	mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp");
71 72
}

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

87
our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
88 89
$project =~ s|^/||; $project =~ s|/$||;
if (defined $project && $project) {
90
	if (!validate_input($project)) {
91
		die_error(undef, "Invalid project parameter");
K
v070  
Kay Sievers 已提交
92 93
	}
	if (!(-d "$projectroot/$project")) {
94
		die_error(undef, "No such directory");
K
v107  
Kay Sievers 已提交
95 96
	}
	if (!(-e "$projectroot/$project/HEAD")) {
97
		die_error(undef, "No such project");
K
v070  
Kay Sievers 已提交
98
	}
K
v227  
Kay Sievers 已提交
99
	$ENV{'GIT_DIR'} = "$projectroot/$project";
K
v118  
Kay Sievers 已提交
100
} else {
K
v142  
Kay Sievers 已提交
101
	git_project_list();
K
v118  
Kay Sievers 已提交
102
	exit;
K
v055  
Kay Sievers 已提交
103
}
K
v085  
Kay Sievers 已提交
104

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

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

119
our $hash_parent = $cgi->param('hp');
120
if (defined $hash_parent) {
121
	if (!validate_input($hash_parent)) {
122
		die_error(undef, "Invalid hash parent parameter");
123
	}
K
v118  
Kay Sievers 已提交
124 125
}

126
our $hash_base = $cgi->param('hb');
127
if (defined $hash_base) {
128
	if (!validate_input($hash_base)) {
129
		die_error(undef, "Invalid hash base parameter");
130
	}
K
v055  
Kay Sievers 已提交
131
}
K
v085  
Kay Sievers 已提交
132

133
our $page = $cgi->param('pg');
K
v206  
Kay Sievers 已提交
134
if (defined $page) {
135
	if ($page =~ m/[^0-9]$/) {
136
		die_error(undef, "Invalid page parameter");
K
v107  
Kay Sievers 已提交
137
	}
K
v053  
Kay Sievers 已提交
138
}
K
v005  
Kay Sievers 已提交
139

140
our $searchtext = $cgi->param('s');
K
v203  
Kay Sievers 已提交
141 142
if (defined $searchtext) {
	if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
143
		die_error(undef, "Invalid search parameter");
K
v203  
Kay Sievers 已提交
144 145 146 147
	}
	$searchtext = quotemeta $searchtext;
}

148
# dispatch
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
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})) {
172
	die_error(undef, "Unknown action");
K
v118  
Kay Sievers 已提交
173
}
174 175
$actions{$action}->();
exit;
K
v118  
Kay Sievers 已提交
176

177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
## ======================================================================
## 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;
}

195 196 197
# 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 已提交
198
	my $str = shift;
199
	$str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
200
	$str =~ s/\+/%2B/g;
K
Kay Sievers 已提交
201
	$str =~ s/ /\+/g;
K
Kay Sievers 已提交
202 203 204
	return $str;
}

205
# replace invalid utf8 character with SUBSTITUTION sequence
206 207 208
sub esc_html {
	my $str = shift;
	$str = decode("utf8", $str, Encode::FB_DEFAULT);
K
Kay Sievers 已提交
209
	$str = escapeHTML($str);
210
	$str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
211 212 213
	return $str;
}

214 215 216 217 218 219 220 221 222 223
# 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;
}

224 225 226 227 228 229 230 231 232 233 234 235 236 237
# escape tabs (convert tabs to spaces)
sub untabify {
	my $line = shift;

	while ((my $pos = index($line, "\t")) != -1) {
		if (my $count = (8 - ($pos % 8))) {
			my $spaces = ' ' x $count;
			$line =~ s/\t/$spaces/;
		}
	}

	return $line;
}

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
## ----------------------------------------------------------------------
## HTML aware string manipulation

sub chop_str {
	my $str = shift;
	my $len = shift;
	my $add_len = shift || 10;

	# allow only $len chars, but don't cut a word if it would fit in $add_len
	# if it doesn't fit, cut it if it's still longer than the dots we would add
	$str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
	my $body = $1;
	my $tail = $2;
	if (length($tail) > 4) {
		$tail = " ...";
		$body =~ s/&[^;]*$//; # remove chopped character entities
	}
	return "$body$tail";
}

## ----------------------------------------------------------------------
## functions returning short strings

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

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

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

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
	if ($age > 60*60*24*365*2) {
		$age_str = (int $age/60/60/24/365);
		$age_str .= " years ago";
	} elsif ($age > 60*60*24*(365/12)*2) {
		$age_str = int $age/60/60/24/(365/12);
		$age_str .= " months ago";
	} elsif ($age > 60*60*24*7*2) {
		$age_str = int $age/60/60/24/7;
		$age_str .= " weeks ago";
	} elsif ($age > 60*60*24*2) {
		$age_str = int $age/60/60/24;
		$age_str .= " days ago";
	} elsif ($age > 60*60*2) {
		$age_str = int $age/60/60;
		$age_str .= " hours ago";
	} elsif ($age > 60*2) {
		$age_str = int $age/60;
		$age_str .= " min ago";
	} elsif ($age > 2) {
		$age_str = int $age;
		$age_str .= " sec ago";
300
	} else {
301
		$age_str .= " right now";
K
v000  
Kay Sievers 已提交
302
	}
303
	return $age_str;
K
Kay Sievers 已提交
304 305
}

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

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

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

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

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

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

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

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

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

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

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

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
# 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
432
		or die_error(undef, "Open git-ls-tree failed");
433 434 435 436 437 438 439 440 441 442 443 444
	my $line = <$fd>;
	close $fd or return undef;

	#'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa	panic.c'
	$line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
	return $3;
}

## ......................................................................
## git utility functions, directly accessing git repository

# assumes that PATH is not symref
K
v142  
Kay Sievers 已提交
445
sub git_read_hash {
K
v041  
Kay Sievers 已提交
446
	my $path = shift;
K
v118  
Kay Sievers 已提交
447

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

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

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

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

	if (-d $projects_list) {
		# search in directory
		my $dir = $projects_list;
		opendir my ($dh), $dir or return undef;
		while (my $dir = readdir($dh)) {
			if (-e "$projectroot/$dir/HEAD") {
				my $pr = {
					path => $dir,
				};
				push @list, $pr
			}
		}
		closedir($dh);
	} elsif (-f $projects_list) {
		# read from file(url-encoded):
		# 'git%2Fgit.git Linus+Torvalds'
		# 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
		# 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
		open my ($fd), $projects_list or return undef;
		while (my $line = <$fd>) {
			chomp $line;
			my ($path, $owner) = split ' ', $line;
			$path = unescape($path);
			$owner = unescape($owner);
			if (!defined $path) {
				next;
			}
			if (-e "$projectroot/$path/HEAD") {
				my $pr = {
					path => $path,
					owner => decode("utf8", $owner, Encode::FB_DEFAULT),
				};
				push @list, $pr
			}
		}
		close $fd;
	}
	@list = sort {$a->{'path'} cmp $b->{'path'}} @list;
	return @list;
}

sub read_info_ref {
	my $type = shift || "";
	my %refs;
	# 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c	refs/tags/v2.6.11
	# c39ae07f393806ccf406ef966e9a15afc43cc36a	refs/tags/v2.6.11^{}
	open my $fd, "$projectroot/$project/info/refs" or return;
	while (my $line = <$fd>) {
		chomp $line;
		# attention: for $type == "" it saves only last path part of ref name
		# e.g. from 'refs/heads/jn/gitweb' it would leave only 'gitweb'
		if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) {
			if (defined $refs{$1}) {
				$refs{$1} .= " / $2";
			} else {
				$refs{$1} = $2;
			}
		}
	}
	close $fd or return;
	return \%refs;
}

## ----------------------------------------------------------------------
## parse to hash functions

sub date_str {
	my $epoch = shift;
	my $tz = shift || "-0000";

	my %date;
	my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
	my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
	my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
	$date{'hour'} = $hour;
	$date{'minute'} = $min;
	$date{'mday'} = $mday;
	$date{'day'} = $days[$wday];
	$date{'month'} = $months[$mon];
	$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
	$date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;

	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
	$date{'hour_local'} = $hour;
	$date{'minute_local'} = $min;
	$date{'tz_local'} = $tz;
	return %date;
}

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

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

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

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

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

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

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

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

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

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

706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
	foreach my $ref_file (@refs) {
		my $ref_id = git_read_hash("$project/$ref_dir/$ref_file");
		my $type = git_get_type($ref_id) || next;
		my %ref_item;
		my %co;
		$ref_item{'type'} = $type;
		$ref_item{'id'} = $ref_id;
		$ref_item{'epoch'} = 0;
		$ref_item{'age'} = "unknown";
		if ($type eq "tag") {
			my %tag = git_read_tag($ref_id);
			$ref_item{'comment'} = $tag{'comment'};
			if ($tag{'type'} eq "commit") {
				%co = git_read_commit($tag{'object'});
				$ref_item{'epoch'} = $co{'committer_epoch'};
				$ref_item{'age'} = $co{'age_string'};
			} elsif (defined($tag{'epoch'})) {
				my $age = time - $tag{'epoch'};
				$ref_item{'epoch'} = $tag{'epoch'};
				$ref_item{'age'} = age_string($age);
K
v203  
Kay Sievers 已提交
726
			}
727 728 729 730 731 732 733 734 735 736 737
			$ref_item{'reftype'} = $tag{'type'};
			$ref_item{'name'} = $tag{'name'};
			$ref_item{'refid'} = $tag{'object'};
		} elsif ($type eq "commit"){
			%co = git_read_commit($ref_id);
			$ref_item{'reftype'} = "commit";
			$ref_item{'name'} = $ref_file;
			$ref_item{'title'} = $co{'title'};
			$ref_item{'refid'} = $ref_id;
			$ref_item{'epoch'} = $co{'committer_epoch'};
			$ref_item{'age'} = $co{'age_string'};
K
v041  
Kay Sievers 已提交
738
		} else {
739 740 741
			$ref_item{'reftype'} = $type;
			$ref_item{'name'} = $ref_file;
			$ref_item{'refid'} = $ref_id;
K
v234  
Kay Sievers 已提交
742
		}
K
v042  
Kay Sievers 已提交
743

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

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

K
v133  
Kay Sievers 已提交
754 755 756 757 758 759 760 761 762 763
sub get_file_owner {
	my $path = shift;

	my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path);
	my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
	if (!defined $gcos) {
		return undef;
	}
	my $owner = $gcos;
	$owner =~ s/[,;].*$//;
764
	return decode("utf8", $owner, Encode::FB_DEFAULT);
K
v133  
Kay Sievers 已提交
765 766
}

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

770 771 772 773 774 775 776 777 778
sub mimetype_guess_file {
	my $filename = shift;
	my $mimemap = shift;
	-r $mimemap or return undef;

	my %mimemap;
	open(MIME, $mimemap) or return undef;
	while (<MIME>) {
		my ($mime, $exts) = split(/\t+/);
779 780 781 782 783
		if (defined $exts) {
			my @exts = split(/\s+/, $exts);
			foreach my $ext (@exts) {
				$mimemap{$ext} = $mime;
			}
K
v118  
Kay Sievers 已提交
784 785
		}
	}
786
	close(MIME);
K
v118  
Kay Sievers 已提交
787

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

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

797 798 799 800 801 802 803
	if ($mimetypes_file) {
		my $file = $mimetypes_file;
		#$file =~ m#^/# or $file = "$projectroot/$path/$file";
		$mime = mimetype_guess_file($filename, $file);
	}
	$mime ||= mimetype_guess_file($filename, '/etc/mime.types');
	return $mime;
804 805
}

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

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

	# just in case
	return $default_blob_plain_mimetype unless $fd;

	if (-T $fd) {
		return 'text/plain' .
		       ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
	} elsif (! $filename) {
		return 'application/octet-stream';
	} elsif ($filename =~ m/\.png$/i) {
		return 'image/png';
	} elsif ($filename =~ m/\.gif$/i) {
		return 'image/gif';
	} elsif ($filename =~ m/\.jpe?g$/i) {
		return 'image/jpeg';
829
	} else {
830
		return 'application/octet-stream';
831
	}
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
}

## ======================================================================
## functions printing HTML: header, footer, error page

sub git_header_html {
	my $status = shift || "200 OK";
	my $expires = shift;

	my $title = "$site_name git";
	if (defined $project) {
		$title .= " - $project";
		if (defined $action) {
			$title .= "/$action";
			if (defined $file_name) {
				$title .= " - $file_name";
				if ($action eq "tree" && $file_name !~ m|/$|) {
					$title .= "/";
				}
			}
		}
853
	}
854 855 856 857 858
	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.
859
	if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
860
		$content_type = 'application/xhtml+xml';
861
	} else {
862
		$content_type = 'text/html';
863
	}
864 865 866 867 868 869 870 871 872 873 874 875 876
	print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires);
	print <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke -->
<!-- git core binaries version $git_version -->
<head>
<meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
<meta name="robots" content="index, nofollow"/>
<title>$title</title>
<link rel="stylesheet" type="text/css" href="$stylesheet"/>
EOF
J
Jakub Narebski 已提交
877 878 879 880 881 882
	print "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" .
	      "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>\n" .
	      "</head>\n";

	print "<body>\n" .
	      "<div class=\"page_header\">\n" .
883
	      "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
M
Martin Waitz 已提交
884
	      "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
	      "</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 已提交
901
		} else {
902
			$search_hash = "HEAD";
K
v160  
Kay Sievers 已提交
903
		}
904 905 906 907 908 909 910 911 912 913
		$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 已提交
914
	}
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
	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 已提交
944
	git_footer_html();
945
	exit;
K
Kay Sievers 已提交
946 947
}

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

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

989 990 991

	if ($hash ne $head || $page) {
		$paging_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$action")}, "HEAD");
992
	} else {
993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
		$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";
1010
	}
1011 1012

	return $paging_nav;
1013 1014
}

1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
## ......................................................................
## 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 已提交
1030

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

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

1045 1046 1047
## ......................................................................
## functions printing large fragments of HTML

1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095
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";
}

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
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;
			}
1254
			$line = untabify($line);
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273
			print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
		}
	}
	close $fd;

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


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

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

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

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

K
v142  
Kay Sievers 已提交
1379 1380
sub git_summary {
	my $descr = git_read_description($project) || "none";
1381
	my $head = git_read_head($project);
K
v142  
Kay Sievers 已提交
1382 1383 1384 1385 1386 1387 1388 1389
	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 已提交
1390 1391 1392
			my ($pr, $ow) = split ' ', $line;
			$pr = unescape($pr);
			$ow = unescape($ow);
K
v142  
Kay Sievers 已提交
1393
			if ($pr eq $project) {
1394
				$owner = decode("utf8", $ow, Encode::FB_DEFAULT);
K
v142  
Kay Sievers 已提交
1395 1396 1397 1398 1399 1400 1401 1402 1403
				last;
			}
		}
		close $fd;
	}
	if (!defined $owner) {
		$owner = get_file_owner("$projectroot/$project");
	}

K
Kay Sievers 已提交
1404
	my $refs = read_info_ref();
K
v142  
Kay Sievers 已提交
1405
	git_header_html();
1406
	git_page_nav('summary','', $head);
1407

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

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

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

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

K
v142  
Kay Sievers 已提交
1437 1438 1439
	git_footer_html();
}

K
v235  
Kay Sievers 已提交
1440
sub git_tag {
1441
	my $head = git_read_head($project);
K
v235  
Kay Sievers 已提交
1442
	git_header_html();
1443
	git_page_nav('','', $head,undef,$head);
K
v235  
Kay Sievers 已提交
1444
	my %tag = git_read_tag($hash);
1445
	git_header_div('commit', esc_html($tag{'name'}), $hash);
K
v235  
Kay Sievers 已提交
1446 1447
	print "<div class=\"title_text\">\n" .
	      "<table cellspacing=\"0\">\n" .
1448 1449
	      "<tr>\n" .
	      "<td>object</td>\n" .
1450 1451
	      "<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" .
1452
	      "</tr>\n";
K
v235  
Kay Sievers 已提交
1453 1454
	if (defined($tag{'author'})) {
		my %ad = date_str($tag{'epoch'}, $tag{'tz'});
1455
		print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
K
v235  
Kay Sievers 已提交
1456 1457 1458 1459 1460 1461 1462
		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) {
1463
		print esc_html($line) . "<br/>\n";
K
v235  
Kay Sievers 已提交
1464 1465 1466 1467 1468
	}
	print "</div>\n";
	git_footer_html();
}

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

1509 1510 1511 1512 1513 1514 1515
		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";
1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527
		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();
}

1528 1529
sub git_blame {
	my $fd;
1530 1531
	die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame'));
	die_error('404 Not Found', "File name not defined") if (!$file_name);
1532
	$hash_base ||= git_read_head($project);
1533
	die_error(undef, "Couldn't find base commit") unless ($hash_base);
1534
	my %co = git_read_commit($hash_base)
1535
		or die_error(undef, "Reading commit failed");
1536 1537
	if (!defined $hash) {
		$hash = git_get_hash_by_path($hash_base, $file_name, "blob")
1538
			or die_error(undef, "Error lookup file");
1539
	}
1540
	open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
1541
		or die_error(undef, "Open git-annotate failed");
1542
	git_header_html();
1543 1544 1545 1546
	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);
1547
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
1548
	git_print_page_path($file_name, 'blob');
1549 1550
	print "<div class=\"page_body\">\n";
	print <<HTML;
1551
<table class="blame">
1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571
  <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;
1572
		my $age_class;
1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583

		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 {
1584
			print qq(  <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
1585 1586 1587 1588 1589 1590
			next;
		}
		$short_rev  = substr ($long_rev, 0, 8);
		$age        = time () - $time;
		$age_str    = age_string ($age);
		$age_str    =~ s/ /&nbsp;/g;
1591
		$age_class  = age_class($age);
1592 1593
		$author     = esc_html ($author);
		$author     =~ s/ /&nbsp;/g;
1594 1595

		$data = untabify($data);
1596
		$data = esc_html ($data);
1597

1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611
		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();
1612 1613
}

1614 1615 1616 1617 1618
sub git_tags {
	my $head = git_read_head($project);
	git_header_html();
	git_page_nav('','', $head,undef,$head);
	git_header_div('summary', $project);
1619

1620 1621 1622
	my $taglist = git_read_refs("refs/tags");
	if (defined @$taglist) {
		git_tags_body($taglist);
1623
	}
1624
	git_footer_html();
1625 1626
}

1627 1628 1629 1630 1631
sub git_heads {
	my $head = git_read_head($project);
	git_header_html();
	git_page_nav('','', $head,undef,$head);
	git_header_div('summary', $project);
1632

1633 1634 1635 1636
	my $taglist = git_read_refs("refs/heads");
	my $alternate = 0;
	if (defined @$taglist) {
		git_heads_body($taglist, $head);
1637
	}
1638
	git_footer_html();
1639 1640
}

K
v203  
Kay Sievers 已提交
1641
sub git_blob_plain {
1642
	if (!defined $hash) {
J
Jakub Narebski 已提交
1643 1644 1645
		if (defined $file_name) {
			my $base = $hash_base || git_read_head($project);
			$hash = git_get_hash_by_path($base, $file_name, "blob")
1646
				or die_error(undef, "Error lookup file");
J
Jakub Narebski 已提交
1647
		} else {
1648
			die_error(undef, "No file name defined");
J
Jakub Narebski 已提交
1649 1650
		}
	}
1651
	my $type = shift;
1652
	open my $fd, "-|", $GIT, "cat-file", "blob", $hash
1653
		or die_error(undef, "Couldn't cat $file_name, $hash");
1654 1655

	$type ||= git_blob_plain_mimetype($fd, $file_name);
1656 1657 1658

	# save as filename, even when no $file_name is given
	my $save_as = "$hash";
1659 1660
	if (defined $file_name) {
		$save_as = $file_name;
1661 1662
	} elsif ($type =~ m/^text\//) {
		$save_as .= '.txt';
1663
	}
1664 1665

	print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\"");
K
v203  
Kay Sievers 已提交
1666
	undef $/;
1667
	binmode STDOUT, ':raw';
K
v203  
Kay Sievers 已提交
1668
	print <$fd>;
1669
	binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
K
v203  
Kay Sievers 已提交
1670 1671 1672 1673
	$/ = "\n";
	close $fd;
}

1674
sub git_blob {
1675
	if (!defined $hash) {
J
Jakub Narebski 已提交
1676 1677 1678
		if (defined $file_name) {
			my $base = $hash_base || git_read_head($project);
			$hash = git_get_hash_by_path($base, $file_name, "blob")
1679
				or die_error(undef, "Error lookup file");
J
Jakub Narebski 已提交
1680
		} else {
1681
			die_error(undef, "No file name defined");
J
Jakub Narebski 已提交
1682 1683
		}
	}
1684
	my $have_blame = git_get_project_config_bool ('blame');
1685
	open my $fd, "-|", $GIT, "cat-file", "blob", $hash
1686
		or die_error(undef, "Couldn't cat $file_name, $hash");
1687 1688 1689 1690 1691 1692
	my $mimetype = git_blob_plain_mimetype($fd, $file_name);
	if ($mimetype !~ m/^text\//) {
		close $fd;
		return git_blob_plain($mimetype);
	}
	git_header_html();
1693
	my $formats_nav = '';
1694 1695 1696
	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
		if (defined $file_name) {
			if ($have_blame) {
1697
				$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | ";
1698
			}
1699 1700 1701
			$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");
1702
		} else {
1703
			$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");
1704
		}
1705
		git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
1706
		git_header_div('commit', esc_html($co{'title'}), $hash_base);
1707 1708 1709 1710 1711
	} else {
		print "<div class=\"page_nav\">\n" .
		      "<br/><br/></div>\n" .
		      "<div class=\"title\">$hash</div>\n";
	}
1712
	git_print_page_path($file_name, "blob");
1713 1714 1715 1716 1717
	print "<div class=\"page_body\">\n";
	my $nr;
	while (my $line = <$fd>) {
		chomp $line;
		$nr++;
1718
		$line = untabify($line);
1719 1720 1721 1722 1723 1724 1725
		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 已提交
1726
sub git_tree {
K
v107  
Kay Sievers 已提交
1727
	if (!defined $hash) {
1728
		$hash = git_read_head($project);
K
v118  
Kay Sievers 已提交
1729
		if (defined $file_name) {
1730
			my $base = $hash_base || $hash;
K
v118  
Kay Sievers 已提交
1731 1732
			$hash = git_get_hash_by_path($base, $file_name, "tree");
		}
K
v157  
Kay Sievers 已提交
1733
		if (!defined $hash_base) {
1734
			$hash_base = $hash;
K
v157  
Kay Sievers 已提交
1735
		}
K
v145  
Kay Sievers 已提交
1736
	}
1737
	$/ = "\0";
1738
	open my $fd, "-|", $GIT, "ls-tree", '-z', $hash
1739
		or die_error(undef, "Open git-ls-tree failed");
1740
	my @entries = map { chomp; $_ } <$fd>;
1741
	close $fd or die_error(undef, "Reading tree failed");
1742
	$/ = "\n";
K
v077  
Kay Sievers 已提交
1743

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

sub git_log {
1809
	my $head = git_read_head($project);
K
v150  
Kay Sievers 已提交
1810
	if (!defined $hash) {
K
v203  
Kay Sievers 已提交
1811
		$hash = $head;
K
v150  
Kay Sievers 已提交
1812
	}
K
v206  
Kay Sievers 已提交
1813 1814
	if (!defined $page) {
		$page = 0;
K
v107  
Kay Sievers 已提交
1815
	}
K
Kay Sievers 已提交
1816
	my $refs = read_info_ref();
K
v206  
Kay Sievers 已提交
1817 1818

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

1824
	my $paging_nav = git_get_paging_nav('log', $hash, $head, $page, $#revlist);
1825 1826 1827 1828

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

K
v107  
Kay Sievers 已提交
1829
	if (!@revlist) {
K
v150  
Kay Sievers 已提交
1830
		my %co = git_read_commit($hash);
1831 1832

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

sub git_commit {
	my %co = git_read_commit($hash);
K
v088  
Kay Sievers 已提交
1880
	if (!%co) {
1881
		die_error(undef, "Unknown commit object");
K
v077  
Kay Sievers 已提交
1882
	}
K
v049  
Kay Sievers 已提交
1883 1884
	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
	my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
K
Kay Sievers 已提交
1885

K
v235  
Kay Sievers 已提交
1886 1887
	my $parent = $co{'parent'};
	if (!defined $parent) {
1888
		$parent = "--root";
K
v085  
Kay Sievers 已提交
1889
	}
1890
	open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash
1891
		or die_error(undef, "Open git-diff-tree failed");
1892
	my @difftree = map { chomp; $_ } <$fd>;
1893
	close $fd or die_error(undef, "Reading git-diff-tree failed");
1894 1895 1896 1897 1898 1899

	# non-textual hash id's can be cached
	my $expires;
	if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
		$expires = "+1d";
	}
K
Kay Sievers 已提交
1900
	my $refs = read_info_ref();
1901
	my $ref = git_get_referencing($refs, $co{'id'});
1902
	my $formats_nav = '';
1903 1904
	if (defined $file_name && defined $co{'parent'}) {
		my $parent = $co{'parent'};
1905
		$formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame");
1906
	}
1907
	git_header_html(undef, $expires);
1908 1909 1910
	git_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
							 $hash, $co{'tree'}, $hash,
							 $formats_nav);
1911

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

sub git_blobdiff {
K
v203  
Kay Sievers 已提交
2075
	mkdir($git_temp, 0700);
K
v021  
Kay Sievers 已提交
2076
	git_header_html();
K
v118  
Kay Sievers 已提交
2077
	if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
2078 2079 2080
		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);
2081
		git_header_div('commit', esc_html($co{'title'}), $hash_base);
K
v118  
Kay Sievers 已提交
2082 2083 2084 2085 2086
	} else {
		print "<div class=\"page_nav\">\n" .
		      "<br/><br/></div>\n" .
		      "<div class=\"title\">$hash vs $hash_parent</div>\n";
	}
2087
	git_print_page_path($file_name, "blob");
K
v070  
Kay Sievers 已提交
2088
	print "<div class=\"page_body\">\n" .
K
v133  
Kay Sievers 已提交
2089
	      "<div class=\"diff_info\">blob:" .
2090
	      $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 已提交
2091
	      " -> blob:" .
2092
	      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) .
K
v133  
Kay Sievers 已提交
2093
	      "</div>\n";
K
v203  
Kay Sievers 已提交
2094
	git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash);
K
v133  
Kay Sievers 已提交
2095
	print "</div>";
K
v021  
Kay Sievers 已提交
2096
	git_footer_html();
K
v118  
Kay Sievers 已提交
2097 2098
}

K
v203  
Kay Sievers 已提交
2099 2100 2101 2102 2103 2104
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 已提交
2105
sub git_commitdiff {
K
v203  
Kay Sievers 已提交
2106
	mkdir($git_temp, 0700);
K
v118  
Kay Sievers 已提交
2107
	my %co = git_read_commit($hash);
K
v088  
Kay Sievers 已提交
2108
	if (!%co) {
2109
		die_error(undef, "Unknown commit object");
K
v077  
Kay Sievers 已提交
2110
	}
K
v160  
Kay Sievers 已提交
2111 2112 2113
	if (!defined $hash_parent) {
		$hash_parent = $co{'parent'};
	}
2114
	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
2115
		or die_error(undef, "Open git-diff-tree failed");
2116
	my @difftree = map { chomp; $_ } <$fd>;
2117
	close $fd or die_error(undef, "Reading git-diff-tree failed");
K
Kay Sievers 已提交
2118

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

K
v203  
Kay Sievers 已提交
2192 2193
sub git_commitdiff_plain {
	mkdir($git_temp, 0700);
2194
	open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
2195
		or die_error(undef, "Open git-diff-tree failed");
2196
	my @difftree = map { chomp; $_ } <$fd>;
2197
	close $fd or die_error(undef, "Reading diff-tree failed");
K
v203  
Kay Sievers 已提交
2198

K
v232  
Kay Sievers 已提交
2199 2200
	# try to figure out the next tag after this commit
	my $tagname;
K
Kay Sievers 已提交
2201
	my $refs = read_info_ref("tags");
2202
	open $fd, "-|", $GIT, "rev-list", "HEAD";
2203
	my @commits = map { chomp; $_ } <$fd>;
K
Kay Sievers 已提交
2204 2205 2206 2207
	close $fd;
	foreach my $commit (@commits) {
		if (defined $refs->{$commit}) {
			$tagname = $refs->{$commit}
K
v232  
Kay Sievers 已提交
2208 2209 2210 2211 2212 2213
		}
		if ($commit eq $hash) {
			last;
		}
	}

2214
	print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\"");
K
v220  
Kay Sievers 已提交
2215 2216 2217
	my %co = git_read_commit($hash);
	my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
	my $comment = $co{'comment'};
K
v232  
Kay Sievers 已提交
2218
	print "From: $co{'author'}\n" .
K
v220  
Kay Sievers 已提交
2219
	      "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
K
v232  
Kay Sievers 已提交
2220 2221
	      "Subject: $co{'title'}\n";
	if (defined $tagname) {
J
Jakub Narebski 已提交
2222
		print "X-Git-Tag: $tagname\n";
K
v232  
Kay Sievers 已提交
2223 2224
	}
	print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
K
v220  
Kay Sievers 已提交
2225
	      "\n";
K
v232  
Kay Sievers 已提交
2226

K
v220  
Kay Sievers 已提交
2227
	foreach my $line (@$comment) {;
2228
		print "$line\n";
K
v220  
Kay Sievers 已提交
2229
	}
K
v232  
Kay Sievers 已提交
2230 2231
	print "---\n\n";

K
v203  
Kay Sievers 已提交
2232 2233 2234 2235 2236 2237
	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 已提交
2238
		if ($status eq "A") {
K
v203  
Kay Sievers 已提交
2239 2240 2241 2242 2243 2244 2245 2246 2247
			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 已提交
2248
sub git_history {
2249 2250
	if (!defined $hash_base) {
		$hash_base = git_read_head($project);
K
v118  
Kay Sievers 已提交
2251
	}
2252
	my $ftype;
2253
	my %co = git_read_commit($hash_base);
K
v118  
Kay Sievers 已提交
2254
	if (!%co) {
2255
		die_error(undef, "Unknown commit object");
K
v062  
Kay Sievers 已提交
2256
	}
K
Kay Sievers 已提交
2257
	my $refs = read_info_ref();
K
v057  
Kay Sievers 已提交
2258
	git_header_html();
2259
	git_page_nav('','', $hash_base,$co{'tree'},$hash_base);
2260
	git_header_div('commit', esc_html($co{'title'}), $hash_base);
2261 2262 2263
	if (!defined $hash && defined $file_name) {
		$hash = git_get_hash_by_path($hash_base, $file_name);
	}
2264
	if (defined $hash) {
2265
		$ftype = git_get_type($hash);
2266
	}
2267
	git_print_page_path($file_name, $ftype);
K
v157  
Kay Sievers 已提交
2268

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

sub git_search {
	if (!defined $searchtext) {
2313
		die_error(undef, "Text field empty");
K
v203  
Kay Sievers 已提交
2314 2315
	}
	if (!defined $hash) {
2316
		$hash = git_read_head($project);
K
v203  
Kay Sievers 已提交
2317 2318 2319
	}
	my %co = git_read_commit($hash);
	if (!%co) {
2320
		die_error(undef, "Unknown commit object");
K
v203  
Kay Sievers 已提交
2321
	}
K
v220  
Kay Sievers 已提交
2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335
	# 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 已提交
2336
	git_header_html();
2337
	git_page_nav('','', $hash,$co{'tree'},$hash);
2338
	git_header_div('commit', esc_html($co{'title'}), $hash);
K
v203  
Kay Sievers 已提交
2339 2340 2341

	print "<table cellspacing=\"0\">\n";
	my $alternate = 0;
K
v220  
Kay Sievers 已提交
2342 2343
	if ($commit_search) {
		$/ = "\0";
2344
		open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next;
K
v220  
Kay Sievers 已提交
2345 2346 2347
		while (my $commit_text = <$fd>) {
			if (!grep m/$searchtext/i, $commit_text) {
				next;
K
v203  
Kay Sievers 已提交
2348
			}
K
v220  
Kay Sievers 已提交
2349 2350
			if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
				next;
K
v203  
Kay Sievers 已提交
2351
			}
K
v220  
Kay Sievers 已提交
2352
			if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
K
v203  
Kay Sievers 已提交
2353 2354
				next;
			}
K
v220  
Kay Sievers 已提交
2355
			my @commit_lines = split "\n", $commit_text;
2356
			my %co = git_read_commit(undef, \@commit_lines);
K
v220  
Kay Sievers 已提交
2357 2358 2359 2360 2361 2362 2363 2364 2365
			if (!%co) {
				next;
			}
			if ($alternate) {
				print "<tr class=\"dark\">\n";
			} else {
				print "<tr class=\"light\">\n";
			}
			$alternate ^= 1;
K
v225  
Kay Sievers 已提交
2366
			print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
2367
			      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
K
v220  
Kay Sievers 已提交
2368
			      "<td>" .
2369
			      $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 已提交
2370 2371 2372
			my $comment = $co{'comment'};
			foreach my $line (@$comment) {
				if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
2373
					my $lead = esc_html($1) || "";
K
v220  
Kay Sievers 已提交
2374
					$lead = chop_str($lead, 30, 10);
2375 2376
					my $match = esc_html($2) || "";
					my $trail = esc_html($3) || "";
K
v220  
Kay Sievers 已提交
2377
					$trail = chop_str($trail, 30, 10);
2378
					my $text = "$lead<span class=\"match\">$match</span>$trail";
K
v220  
Kay Sievers 已提交
2379
					print chop_str($text, 80, 5) . "<br/>\n";
K
v203  
Kay Sievers 已提交
2380
				}
K
v220  
Kay Sievers 已提交
2381 2382 2383
			}
			print "</td>\n" .
			      "<td class=\"link\">" .
2384 2385
			      $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 已提交
2386 2387 2388 2389 2390 2391 2392 2393
			print "</td>\n" .
			      "</tr>\n";
		}
		close $fd;
	}

	if ($pickaxe_search) {
		$/ = "\n";
2394
		open my $fd, "-|", "$GIT rev-list $hash | $GIT diff-tree -r --stdin -S\'$searchtext\'";
K
v220  
Kay Sievers 已提交
2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405
		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 已提交
2406
				}
K
v220  
Kay Sievers 已提交
2407 2408 2409 2410
				if ($set{'id'} =~ m/0{40}/) {
					next;
				}
				push @files, \%set;
2411
			} elsif ($line =~ m/^([0-9a-fA-F]{40})$/){
K
v220  
Kay Sievers 已提交
2412 2413 2414 2415 2416 2417 2418
				if (%co) {
					if ($alternate) {
						print "<tr class=\"dark\">\n";
					} else {
						print "<tr class=\"light\">\n";
					}
					$alternate ^= 1;
K
v225  
Kay Sievers 已提交
2419
					print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
2420
					      "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
K
v220  
Kay Sievers 已提交
2421
					      "<td>" .
2422
					      $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$co{'id'}"), -class => "list"}, "<b>" .
2423
					      esc_html(chop_str($co{'title'}, 50)) . "</b><br/>");
K
v220  
Kay Sievers 已提交
2424 2425
					while (my $setref = shift @files) {
						my %set = %$setref;
2426
						print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$set{'id'};hb=$co{'id'};f=$set{'file'}"), class => "list"},
2427
						      "<span class=\"match\">" . esc_html($set{'file'}) . "</span>") .
K
v220  
Kay Sievers 已提交
2428 2429 2430 2431
						      "<br/>\n";
					}
					print "</td>\n" .
					      "<td class=\"link\">" .
2432 2433
					      $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 已提交
2434 2435 2436 2437
					print "</td>\n" .
					      "</tr>\n";
				}
				%co = git_read_commit($1);
K
v203  
Kay Sievers 已提交
2438 2439
			}
		}
K
v220  
Kay Sievers 已提交
2440
		close $fd;
K
v203  
Kay Sievers 已提交
2441 2442 2443 2444 2445 2446
	}
	print "</table>\n";
	git_footer_html();
}

sub git_shortlog {
2447
	my $head = git_read_head($project);
K
v203  
Kay Sievers 已提交
2448 2449 2450
	if (!defined $hash) {
		$hash = $head;
	}
K
v206  
Kay Sievers 已提交
2451 2452 2453
	if (!defined $page) {
		$page = 0;
	}
K
Kay Sievers 已提交
2454
	my $refs = read_info_ref();
K
v206  
Kay Sievers 已提交
2455 2456

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

2462
	my $paging_nav = git_get_paging_nav('shortlog', $hash, $head, $page, $#revlist);
2463 2464 2465 2466 2467 2468 2469
	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");
	}

2470 2471 2472

	git_header_html();
	git_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
2473
	git_header_div('summary', $project);
2474

2475 2476
	git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);

K
v203  
Kay Sievers 已提交
2477 2478
	git_footer_html();
}
2479 2480 2481 2482 2483 2484 2485

## ......................................................................
## 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)
2486
		or die_error(undef, "Open git-rev-list failed");
2487
	my @revlist = map { chomp; $_ } <$fd>;
2488
	close $fd or die_error(undef, "Reading git-rev-list failed");
2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573
	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";
}