Merge branch 'lt/web'
[gitweb.git] / gitweb / gitweb.perl
index 6e4261d5f23174cc771dad193ba522116d1ab498..ae13e3e701d0c73903521b56d31712b9799ef5e5 100755 (executable)
 binmode STDOUT, ':utf8';
 
 our $cgi = new CGI;
-our $version = "@@GIT_VERSION@@";
+our $version = "++GIT_VERSION++";
 our $my_url = $cgi->url();
 our $my_uri = $cgi->url(-absolute => 1);
-our $rss_link = "";
 
 # core git executable to use
 # this can just be "git" if your webserver has a sensible PATH
-our $GIT = "@@GIT_BINDIR@@/git";
+our $GIT = "++GIT_BINDIR++/git";
 
 # absolute fs-path which will be prepended to the project path
 #our $projectroot = "/pub/scm";
-our $projectroot = "@@GITWEB_PROJECTROOT@@";
-
-# version of the core git binary
-our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+our $projectroot = "++GITWEB_PROJECTROOT++";
 
 # location for temporary files needed for diffs
 our $git_temp = "/tmp/gitweb";
-if (! -d $git_temp) {
-       mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp");
-}
 
 # target of the home link on top of all pages
 our $home_link = $my_uri;
 
 # name of your site or organization to appear in page titles
 # replace this with something more descriptive for clearer bookmarks
-our $site_name = "@@GITWEB_SITENAME@@" || $ENV{'SERVER_NAME'} || "Untitled";
+our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
 
 # html text to include at home page
-our $home_text = "@@GITWEB_HOMETEXT@@";
+our $home_text = "++GITWEB_HOMETEXT++";
 
 # URI of default stylesheet
-our $stylesheet = "@@GITWEB_CSS@@";
+our $stylesheet = "++GITWEB_CSS++";
+# URI of GIT logo
+our $logo = "++GITWEB_LOGO++";
 
 # source of projects list
-our $projects_list = "@@GITWEB_LIST@@" || "$projectroot";
+our $projects_list = "++GITWEB_LIST++";
 
 # default blob_plain mimetype and default charset for text/plain blob
 our $default_blob_plain_mimetype = 'text/plain';
 # (relative to the current git repository)
 our $mimetypes_file = undef;
 
+our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
+require $GITWEB_CONFIG if -e $GITWEB_CONFIG;
+
+# version of the core git binary
+our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+
+$projects_list ||= $projectroot;
+if (! -d $git_temp) {
+       mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp");
+}
+
+# ======================================================================
 # input validation and dispatch
 our $action = $cgi->param('a');
 if (defined $action) {
        if ($action =~ m/[^0-9a-zA-Z\.\-_]/) {
-               undef $action;
-               die_error(undef, "Invalid action parameter.");
+               die_error(undef, "Invalid action parameter");
        }
-       if ($action eq "git-logo.png") {
-               git_logo();
-               exit;
-       } elsif ($action eq "opml") {
+       # action which does not check rest of parameters
+       if ($action eq "opml") {
                git_opml();
                exit;
        }
 
 our $project = ($cgi->param('p') || $ENV{'PATH_INFO'});
 if (defined $project) {
-       $project =~ s|^/||; $project =~ s|/$||;
-       $project = validate_input($project);
-       if (!defined($project)) {
-               die_error(undef, "Invalid project parameter.");
+       $project =~ s|^/||;
+       $project =~ s|/$||;
+}
+if (defined $project && $project) {
+       if (!validate_input($project)) {
+               die_error(undef, "Invalid project parameter");
        }
        if (!(-d "$projectroot/$project")) {
-               undef $project;
-               die_error(undef, "No such directory.");
+               die_error(undef, "No such directory");
        }
        if (!(-e "$projectroot/$project/HEAD")) {
-               undef $project;
-               die_error(undef, "No such project.");
+               die_error(undef, "No such project");
        }
-       $rss_link = "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" .
-                   "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>";
        $ENV{'GIT_DIR'} = "$projectroot/$project";
 } else {
        git_project_list();
 
 our $file_name = $cgi->param('f');
 if (defined $file_name) {
-       $file_name = validate_input($file_name);
-       if (!defined($file_name)) {
-               die_error(undef, "Invalid file parameter.");
+       if (!validate_input($file_name)) {
+               die_error(undef, "Invalid file parameter");
        }
 }
 
 our $hash = $cgi->param('h');
 if (defined $hash) {
-       $hash = validate_input($hash);
-       if (!defined($hash)) {
-               die_error(undef, "Invalid hash parameter.");
+       if (!validate_input($hash)) {
+               die_error(undef, "Invalid hash parameter");
        }
 }
 
 our $hash_parent = $cgi->param('hp');
 if (defined $hash_parent) {
-       $hash_parent = validate_input($hash_parent);
-       if (!defined($hash_parent)) {
-               die_error(undef, "Invalid hash parent parameter.");
+       if (!validate_input($hash_parent)) {
+               die_error(undef, "Invalid hash parent parameter");
        }
 }
 
 our $hash_base = $cgi->param('hb');
 if (defined $hash_base) {
-       $hash_base = validate_input($hash_base);
-       if (!defined($hash_base)) {
-               die_error(undef, "Invalid hash base parameter.");
+       if (!validate_input($hash_base)) {
+               die_error(undef, "Invalid hash base parameter");
        }
 }
 
 our $page = $cgi->param('pg');
 if (defined $page) {
        if ($page =~ m/[^0-9]$/) {
-               undef $page;
-               die_error(undef, "Invalid page parameter.");
+               die_error(undef, "Invalid page parameter");
        }
 }
 
 our $searchtext = $cgi->param('s');
 if (defined $searchtext) {
        if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
-               undef $searchtext;
-               die_error(undef, "Invalid search parameter.");
+               die_error(undef, "Invalid search parameter");
        }
        $searchtext = quotemeta $searchtext;
 }
 
 $action = 'summary' if (!defined($action));
 if (!defined($actions{$action})) {
-       undef $action;
-       die_error(undef, "Unknown action.");
+       die_error(undef, "Unknown action");
 }
 $actions{$action}->();
 exit;
@@ -229,6 +224,20 @@ sub unquote {
        return $str;
 }
 
+# escape tabs (convert tabs to spaces)
+sub untabify {
+       my $line = shift;
+
+       while ((my $pos = index($line, "\t")) != -1) {
+               if (my $count = (8 - ($pos % 8))) {
+                       my $spaces = ' ' x $count;
+                       $line =~ s/\t/$spaces/;
+               }
+       }
+
+       return $line;
+}
+
 ## ----------------------------------------------------------------------
 ## HTML aware string manipulation
 
@@ -423,7 +432,7 @@ sub git_get_hash_by_path {
        my $tree = $base;
 
        open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
-               or die_error(undef, "Open git-ls-tree failed.");
+               or die_error(undef, "Open git-ls-tree failed");
        my $line = <$fd>;
        close $fd or return undef;
 
@@ -850,7 +859,7 @@ sub git_header_html {
        # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
        # we have to do this because MSIE sometimes globs '*/*', pretending to
        # support xhtml+xml but choking when it gets what it asked for.
-       if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
+       if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) {
                $content_type = 'application/xhtml+xml';
        } else {
                $content_type = 'text/html';
@@ -867,13 +876,19 @@ sub git_header_html {
 <meta name="robots" content="index, nofollow"/>
 <title>$title</title>
 <link rel="stylesheet" type="text/css" href="$stylesheet"/>
-$rss_link
-</head>
-<body>
 EOF
-       print "<div class=\"page_header\">\n" .
+       if (defined $project) {
+               printf('<link rel="alternate" title="%s log" '.
+                      'href="%s" type="application/rss+xml"/>'."\n",
+                      esc_param($project),
+                      esc_param("$my_uri?p=$project;a=rss"));
+       }
+
+       print "</head>\n" .
+             "<body>\n" .
+             "<div class=\"page_header\">\n" .
              "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
-             "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
+             "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
              "</a>\n";
        print $cgi->a({-href => esc_param($home_link)}, "projects") . " / ";
        if (defined $project) {
@@ -1050,7 +1065,6 @@ sub git_shortlog_body {
                #my $ref = defined $refs ? git_get_referencing($refs, $commit) : '';
                my $ref = git_get_referencing($refs, $commit);
                my %co = git_read_commit($commit);
-               my %ad = date_str($co{'author_epoch'});
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
@@ -1243,12 +1257,7 @@ sub git_diff_print {
                                # skip errors
                                next;
                        }
-                       while ((my $pos = index($line, "\t")) != -1) {
-                               if (my $count = (8 - (($pos-1) % 8))) {
-                                       my $spaces = ' ' x $count;
-                                       $line =~ s/\t/$spaces/;
-                               }
-                       }
+                       $line = untabify($line);
                        print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
                }
        }
@@ -1267,36 +1276,16 @@ sub git_diff_print {
 ## ======================================================================
 ## actions
 
-# git-logo (cached in browser for one day)
-sub git_logo {
-       binmode STDOUT, ':raw';
-       print $cgi->header(-type => 'image/png', -expires => '+1d');
-       # cat git-logo.png | hexdump -e '16/1 " %02x"  "\n"' | sed 's/ /\\x/g'
-       print   "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" .
-               "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" .
-               "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" .
-               "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" .
-               "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" .
-               "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" .
-               "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" .
-               "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" .
-               "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" .
-               "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" .
-               "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" .
-               "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" .
-               "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82";
-}
-
 sub git_project_list {
        my $order = $cgi->param('o');
        if (defined $order && $order !~ m/project|descr|owner|age/) {
-               die_error(undef, "Invalid order parameter '$order'.");
+               die_error(undef, "Unknown order parameter");
        }
 
        my @list = git_read_projects();
        my @projects;
        if (!@list) {
-               die_error(undef, "No projects found.");
+               die_error(undef, "No projects found");
        }
        foreach my $pr (@list) {
                my $head = git_read_head($pr->{'path'});
@@ -1430,7 +1419,7 @@ sub git_summary {
              "</table>\n";
 
        open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project)
-               or die_error(undef, "Open git-rev-list failed.");
+               or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd;
        git_header_div('shortlog');
@@ -1486,10 +1475,10 @@ sub git_tag {
 sub git_blame2 {
        my $fd;
        my $ftype;
-       die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame'));
+       die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame'));
        die_error('404 Not Found', "File name not defined") if (!$file_name);
        $hash_base ||= git_read_head($project);
-       die_error(undef, "Reading commit failed") unless ($hash_base);
+       die_error(undef, "Couldn't find base commit") unless ($hash_base);
        my %co = git_read_commit($hash_base)
                or die_error(undef, "Reading commit failed");
        if (!defined $hash) {
@@ -1498,10 +1487,10 @@ sub git_blame2 {
        }
        $ftype = git_get_type($hash);
        if ($ftype !~ "blob") {
-               die_error("400 Bad Request", "object is not a blob");
+               die_error("400 Bad Request", "Object is not a blob");
        }
        open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base)
-               or die_error(undef, "Open git-blame failed.");
+               or die_error(undef, "Open git-blame failed");
        git_header_html();
        my $formats_nav =
                $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
@@ -1509,7 +1498,7 @@ sub git_blame2 {
        git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
        git_header_div('commit', esc_html($co{'title'}), $hash_base);
        git_print_page_path($file_name, $ftype);
-       my @rev_color = (qw(light dark));
+       my @rev_color = (qw(light2 dark2));
        my $num_colors = scalar(@rev_color);
        my $current_color = 0;
        my $last_rev;
@@ -1544,18 +1533,18 @@ sub git_blame2 {
 
 sub git_blame {
        my $fd;
-       die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame'));
-       die_error('404 Not Found', "What file will it be, master?") if (!$file_name);
+       die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame'));
+       die_error('404 Not Found', "File name not defined") if (!$file_name);
        $hash_base ||= git_read_head($project);
-       die_error(undef, "Reading commit failed.") unless ($hash_base);
+       die_error(undef, "Couldn't find base commit") unless ($hash_base);
        my %co = git_read_commit($hash_base)
-               or die_error(undef, "Reading commit failed.");
+               or die_error(undef, "Reading commit failed");
        if (!defined $hash) {
                $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
-                       or die_error(undef, "Error lookup file.");
+                       or die_error(undef, "Error lookup file");
        }
        open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
-               or die_error(undef, "Open git-annotate failed.");
+               or die_error(undef, "Open git-annotate failed");
        git_header_html();
        my $formats_nav =
                $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
@@ -1608,13 +1597,8 @@ sub git_blame {
                $age_class  = age_class($age);
                $author     = esc_html ($author);
                $author     =~ s/ /&nbsp;/g;
-               # escape tabs
-               while ((my $pos = index($data, "\t")) != -1) {
-                       if (my $count = (8 - ($pos % 8))) {
-                               my $spaces = ' ' x $count;
-                               $data =~ s/\t/$spaces/;
-                       }
-               }
+
+               $data = untabify($data);
                $data = esc_html ($data);
 
                print <<HTML;
@@ -1653,7 +1637,6 @@ sub git_heads {
        git_header_div('summary', $project);
 
        my $taglist = git_read_refs("refs/heads");
-       my $alternate = 0;
        if (defined @$taglist) {
                git_heads_body($taglist, $head);
        }
@@ -1665,14 +1648,14 @@ sub git_blob_plain {
                if (defined $file_name) {
                        my $base = $hash_base || git_read_head($project);
                        $hash = git_get_hash_by_path($base, $file_name, "blob")
-                               or die_error(undef, "Error lookup file.");
+                               or die_error(undef, "Error lookup file");
                } else {
-                       die_error(undef, "No file name defined.");
+                       die_error(undef, "No file name defined");
                }
        }
        my $type = shift;
        open my $fd, "-|", $GIT, "cat-file", "blob", $hash
-               or die_error("Couldn't cat $file_name, $hash");
+               or die_error(undef, "Couldn't cat $file_name, $hash");
 
        $type ||= git_blob_plain_mimetype($fd, $file_name);
 
@@ -1698,14 +1681,14 @@ sub git_blob {
                if (defined $file_name) {
                        my $base = $hash_base || git_read_head($project);
                        $hash = git_get_hash_by_path($base, $file_name, "blob")
-                               or die_error(undef, "Error lookup file.");
+                               or die_error(undef, "Error lookup file");
                } else {
-                       die_error(undef, "No file name defined.");
+                       die_error(undef, "No file name defined");
                }
        }
        my $have_blame = git_get_project_config_bool ('blame');
        open my $fd, "-|", $GIT, "cat-file", "blob", $hash
-               or die_error(undef, "Couldn't cat $file_name, $hash.");
+               or die_error(undef, "Couldn't cat $file_name, $hash");
        my $mimetype = git_blob_plain_mimetype($fd, $file_name);
        if ($mimetype !~ m/^text\//) {
                close $fd;
@@ -1737,12 +1720,7 @@ sub git_blob {
        while (my $line = <$fd>) {
                chomp $line;
                $nr++;
-               while ((my $pos = index($line, "\t")) != -1) {
-                       if (my $count = (8 - ($pos % 8))) {
-                               my $spaces = ' ' x $count;
-                               $line =~ s/\t/$spaces/;
-                       }
-               }
+               $line = untabify($line);
                printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line);
        }
        close $fd or print "Reading blob failed.\n";
@@ -1763,9 +1741,9 @@ sub git_tree {
        }
        $/ = "\0";
        open my $fd, "-|", $GIT, "ls-tree", '-z', $hash
-               or die_error(undef, "Open git-ls-tree failed.");
+               or die_error(undef, "Open git-ls-tree failed");
        my @entries = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading tree failed.");
+       close $fd or die_error(undef, "Reading tree failed");
        $/ = "\n";
 
        my $refs = read_info_ref();
@@ -1773,6 +1751,7 @@ sub git_tree {
        git_header_html();
        my $base_key = "";
        my $base = "";
+       my $have_blame = git_get_project_config_bool ('blame');
        if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
                $base_key = ";hb=$hash_base";
                git_page_nav('tree','', $hash_base);
@@ -1808,9 +1787,11 @@ sub git_tree {
                              $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)) .
                              "</td>\n" .
                              "<td class=\"link\">" .
-                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") .
-#                            " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") .
-                             " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") .
+                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob");
+                       if ($have_blame) {
+                               print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame");
+                       }
+                       print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") .
                              " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") .
                              "</td>\n";
                } elsif ($t_type eq "tree") {
@@ -1841,7 +1822,7 @@ sub git_log {
 
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
        open my $fd, "-|", $GIT, "rev-list", $limit, $hash
-               or die_error(undef, "Open git-rev-list failed.");
+               or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd;
 
@@ -1902,7 +1883,7 @@ sub git_log {
 sub git_commit {
        my %co = git_read_commit($hash);
        if (!%co) {
-               die_error(undef, "Unknown commit object.");
+               die_error(undef, "Unknown commit object");
        }
        my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
        my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
@@ -1912,9 +1893,9 @@ sub git_commit {
                $parent = "--root";
        }
        open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash
-               or die_error(undef, "Open git-diff-tree failed.");
+               or die_error(undef, "Open git-diff-tree failed");
        my @difftree = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading git-diff-tree failed.");
+       close $fd or die_error(undef, "Reading git-diff-tree failed");
 
        # non-textual hash id's can be cached
        my $expires;
@@ -2007,7 +1988,7 @@ sub git_commit {
        foreach my $line (@difftree) {
                # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M      ls-files.c'
                # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M      rev-tree.c'
-               if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
+               if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) {
                        next;
                }
                my $from_mode = $1;
@@ -2034,11 +2015,11 @@ sub git_commit {
                              "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n";
                } elsif ($status eq "D") {
                        print "<td>" .
-                             $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" .
+                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" .
                              "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" .
                              "<td class=\"link\">" .
-                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") .
-                             " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") .
+                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$file")}, "blob") .
+                             " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$parent;f=$file")}, "history") .
                              "</td>\n"
                } elsif ($status eq "M" || $status eq "T") {
                        my $mode_chnge = "";
@@ -2080,7 +2061,7 @@ sub git_commit {
                        print "<td>" .
                              $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" .
                              "<td><span class=\"file_status moved\">[moved from " .
-                             $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)) .
+                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$parent;f=$from_file"), -class => "list"}, esc_html($from_file)) .
                              " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" .
                              "<td class=\"link\">" .
                              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob");
@@ -2130,15 +2111,15 @@ sub git_commitdiff {
        mkdir($git_temp, 0700);
        my %co = git_read_commit($hash);
        if (!%co) {
-               die_error(undef, "Unknown commit object.");
+               die_error(undef, "Unknown commit object");
        }
        if (!defined $hash_parent) {
-               $hash_parent = $co{'parent'};
+               $hash_parent = $co{'parent'} || '--root';
        }
        open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
-               or die_error(undef, "Open git-diff-tree failed.");
+               or die_error(undef, "Open git-diff-tree failed");
        my @difftree = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading diff-tree failed.");
+       close $fd or die_error(undef, "Reading git-diff-tree failed");
 
        # non-textual hash id's can be cached
        my $expires;
@@ -2180,7 +2161,9 @@ sub git_commitdiff {
        foreach my $line (@difftree) {
                # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M      ls-files.c'
                # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M      rev-tree.c'
-               $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/;
+               if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
+                       next;
+               }
                my $from_mode = $1;
                my $to_mode = $2;
                my $from_id = $3;
@@ -2194,15 +2177,17 @@ sub git_commitdiff {
                        git_diff_print(undef, "/dev/null", $to_id, "b/$file");
                } elsif ($status eq "D") {
                        print "<div class=\"diff_info\">" . file_type($from_mode) . ":" .
-                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" .
+                             $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" .
                              "</div>\n";
                        git_diff_print($from_id, "a/$file", undef, "/dev/null");
                } elsif ($status eq "M") {
                        if ($from_id ne $to_id) {
                                print "<div class=\"diff_info\">" .
-                                     file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) .
+                                     file_type($from_mode) . ":" .
+                                     $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) .
                                      " -> " .
-                                     file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id);
+                                     file_type($to_mode) . ":" .
+                                     $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id);
                                print "</div>\n";
                                git_diff_print($from_id, "a/$file",  $to_id, "b/$file");
                        }
@@ -2215,10 +2200,17 @@ sub git_commitdiff {
 
 sub git_commitdiff_plain {
        mkdir($git_temp, 0700);
+       my %co = git_read_commit($hash);
+       if (!%co) {
+               die_error(undef, "Unknown commit object");
+       }
+       if (!defined $hash_parent) {
+               $hash_parent = $co{'parent'} || '--root';
+       }
        open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
-               or die_error(undef, "Open git-diff-tree failed.");
+               or die_error(undef, "Open git-diff-tree failed");
        my @difftree = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading diff-tree failed.");
+       close $fd or die_error(undef, "Reading diff-tree failed");
 
        # try to figure out the next tag after this commit
        my $tagname;
@@ -2236,7 +2228,6 @@ sub git_commitdiff_plain {
        }
 
        print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\"");
-       my %co = git_read_commit($hash);
        my %ad = date_str($co{'author_epoch'}, $co{'author_tz'});
        my $comment = $co{'comment'};
        print "From: $co{'author'}\n" .
@@ -2254,7 +2245,9 @@ sub git_commitdiff_plain {
        print "---\n\n";
 
        foreach my $line (@difftree) {
-               $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/;
+               if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
+                       next;
+               }
                my $from_id = $3;
                my $to_id = $4;
                my $status = $5;
@@ -2276,7 +2269,7 @@ sub git_history {
        my $ftype;
        my %co = git_read_commit($hash_base);
        if (!%co) {
-               die_error(undef, "Unknown commit object.");
+               die_error(undef, "Unknown commit object");
        }
        my $refs = read_info_ref();
        git_header_html();
@@ -2334,14 +2327,14 @@ sub git_history {
 
 sub git_search {
        if (!defined $searchtext) {
-               die_error("", "Text field empty.");
+               die_error(undef, "Text field empty");
        }
        if (!defined $hash) {
                $hash = git_read_head($project);
        }
        my %co = git_read_commit($hash);
        if (!%co) {
-               die_error(undef, "Unknown commit object.");
+               die_error(undef, "Unknown commit object");
        }
        # pickaxe may take all resources of your box and run for several minutes
        # with every query - so decide by yourself how public you make this feature :)
@@ -2479,7 +2472,7 @@ sub git_shortlog {
 
        my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
        open my $fd, "-|", $GIT, "rev-list", $limit, $hash
-               or die_error(undef, "Open git-rev-list failed.");
+               or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
        close $fd;
 
@@ -2507,9 +2500,9 @@ sub git_shortlog {
 sub git_rss {
        # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
        open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_read_head($project)
-               or die_error(undef, "Open git-rev-list failed.");
+               or die_error(undef, "Open git-rev-list failed");
        my @revlist = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading rev-list failed.");
+       close $fd or die_error(undef, "Reading git-rev-list failed");
        print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
        print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
              "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n";