b13f75c21a14788e216a135f6777488cad6a9d07
   1#!/usr/bin/perl
   2
   3# This file is licensed under the GPL v2, or a later version
   4# (C) 2005, Kay Sievers <kay.sievers@vrfy.org>
   5# (C) 2005, Christian Gierke <ch@gierke.de>
   6
   7use strict;
   8use warnings;
   9use CGI qw(:standard :escapeHTML);
  10use CGI::Carp qw(fatalsToBrowser);
  11
  12my $cgi = new CGI;
  13my $gitbin = "/home/kay/bin/git";
  14my $gitroot = "/home/kay/public_html";
  15my $gittmp = "/tmp";
  16my $myself = $cgi->url(-relative => 1);
  17
  18my $project = $cgi->param("project") || "";
  19my $action = $cgi->param("action") || "";
  20my $hash = $cgi->param("hash") || "";
  21my $parent = $cgi->param("parent") || "";
  22my $view_back = $cgi->param("view_back") || 60*60*24;
  23my $projectroot = "$gitroot/$project";
  24$ENV{'SHA1_FILE_DIRECTORY'} = "$projectroot/.git/objects";
  25
  26$hash =~ s/[^0-9a-fA-F]//g;
  27$parent =~ s/[^0-9a-fA-F]//g;
  28$project =~ s/[^0-9a-zA-Z\-\._]//g;
  29
  30sub git_header {
  31        print $cgi->header(-type => 'text/html; charset: utf-8');
  32print <<EOF;
  33<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  34<html>
  35<head>
  36        <title>git - $project $action</title>
  37        <style type="text/css">
  38                body { font-family: sans-serif; font-size: 12px; margin:25px; }
  39                div.body { border-width:1px; border-style:solid; border-color:#D9D8D1; }
  40                div.head1 { font-size:20px; padding:8px; background-color: #D9D8D1; font-weight:bold; }
  41                div.head1 a:visited { color:#0000cc; }
  42                div.head1 a:hover { color:#880000; }
  43                div.head1 a:active { color:#880000; }
  44                div.head2 { padding:8px; }
  45                div.head2 a:visited { color:#0000cc; }
  46                div.head2 a:hover { color:#880000; }
  47                div.head2 a:active { color:#880000; }
  48                div.main { padding:8px; font-family: sans-serif; font-size: 12px; }
  49                table { padding:0px; margin:0px; width:100%; }
  50                tr { vertical-align:top; }
  51                td { padding:8px; margin:0px; font-family: sans-serif; font-size: 12px; }
  52                td.head1 { background-color: #D9D8D1; font-weight:bold; }
  53                td.head1 a { color:#000000; text-decoration:none; }
  54                td.head1 a:hover { color:#880000; text-decoration:underline; }
  55                td.head1 a:visited { color:#000000; }
  56                td.head2 { background-color: #EDECE6; font-family: monospace; font-size:12px; }
  57                td.head3 { background-color: #EDECE6; font-size:10px; }
  58                div.add { color: #008800; }
  59                div.subtract { color: #CC0000; }
  60                div.diff_head { color: #990099; }
  61                a { color:#0000cc; }
  62                a:hover { color:#880000; }
  63                a:visited { color:#880000; }
  64                a:active { color:#880000; }
  65        </style>
  66</head>
  67<body>
  68EOF
  69        print "<div class=\"body\">\n";
  70        print "<div class=\"head1\">";
  71        print "<a href=\"http://kernel.org/pub/software/scm/git/\"><img src=\"git_logo.png\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/></a>";
  72        print $cgi->a({-href => "$myself"}, "projects");
  73        if ($project ne "") {
  74                print " / " . $cgi->a({-href => "$myself?project=$project&action=log&view_back=" . 60*60*24}, $project);
  75        }
  76        if ($action ne "") {
  77                print " / $action";
  78        }
  79        print "</div>\n";
  80}
  81
  82sub git_footer {
  83        print "</div>";
  84        print $cgi->end_html();
  85}
  86
  87sub git_diff {
  88        my $old_name = shift;
  89        my $new_name = shift;
  90        my $old = shift;
  91        my $new = shift;
  92
  93        my $label_old = "/dev/null";
  94        my $label_new = "/dev/null";
  95        my $tmp_old = "/dev/null";
  96        my $tmp_new = "/dev/null";
  97
  98        # create temp from-file
  99        if ($old ne "") {
 100                open my $fd2, "> $gittmp/$old";
 101                open my $fd, "-|", "$gitbin/cat-file", "blob", $old;
 102                while (my $line = <$fd>) {
 103                        print $fd2 $line;
 104                }
 105                close $fd2;
 106                close $fd;
 107                $tmp_old = "$gittmp/$old";
 108                $label_old = "a/$old_name";
 109        }
 110
 111        # create tmp to-file
 112        if ($new ne "") {
 113                open my $fd2, "> $gittmp/$new";
 114                open my $fd, "-|", "$gitbin/cat-file", "blob", $new;
 115                while (my $line = <$fd>) {
 116                        print $fd2 $line;
 117                }
 118                close $fd2;
 119                close $fd;
 120                $tmp_new = "$gittmp/$new";
 121                $label_new = "b/$new_name";
 122        }
 123
 124        open my $fd, "-|", "/usr/bin/diff", "-L", $label_old, "-L", $label_new, "-u", "-p", $tmp_old, $tmp_new;
 125        while (my $line = <$fd>) {
 126                my $char = substr($line,0,1);
 127                print '<div class="add">' if $char eq '+';
 128                print '<div class="subtract">' if $char eq '-';
 129                print '<div class="diff_head">' if $char eq '@';
 130                print escapeHTML($line);
 131                print '</div>' if $char eq '+' or $char eq '-' or $char eq '@';
 132        }
 133        close $fd;
 134        #unlink("$gittmp/$new");
 135        #unlink("$gittmp/$old");
 136}
 137
 138if ($project eq "") {
 139        open my $fd, "-|", "ls", "-1", $gitroot;
 140        my (@path) = map { chomp; $_ } <$fd>;
 141        close $fd;
 142        git_header();
 143        print "<br/><br/><div class=\"main\">\n";
 144        foreach my $line (@path) {
 145                if (-e "$gitroot/$line/.git/HEAD") {
 146                        print $cgi->a({-href => "$myself?project=$line"}, $line) . "<br/>\n";
 147                }
 148        }
 149        print "<br/></div>";
 150        git_footer();
 151        exit;
 152}
 153
 154if ($action eq "") {
 155        print $cgi->redirect("$myself?project=$project&action=log&view_back=$view_back");
 156        exit;
 157}
 158
 159if ($action eq "blob") {
 160        git_header();
 161        print "<br/><br/><div class=\"main\">\n";
 162        print "<pre>\n";
 163        open my $fd, "-|", "$gitbin/cat-file", "blob", $hash;
 164        my $nr;
 165        while (my $line = <$fd>) {
 166                $nr++;
 167                print "$nr\t" . escapeHTML($line);;
 168        }
 169        close $fd;
 170        print "</pre>\n";
 171        print "<br/></div>";
 172        git_footer();
 173} elsif ($action eq "tree") {
 174        if ($hash eq "") {
 175                open my $fd, "$projectroot/.git/HEAD";
 176                my $head = <$fd>;
 177                chomp $head;
 178                close $fd;
 179                $hash = $head;
 180        }
 181        open my $fd, "-|", "$gitbin/ls-tree", $hash;
 182        my (@entries) = map { chomp; $_ } <$fd>;
 183        close $fd;
 184        git_header();
 185        print "<br/><br/><div class=\"main\">\n";
 186        print "<pre>\n";
 187        foreach my $line (@entries) {
 188                $line =~ m/^([0-9]+)\t(.*)\t(.*)\t(.*)$/;
 189                my $t_type = $2;
 190                my $t_hash = $3;
 191                my $t_name = $4;
 192                if ($t_type eq "blob") {
 193                        print "BLOB\t" . $cgi->a({-href => "$myself?project=$project&action=blob&hash=$3"}, $4) . "\n";
 194                } elsif ($t_type eq "tree") {
 195                        print "TREE\t" . $cgi->a({-href => "$myself?project=$project&action=tree&hash=$3"}, $4) . "\n";
 196                }
 197        }
 198        print "</pre>\n";
 199        print "<br/></div>";
 200        git_footer();
 201} elsif ($action eq "log" || $action eq "show_log" ) {
 202        open my $fd, "$projectroot/.git/HEAD";
 203        my $head = <$fd>;
 204        chomp $head;
 205        close $fd;
 206        open $fd, "-|", "$gitbin/rev-tree", $head;
 207        my (@revtree) = map { chomp; $_ } <$fd>;
 208        close $fd;
 209        git_header();
 210        print "<div class=\"head2\">\n";
 211        print "view  ";
 212        print $cgi->a({-href => "$myself?project=$project&action=log&view_back=" . 60*60*24}, "last day") . " | ";
 213        print $cgi->a({-href => "$myself?project=$project&action=log&view_back=" . 60*60*24*7}, "week") . " | ";
 214        print $cgi->a({-href => "$myself?project=$project&action=log&view_back=" . 60*60*24*30}, "month") . " | ";
 215        print $cgi->a({-href => "$myself?project=$project&action=log&view_back=" . 60*60*24*365}, "year") . " | ";
 216        print $cgi->a({-href => "$myself?project=$project&action=log&view_back=-1"}, "all") . "<br/>\n";
 217        print "<br/><br/>\n";
 218        print "</div>\n";
 219        print "<table cellspacing=\"0\" class=\"log\">\n";
 220        foreach my $rev (reverse sort @revtree) {
 221                last if !($rev =~ m/^([0-9]+) ([0-9a-fA-F]+).* ([0-9a-fA-F]+)/);
 222                my $time = $1;
 223                my $commit = $2;
 224                my $parent = $3;
 225                my @parents;
 226                my $author;
 227                my $author_name;
 228                my $author_time;
 229                my $author_timezone;
 230                my $committer;
 231                my $committer_time;
 232                my $committer_timezone;
 233                my $tree;
 234                my $comment;
 235                my $shortlog;
 236                open my $fd, "-|", "$gitbin/cat-file", "commit", $commit;
 237                while (my $line = <$fd>) {
 238                        chomp($line);
 239                        last if $line eq "";
 240                        if ($line =~ m/^tree (.*)$/) {
 241                                $tree = $1;
 242                        } elsif ($line =~ m/^parent (.*)$/) {
 243                                push @parents, $1;
 244                        } elsif ($line =~ m/^committer (.*>) ([0-9]+) (.*)$/) {
 245                                $committer = $1;
 246                                $committer_time = $2;
 247                                $committer_timezone = $3;
 248                        } elsif ($line =~ m/^author (.*>) ([0-9]+) (.*)$/) {
 249                                $author = $1;
 250                                $author_time = $2;
 251                                $author_timezone = $3;
 252                                $author =~ m/^(.*) </;
 253                                $author_name = $1;
 254                        }
 255                }
 256                $shortlog = <$fd>;
 257                $shortlog = escapeHTML($shortlog);
 258                $comment = $shortlog . "<br/>";
 259                while (my $line = <$fd>) {
 260                                chomp($line);
 261                                $comment .= escapeHTML($line) . "<br/>\n";
 262                }
 263                close $fd;
 264                my $age = time-$committer_time;
 265                last if ($view_back > 0 && $age > $view_back);
 266
 267                my $age_string;
 268                if ($age > 60*60*24*365*2) {
 269                        $age_string = int $age/60/60/24/365;
 270                        $age_string .= " years ago";
 271                } elsif ($age > 60*60*24*365/12*2) {
 272                        $age_string = int $age/60/60/24/365/12;
 273                        $age_string .= " months ago";
 274                } elsif ($age > 60*60*24*7*2) {
 275                        $age_string = int $age/60/60/24/7;
 276                        $age_string .= " weeks ago";
 277                } elsif ($age > 60*60*24*2) {
 278                        $age_string = int $age/60/60/24;
 279                        $age_string .= " days ago";
 280                } elsif ($age > 60*60*2) {
 281                        $age_string = int $age/60/60;
 282                        $age_string .= " hours ago";
 283                } elsif ($age > 60*2) {
 284                        $age_string = int $age/60;
 285                        $age_string .= " minutes ago";
 286                }
 287                print "<tr>\n";
 288                print "<td class=\"head1\">" . $age_string . "</td>\n";
 289                print "<td class=\"head1\"><a href=\"$myself?project=$project&amp;action=commit&amp;hash=$commit&amp;parent=$parent\">" . $shortlog . "</a></td>";
 290                print "</tr>\n";
 291                print "<tr>\n";
 292                print "<td class=\"head3\">";
 293                print $cgi->a({-href => "$myself?project=$project&action=diffs&hash=$commit&parent=$parent"}, "view diff") . "<br/>\n";
 294                print $cgi->a({-href => "$myself?project=$project&action=commit&hash=$commit&parent=$parent"}, "view commit") . "<br/>\n";
 295                print $cgi->a({-href => "$myself?project=$project&action=tree&hash=$tree"}, "view tree") . "<br/>\n";
 296                print "</td>\n";
 297                print "<td class=\"head2\">\n";
 298                print "author &nbsp; &nbsp;" . escapeHTML($author) . " [" . gmtime($author_time) . " " . $author_timezone . "]<br/>\n";
 299                print "committer " . escapeHTML($committer) . " [" . gmtime($committer_time) . " " . $committer_timezone . "]<br/>\n";
 300                print "commit &nbsp; &nbsp;$commit<br/>\n";
 301                print "tree &nbsp; &nbsp; &nbsp;$tree<br/>\n";
 302                foreach my $par (@parents) {
 303                        print "parent &nbsp; &nbsp;$par<br/>\n";
 304                }
 305                print "</td>";
 306                print "</tr>\n";
 307                print "<tr>\n";
 308                print "<td></td>\n";
 309                print "<td>\n";
 310                print "$comment<br/><br/>\n";
 311                print "</td>";
 312                print "</tr>\n";
 313        }
 314        print "</table>\n";
 315        git_footer();
 316} elsif ($action eq "commit") {
 317        open my $fd, "-|", "$gitbin/diff-tree", "-r", $parent, $hash;
 318        my (@difftree) = map { chomp; $_ } <$fd>;
 319        close $fd;
 320
 321        git_header();
 322        print "<br/><br/><div class=\"main\">\n";
 323        print "<pre>\n";
 324        foreach my $line (@difftree) {
 325                $line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/;
 326                my $op = $1;
 327                my $mode = $2;
 328                my $type = $3;
 329                my $id = $4;
 330                my $file = $5;
 331                if ($type eq "blob") {
 332                        if ($op eq "+") {
 333                                print "NEW\t" . $cgi->a({-href => "$myself?project=$project&action=blob&hash=$id"}, $file) . "\n";
 334                        } elsif ($op eq "-") {
 335                                print "DEL\t" . $cgi->a({-href => "$myself?project=$project&action=blob&hash=$id"}, $file) . "\n";
 336                        } elsif ($op eq "*") {
 337                                $id =~ m/([0-9a-fA-F]+)->([0-9a-fA-F]+)/;
 338                                my $old = $1;
 339                                my $new = $2;
 340                                print "CHANGED\t" . $cgi->a({-href => "$myself?project=$project&action=diff&hash=$old&parent=$new"}, $file) . "\n";
 341                        }
 342                }
 343        }
 344        print "</pre>\n";
 345        print "<br/></div>";
 346        git_footer();
 347} elsif ($action eq "diff") {
 348        git_header();
 349        print "<br/><br/><div class=\"main\">\n";
 350        print "<pre>\n";
 351        git_diff($hash, $parent, $hash, $parent);
 352        print "</pre>\n";
 353        print "<br/></div>";
 354        git_footer();
 355} elsif ($action eq "diffs") {
 356        open my $fd, "-|", "$gitbin/diff-tree", "-r", $parent, $hash;
 357        my (@difftree) = map { chomp; $_ } <$fd>;
 358        close $fd;
 359
 360        git_header();
 361        print "<br/><br/><div class=\"main\">\n";
 362        print "<pre>\n";
 363        foreach my $line (@difftree) {
 364                $line =~ m/^(.)(.*)\t(.*)\t(.*)\t(.*)$/;
 365                my $op = $1;
 366                my $mode = $2;
 367                my $type = $3;
 368                my $id = $4;
 369                my $file = $5;
 370                if ($type eq "blob") {
 371                        if ($op eq "+") {
 372                                git_diff("", $file, "", $id);
 373                        } elsif ($op eq "-") {
 374                                git_diff($file, "", $id, "");
 375                        } elsif ($op eq "*") {
 376                                $id =~ m/([0-9a-fA-F]+)->([0-9a-fA-F]+)/;
 377                                git_diff($file, $file, $1, $2);
 378                        }
 379                }
 380                print "<br/>\n";
 381        }
 382        print "</pre>\n";
 383        print "<br/></div>";
 384        print "<br/></div>";
 385        git_footer();
 386} else {
 387        git_header();
 388        print "<br/><br/><div class=\"main\">\n";
 389        print "unknown action\n";
 390        print "<br/></div>";
 391        git_footer();
 392}