3 # gitweb.pl - simple web interface to track changes in git repositories
5 # (C) 2005, Kay Sievers <kay.sievers@vrfy.org>
6 # (C) 2005, Christian Gierke <ch@gierke.de>
8 # This program is licensed under the GPL v2, or a later version
12 use CGI qw(:standard :escapeHTML);
13 use CGI::Carp qw(fatalsToBrowser);
18 my $my_url = $cgi->url();
19 my $my_uri = $cgi->url(-absolute => 1);
22 # absolute fs-path which will be prepended to the project path
23 my $projectroot = "/pub/scm";
25 # location of the git-core binaries
26 my $gitbin = "/usr/bin";
28 # location for temporary files needed for diffs
29 my $gittmp = "/tmp/gitweb";
31 # target of the home link on top of all pages
32 my $home_link = $my_uri;
35 # handler to return the list of projects
36 sub get_projects_list {
40 # my $dir = $projectroot;
41 # opendir my $dh, $dir || return undef;
42 # while (my $dir = readdir($dh)) {
43 # if (-e "$projectroot/$dir/HEAD") {
50 my $file = "index/index.txt";
51 open my $fd , $file || return undef;
52 while (my $line = <$fd>) {
54 if (-e "$projectroot/$line/HEAD") {
65 my $project = $cgi->param('p');
66 if (defined $project) {
67 if ($project =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
69 die_error("", "Non-canonical project parameter.");
71 if ($project =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~]/) {
73 die_error("", "Invalid character in project parameter.");
75 if (!(-d "$projectroot/$project")) {
77 die_error("", "No such directory.");
79 if (!(-e "$projectroot/$project/HEAD")) {
81 die_error("", "No such project.");
83 $rss_link = "<link rel=\"alternate\" title=\"$project log\" href=\"$my_uri?p=$project;a=rss\" type=\"application/rss+xml\"/>";
84 $ENV{'SHA1_FILE_DIRECTORY'} = "$projectroot/$project/objects";
87 my $file_name = $cgi->param('f');
88 if (defined $file_name) {
89 if ($file_name =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
91 die_error("", "Non-canonical file parameter.");
93 if ($file_name =~ m/[^a-zA-Z0-9_\.\/\-\+\#\~]/) {
95 die_error("", "Invalid character in file parameter.");
99 my $action = $cgi->param('a');
100 if (defined $action) {
101 if ($action =~ m/[^0-9a-zA-Z\.\-]+/) {
103 die_error("", "Invalid action parameter.");
109 my $hash = $cgi->param('h');
110 if (defined $hash && !($hash =~ m/^[0-9a-fA-F]{40}$/)) {
112 die_error("", "Invalid hash parameter.");
115 my $hash_parent = $cgi->param('hp');
116 if (defined $hash_parent && !($hash_parent =~ m/^[0-9a-fA-F]{40}$/)) {
118 die_error("", "Invalid parent hash parameter.");
121 my $time_back = $cgi->param('t');
122 if (defined $time_back) {
123 if ($time_back =~ m/^[^0-9]+$/) {
125 die_error("", "Invalid time parameter.");
129 sub git_header_html {
130 my $status = shift || "200 OK";
133 if (defined $project) {
134 $title .= " - $project";
135 if (defined $action) {
136 $title .= "/$action";
139 print $cgi->header(-type=>'text/html', -charset => 'utf-8', -status=> $status);
141 <?xml version="1.0" encoding="utf-8"?>
142 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
143 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
144 <!-- git web interface v$version, (C) 2005, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke <ch\@gierke.de> -->
146 <title>$title</title>
148 <style type="text/css">
149 body { font-family: sans-serif; font-size: 12px; margin:0px; }
151 a:hover { color:#880000; }
152 a:visited { color:#880000; }
153 a:active { color:#880000; }
155 margin:15px 15px 0px; height:25px; padding:8px;
156 font-size:18px; font-weight:bold; background-color:#d9d8d1;
158 div.page_header a:visited { color:#0000cc; }
159 div.page_header a:hover { color:#880000; }
160 div.page_nav { margin:0px 15px; padding:8px; border:solid #d9d8d1; border-width:0px 1px; }
161 div.page_nav a:visited { color:#0000cc; }
162 div.page_footer { margin:0px 15px 15px; height:17px; padding:4px; padding-left:8px; background-color: #d9d8d1; }
163 div.page_footer_text { float:left; color:#555555; font-style:italic; }
164 div.page_body { margin:0px 15px; padding:8px; border:solid #d9d8d1; border-width:0px 1px; }
166 display:block; margin:0px 15px; padding:6px 8px;
167 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
169 a.title:hover { background-color: #d9d8d1; }
170 div.title_text { margin:0px 15px; padding:6px 8px; border: solid #d9d8d1; border-width:0px 1px 1px; }
171 div.log_body { margin:0px 15px; padding:8px; padding-left:150px; border:solid #d9d8d1; border-width:0px 1px; }
172 span.log_age { position:relative; float:left; width:142px; font-style:italic; }
173 div.log_link { font-size:10px; font-family:sans-serif; font-style:normal; position:relative; float:left; width:142px; }
175 display:block; margin:0px 15px; padding:4px 6px 2px; border:solid #d9d8d1; border-width:1px 1px 0px;
179 display:block; margin:0px 15px; padding:4px 6px 4px; border:solid #d9d8d1; border-width:1px 1px 0px;
182 div.list a { text-decoration:none; color:#000000; }
183 div.list a:hover { color:#880000; }
185 margin:0px 15px; padding:0px 6px 8px; border:solid #d9d8d1; border-width:0px 1px;
186 font-family:sans-serif; font-size:10px;
188 td { padding:5px 15px 0px 0px; font-size:12px; }
189 th { padding-right:10px; font-size:12px; text-align:left; }
190 span.diff_info { color:#000099; background-color:#edece6; font-style:italic; }
191 a.rss_logo { float:right; border:1px solid; line-height:15px;
192 border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e; width:35px;
193 color:#ffffff; background-color:#ff6600;
194 font-weight:bold; font-family:sans-serif; text-align:center; vertical-align:middle;
195 font-size:10px; display:block; text-decoration:none;
197 a.rss_logo:hover { background-color:#ee5500; }
202 print "<div class=\"page_header\">\n" .
203 "<a href=\"http://kernel.org/pub/software/scm/cogito\">" .
204 "<img src=\"$my_uri?a=git-logo.png\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/></a>";
205 print $cgi->a({-href => $home_link}, "projects") . " / ";
206 if (defined $project) {
207 print $cgi->a({-href => "$my_uri?p=$project;a=log"}, escapeHTML($project));
208 if (defined $action) {
215 sub git_footer_html {
216 print "<div class=\"page_footer\">\n";
217 if (defined $project) {
218 my $descr = git_description($project);
219 if (defined $descr) {
220 print "<div class=\"page_footer_text\">" . escapeHTML($descr) . "</div>\n";
222 print $cgi->a({-href => "$my_uri?p=$project;a=rss", -class => "rss_logo"}, "RSS") . "\n";
230 my $status = shift || "403 Forbidden";
231 my $error = shift || "Malformed query, file missing or permission denied";
233 git_header_html($status);
234 print "<div class=\"page_body\">\n" .
236 print "$status - $error\n";
237 print "<br/></div>\n";
244 open my $fd, "$projectroot/$path/HEAD" || return undef;
248 if ($head =~ m/^[0-9a-fA-F]{40}$/) {
255 sub git_description {
257 open my $fd, "$projectroot/$path/description" || return undef;
269 open my $fd, "-|", "$gitbin/git-cat-file commit $commit" || return;
270 while (my $line = <$fd>) {
271 last if $line eq "\n";
273 if ($line =~ m/^tree (.*)$/) {
275 } elsif ($line =~ m/^parent (.*)$/) {
277 } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
279 $co{'author_epoch'} = $2;
280 $co{'author_tz'} = $3;
281 $co{'author_name'} = $co{'author'};
282 $co{'author_name'} =~ s/ <.*//;
283 } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) {
284 $co{'committer'} = $1;
285 $co{'committer_epoch'} = $2;
286 $co{'committer_tz'} = $3;
287 $co{'committer_name'} = $co{'committer'};
288 $co{'committer_name'} =~ s/ <.*//;
291 $co{'parents'} = \@parents;
292 $co{'parent'} = $parents[0];
293 my (@comment) = map { chomp; $_ } <$fd>;
294 $co{'comment'} = \@comment;
295 $co{'title'} = $comment[0];
297 if (!defined $co{'tree'}) {
301 my $age = time - $co{'committer_epoch'};
303 if ($age > 60*60*24*365*2) {
304 $co{'age_string'} = (int $age/60/60/24/365);
305 $co{'age_string'} .= " years ago";
306 } elsif ($age > 60*60*24*365/12*2) {
307 $co{'age_string'} = int $age/60/60/24/365/12;
308 $co{'age_string'} .= " months ago";
309 } elsif ($age > 60*60*24*7*2) {
310 $co{'age_string'} = int $age/60/60/24/7;
311 $co{'age_string'} .= " weeks ago";
312 } elsif ($age > 60*60*24*2) {
313 $co{'age_string'} = int $age/60/60/24;
314 $co{'age_string'} .= " days ago";
315 } elsif ($age > 60*60*2) {
316 $co{'age_string'} = int $age/60/60;
317 $co{'age_string'} .= " hours ago";
318 } elsif ($age > 60*2) {
319 $co{'age_string'} = int $age/60;
320 $co{'age_string'} .= " minutes ago";
322 $co{'age_string'} = int $age;
323 $co{'age_string'} .= " seconds ago";
325 $co{'age_string'} .= " right now";
332 my $from_name = shift;
336 my $from_tmp = "/dev/null";
337 my $to_tmp = "/dev/null";
340 # create tmp from-file
342 $from_tmp = "$gittmp/gitweb_" . $$ . "_from";
343 open my $fd2, "> $from_tmp";
344 open my $fd, "-|", "$gitbin/git-cat-file blob $from";
353 $to_tmp = "$gittmp/gitweb_" . $$ . "_to";
354 open my $fd2, "> $to_tmp";
355 open my $fd, "-|", "$gitbin/git-cat-file blob $to";
362 open my $fd, "-|", "/usr/bin/diff -u -p -L $from_name -L $to_name $from_tmp $to_tmp";
363 while (my $line = <$fd>) {
364 my $char = substr($line,0,1);
366 next if $char eq '\\';
368 print '<span style="color: #008800;">' if $char eq '+';
369 print '<span style="color: #CC0000;">' if $char eq '-';
370 print '<span style="color: #990099;">' if $char eq '@';
371 print escapeHTML($line);
372 print '</span>' if $char eq '+' or $char eq '-' or $char eq '@';
385 my $mode = oct shift;
387 if (S_ISDIR($mode & S_IFMT)) {
389 } elsif (S_ISLNK($mode)) {
391 } elsif (S_ISREG($mode)) {
392 # git cares only about the executable bit
393 if ($mode & S_IXUSR) {
404 my $mode = oct shift;
406 if (S_ISDIR($mode & S_IFMT)) {
408 } elsif (S_ISLNK($mode)) {
410 } elsif (S_ISREG($mode)) {
419 my $tz = shift || "-0000";
422 my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
423 my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
424 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
425 $date{'hour'} = $hour;
426 $date{'minute'} = $min;
427 $date{'mday'} = $mday;
428 $date{'day'} = $days[$wday];
429 $date{'month'} = $months[$mon];
430 $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
431 $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min;
433 $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
434 my $local = $epoch + ((int $1 + ($2/60)) * 3600);
435 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local);
436 $date{'hour_local'} = $hour;
437 $date{'minute_local'} = $min;
438 $date{'tz_local'} = $tz;
442 # git-logo (cached in browser for one day)
443 if (defined $action && $action eq "git-logo.png") {
444 print $cgi->header(-type => 'image/png', -expires => '+1d');
445 # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g'
446 print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
447 "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
448 "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
449 "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
450 "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
451 "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
452 "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
453 "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
454 "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
455 "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
456 "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
457 "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
458 "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
463 if (!defined $project) {
464 my $projects = get_projects_list();
466 print "<div class=\"page_body\">\n";
467 print "<table cellspacing=\"0\">\n";
469 "<th>Project</th>\n" .
470 "<th>Description</th>\n" .
472 "<th>last change</th>\n" .
475 foreach my $proj (@$projects) {
476 my $head = git_head($proj);
477 if (!defined $head) {
480 $ENV{'SHA1_FILE_DIRECTORY'} = "$projectroot/$proj/objects";
481 my %co = git_commit($head);
485 my $descr = git_description($proj) || "";
487 my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat("$projectroot/$proj");
488 my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid);
491 $owner =~ s/[,;].*$//;
494 "<td>" . $cgi->a({-href => "$my_uri?p=$proj;a=log"}, escapeHTML($proj)) . "</td>\n" .
495 "<td>$descr</td>\n" .
496 "<td><i>$owner</i></td>\n";
497 if ($co{'age'} < 60*60*2) {
498 print "<td><span style =\"color: #009900;\"><b><i>" . $co{'age_string'} . "</i></b></span></td>\n";
499 } elsif ($co{'age'} < 60*60*24*2) {
500 print "<td><span style =\"color: #009900;\"><i>" . $co{'age_string'} . "</i></span></td>\n";
502 print "<td><i>" . $co{'age_string'} . "</i></td>\n";
515 if ($action eq "blob") {
516 open my $fd, "-|", "$gitbin/git-cat-file blob $hash" || die_error("", "Open failed.");
518 print "<div class=\"page_nav\">\n";
519 print "<br/><br/></div>\n";
520 print "<div class=\"title\">$hash</div>\n";
521 print "<div class=\"page_body\"><pre>\n";
523 while (my $line = <$fd>) {
525 printf "<span style =\"color: #999999;\">%4i\t</span>%s", $nr, escapeHTML($line);;
527 close $fd || print "Reading blob failed.\n";
528 print "</pre><br/>\n";
531 } elsif ($action eq "tree") {
532 if (!defined $hash) {
533 $hash = git_head($project);
535 open my $fd, "-|", "$gitbin/git-ls-tree $hash" || die_error("", "Open failed.");
536 my (@entries) = map { chomp; $_ } <$fd>;
537 close $fd || die_error("", "Reading tree failed.");
540 my %co = git_commit($hash);
542 print "<div class=\"page_nav\"> view\n" .
543 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash"}, "commit") . " | " .
544 $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash"}, "diffs") . " | " .
545 $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$hash"}, "tree") .
549 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" .
552 print "<div class=\"page_nav\">\n";
553 print "<br/><br/></div>\n";
554 print "<div class=\"title\">$hash</div>\n";
556 print "<div class=\"page_body\">\n";
557 print "<br/><pre>\n";
558 foreach my $line (@entries) {
559 #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
560 $line =~ m/^([0-9]+)\t(.*)\t(.*)\t(.*)$/;
565 if ($t_type eq "blob") {
566 print mode_str($t_mode). " " . $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$t_hash"}, $t_name);
567 if (S_ISLNK(oct $t_mode)) {
568 open my $fd, "-|", "$gitbin/git-cat-file blob $t_hash";
571 print "\t -> $target";
574 } elsif ($t_type eq "tree") {
575 print mode_str($t_mode). " " . $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$t_hash"}, $t_name) . "\n";
581 } elsif ($action eq "rss") {
582 open my $fd, "-|", "$gitbin/git-rev-list --max-count=20 " . git_head($project) || die_error("", "Open failed.");
583 my (@revlist) = map { chomp; $_ } <$fd>;
584 close $fd || die_error("", "Reading rev-list failed.");
586 print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
587 print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
588 "<rss version=\"0.91\">\n";
590 print "<title>$project</title>\n".
591 "<link> " . $my_url . "/$project/log</link>\n".
592 "<description>$project log</description>\n".
593 "<language>en</language>\n";
595 foreach my $commit (@revlist) {
596 my %co = git_commit($commit);
597 my %ad = date_str($co{'author_epoch'});
599 "\t<title>" . sprintf("%d %s %02d:%02d", $ad{'mday'}, $ad{'month'}, $ad{'hour'}, $ad{'min'}) . " - " . escapeHTML($co{'title'}) . "</title>\n" .
600 "\t<link> " . $my_url . "?p=$project;a=commit;h=$commit</link>\n" .
602 my $comment = $co{'comment'};
603 foreach my $line (@$comment) {
604 print escapeHTML($line) . "<br/>\n";
606 print "\t</description>\n" .
611 print "</channel></rss>";
612 } elsif ($action eq "log") {
613 my $head = git_head($project);
614 my $limit_option = "";
615 if (!defined $time_back) {
616 $limit_option = "--max-count=10";
617 } elsif ($time_back > 0) {
618 my $date = time - $time_back*24*60*60;
619 $limit_option = "--max-age=$date";
621 open my $fd, "-|", "$gitbin/git-rev-list $limit_option $head" || die_error("", "Open failed.");
622 my (@revlist) = map { chomp; $_ } <$fd>;
623 close $fd || die_error("", "Reading rev-list failed.");
626 print "<div class=\"page_nav\">\n";
628 print $cgi->a({-href => "$my_uri?p=$project;a=log"}, "last 10") . " | " .
629 $cgi->a({-href => "$my_uri?p=$project;a=log;t=1"}, "day") . " | " .
630 $cgi->a({-href => "$my_uri?p=$project;a=log;t=7"}, "week") . " | " .
631 $cgi->a({-href => "$my_uri?p=$project;a=log;t=31"}, "month") . " | " .
632 $cgi->a({-href => "$my_uri?p=$project;a=log;t=365"}, "year") . " | " .
633 $cgi->a({-href => "$my_uri?p=$project;a=log;t=0"}, "all") . "<br/>\n";
634 print "<br/><br/>\n" .
638 my %co = git_commit($head);
639 print "<div class=\"page_body\"> Last change " . $co{'age_string'} . ".<br/><br/></div>\n";
642 foreach my $commit (@revlist) {
643 my %co = git_commit($commit);
645 my %ad = date_str($co{'author_epoch'});
647 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit", -class => "title"},
648 "<span class=\"log_age\">" . $co{'age_string'} . "</span>" . escapeHTML($co{'title'})) . "\n" .
650 print "<div class=\"title_text\">\n" .
651 "<div class=\"log_link\">\n" .
652 "view " . $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") . " | " .
653 $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$commit"}, "diff") . "<br/>\n" .
655 "<i>" . escapeHTML($co{'author_name'}) . " [" . $ad{'rfc2822'} . "]</i><br/>\n" .
657 "<div class=\"log_body\">\n";
658 my $comment = $co{'comment'};
659 foreach my $line (@$comment) {
660 last if ($line =~ m/^(signed-off|acked)-by:/i);
661 print escapeHTML($line) . "<br/>\n";
669 } elsif ($action eq "commit") {
670 my %co = git_commit($hash);
672 die_error("", "Unknown commit object.");
674 my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
675 my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
678 if (defined $co{'parent'}) {
679 open my $fd, "-|", "$gitbin/git-diff-tree -r " . $co{'parent'} . " $hash" || die_error("", "Open failed.");
680 @difftree = map { chomp; $_ } <$fd>;
681 close $fd || die_error("", "Reading diff-tree failed.");
683 # fake git-diff-tree output for initial revision
684 open my $fd, "-|", "$gitbin/git-ls-tree -r $hash" || die_error("", "Open failed.");
685 @difftree = map { chomp; "+" . $_ } <$fd>;
686 close $fd || die_error("", "Reading ls-tree failed.");
689 print "<div class=\"page_nav\"> view\n" .
690 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash"}, "commit") . " | \n" .
691 $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash"}, "diffs") . " | \n" .
692 $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$hash"}, "tree") . "\n" .
693 "<br/><br/></div>\n";
694 if (defined $co{'parent'}) {
696 $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" .
700 $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" .
703 print "<div class=\"title_text\">\n" .
704 "<table cellspacing=\"0\">\n";
705 print "<tr><td>author</td><td>" . escapeHTML($co{'author'}) . "</td></tr>\n".
706 "<tr><td></td><td> " . $ad{'rfc2822'};
707 if ($ad{'hour_local'} < 6) {
708 printf(" (<span style=\"color: #cc0000;\">%02d:%02d</span> %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
710 printf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'});
712 print "</td></tr>\n";
713 print "<tr><td>committer</td><td>" . escapeHTML($co{'committer'}) . "</td></tr>\n";
714 print "<tr><td></td><td> " . $cd{'rfc2822'} .
715 sprintf(" (%02d:%02d %s)", $cd{'hour_local'}, $cd{'minute_local'}, $cd{'tz_local'}) . "</td></tr>\n";
716 print "<tr><td>commit</td><td style=\"font-family: monospace;\">$hash</td></tr>\n";
717 print "<tr><td>tree</td><td style=\"font-family: monospace;\">" .
718 $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$hash"}, $co{'tree'}) . "</td></tr>\n";
719 my $parents = $co{'parents'};
720 foreach my $par (@$parents) {
721 print "<tr><td>parent</td><td style=\"font-family: monospace;\">" .
722 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$par"}, $par) . "</td></tr>\n";
726 print "<div class=\"page_body\">\n";
727 my $comment = $co{'comment'};
728 foreach my $line (@$comment) {
729 if ($line =~ m/(signed-off|acked)-by:/i) {
730 print "<span style=\"color: #888888\">" . escapeHTML($line) . "</span><br/>\n";
732 print escapeHTML($line) . "<br/>\n";
736 if ($#difftree > 10) {
737 print "<div class=\"list_head\">" . ($#difftree + 1) . " files changed:<br/></div>\n";
739 foreach my $line (@difftree) {
740 # '*100644->100644 blob 9f91a116d91926df3ba936a80f020a6ab1084d2b->bb90a0c3a91eb52020d0db0e8b4f94d30e02d596 net/ipv4/route.c'
741 # '+100644 blob 4a83ab6cd565d21ab0385bac6643826b83c2fcd4 arch/arm/lib/bitops.h'
742 # '*100664->100644 blob b1a8e3dd5556b61dd771d32307c6ee5d7150fa43->b1a8e3dd5556b61dd771d32307c6ee5d7150fa43 show-files.c'
743 # '*100664->100644 blob d08e895238bac36d8220586fdc28c27e1a7a76d3->d08e895238bac36d8220586fdc28c27e1a7a76d3 update-cache.c'
744 $line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/;
750 if ($type eq "blob") {
753 if (S_ISREG(oct $mode)) {
754 $mode_chng = sprintf(" with mode: %04o", (oct $mode) & 0777);
756 print "<div class=\"list\">\n" .
757 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id"},
758 escapeHTML($file) . " <span style=\"color: #008000;\">[new " . file_type($mode) . $mode_chng . "]</span>") . "\n" .
760 print "<div class=\"link\">\n" .
762 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id"}, "file") . "<br/>\n" .
764 } elsif ($op eq "-") {
765 print "<div class=\"list\">\n" .
766 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id"},
767 escapeHTML($file) . " <span style=\"color: #c00000;\">[deleted " . file_type($mode) . "]</span>") . "\n" .
769 print "<div class=\"link\">\n" .
771 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id"}, "file") . " | " .
772 $cgi->a({-href => "$my_uri?p=$project;a=history;h=$hash;f=$file"}, "history") . "<br/>\n" .
774 } elsif ($op eq "*") {
775 $id =~ m/([0-9a-fA-F]+)->([0-9a-fA-F]+)/;
778 $mode =~ m/^([0-7]{6})->([0-7]{6})$/;
782 if ($from_mode != $to_mode) {
783 $mode_chnge = " <span style=\"color: #888888;\">[changed";
784 if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) {
785 $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode);
787 if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) {
788 if (S_ISREG($from_mode) && S_ISREG($to_mode)) {
789 $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777);
790 } elsif (S_ISREG($to_mode)) {
791 $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777);
794 $mode_chnge .= "]</span>\n";
796 print "<div class=\"list\">\n";
797 if ($to_id ne $from_id) {
798 print $cgi->a({-href => "$my_uri?p=$project;a=blobdiff;h=$to_id;hp=$from_id"},
799 escapeHTML($file) . $mode_chnge) . "\n" .
802 print $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id"},
803 escapeHTML($file) . $mode_chnge) . "\n" .
806 print "<div class=\"link\">\n" .
808 if ($to_id ne $from_id) {
809 print $cgi->a({-href => "$my_uri?p=$project;a=blobdiff;h=$to_id;hp=$from_id"}, "diff") . " | ";
811 print $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id"}, "file") . " | " .
812 $cgi->a({-href => "$my_uri?p=$project;a=history;h=$hash;f=$file"}, "history") . "<br/>\n" .
818 } elsif ($action eq "blobdiff") {
819 mkdir($gittmp, 0700);
821 print "<div class=\"page_nav\">\n";
822 print "<br/><br/></div>\n";
823 print "<div class=\"title\">$hash vs $hash_parent</div>\n";
824 print "<div class=\"page_body\">\n" .
826 print "<span class=\"diff_info\">blob:" .
827 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$hash_parent"}, $hash_parent) .
829 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$hash"}, $hash) .
831 git_diff_html($hash_parent, $hash_parent, $hash, $hash);
835 } elsif ($action eq "commitdiff") {
836 mkdir($gittmp, 0700);
837 my %co = git_commit($hash);
839 die_error("", "Unknown commit object.");
841 open my $fd, "-|", "$gitbin/git-diff-tree -r " . $co{'parent'} . " $hash" || die_error("", "Open failed.");
842 my (@difftree) = map { chomp; $_ } <$fd>;
843 close $fd || die_error("", "Reading diff-tree failed.");
846 print "<div class=\"page_nav\"> view\n" .
847 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash"}, "commit") . " | \n" .
848 $cgi->a({-href => "$my_uri?p=$project;a=commitdiff;h=$hash"}, "diffs") . " | \n" .
849 $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$hash"}, "tree") . "\n" .
850 "<br/><br/></div>\n";
852 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash", -class => "title"}, escapeHTML($co{'title'})) . "\n" .
854 print "<div class=\"page_body\">\n" .
856 foreach my $line (@difftree) {
857 # '*100644->100644 blob 8e5f9bbdf4de94a1bc4b4da8cb06677ce0a57716->8da3a306d0c0c070d87048d14a033df02f40a154 Makefile'
858 $line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/;
864 if ($type eq "blob") {
866 print "<span class=\"diff_info\">" . file_type($mode) . ":" .
867 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id"}, $id) . "(new)" .
869 git_diff_html(undef, "/dev/null", $id, "b/$file");
870 } elsif ($op eq "-") {
871 print "<span class=\"diff_info\">" . file_type($mode) . ":" .
872 $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$id"}, $id) . "(deleted)" .
874 git_diff_html($id, "a/$file", undef, "/dev/null");
875 } elsif ($op eq "*") {
876 $id =~ m/([0-9a-fA-F]+)->([0-9a-fA-F]+)/;
879 $mode =~ m/([0-7]+)->([0-7]+)/;
882 if ($from_id ne $to_id) {
883 print "<span class=\"diff_info\">" .
884 file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$from_id"}, $from_id) .
886 file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?p=$project;a=blob;h=$to_id"}, $to_id);
888 git_diff_html($from_id, "a/$file", $to_id, "b/$file");
893 print "</pre><br/>\n";
896 } elsif ($action eq "history") {
897 if (!defined $hash) {
898 $hash = git_head($project);
901 print "<div class=\"page_nav\">\n";
902 print "<br/><br/></div>\n";
904 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$hash", -class => "title"}, escapeHTML($file_name)) . "\n" .
906 open my $fd, "-|", "$gitbin/git-rev-list $hash | $gitbin/git-diff-tree -r --stdin $file_name";
908 while (my $line = <$fd>) {
909 if ($line =~ m/^([0-9a-fA-F]{40}) /){
913 if ($line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/ && (defined $commit)) {
916 if ($file ne $file_name || $type ne "blob") {
919 my %co = git_commit($commit);
923 print "<div class=\"list\">\n" .
924 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"},
925 "<span class=\"log_age\">" . $co{'age_string'} . "</span>" . escapeHTML($co{'title'})) . "\n" .
927 print "<div class=\"link\">\n" .
929 $cgi->a({-href => "$my_uri?p=$project;a=commit;h=$commit"}, "commit") . " | " .
930 $cgi->a({-href => "$my_uri?p=$project;a=tree;h=$commit"}, "tree") . "<br/><br/>\n" .
940 die_error("", "Unknown action.");