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