+ "<div class=\"diff_info\">blob:" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash_parent;hb=$hash_base;f=$file_name")}, $hash_parent) .
+ " -> blob:" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, $hash) .
+ "</div>\n";
+ git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash);
+ print "</div>";
+ git_footer_html();
+}
+
+sub git_blobdiff_plain {
+ mkdir($git_temp, 0700);
+ print $cgi->header(-type => "text/plain", -charset => 'utf-8');
+ git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash, "plain");
+}
+
+sub git_commitdiff {
+ 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'};
+ }
+ open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
+ my (@difftree) = map { chomp; $_ } <$fd>;
+ close $fd or die_error(undef, "Reading diff-tree failed.");
+
+ # non-textual hash id's can be cached
+ my $expires;
+ if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
+ $expires = "+1d";
+ }
+ my $refs = read_info_ref();
+ my $ref = "";
+ if (defined $refs->{$co{'id'}}) {
+ $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>";
+ }
+ git_header_html(undef, $expires);
+ print "<div class=\"page_nav\">\n" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
+ " | commitdiff" .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "<br/>\n";
+ print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . "\n" .
+ "</div>\n";
+ print "<div>\n" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" .
+ "</div>\n";
+ print "<div class=\"page_body\">\n";
+ my $comment = $co{'comment'};
+ my $empty = 0;
+ my $signed = 0;
+ my @log = @$comment;
+ # remove first and empty lines after that
+ shift @log;
+ while (defined $log[0] && $log[0] eq "") {
+ shift @log;
+ }
+ foreach my $line (@log) {
+ if ($line =~ m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i) {
+ next;
+ }
+ if ($line eq "") {
+ if ($empty) {
+ next;
+ }
+ $empty = 1;
+ } else {
+ $empty = 0;
+ }
+ print format_log_line_html($line) . "<br/>\n";
+ }
+ print "<br/>\n";
+ 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(.*)$/;
+ my $from_mode = $1;
+ my $to_mode = $2;
+ my $from_id = $3;
+ my $to_id = $4;
+ my $status = $5;
+ my $file = validate_input(unquote($6));
+ if ($status eq "A") {
+ print "<div class=\"diff_info\">" . file_type($to_mode) . ":" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" .
+ "</div>\n";
+ git_diff_print(undef, "/dev/null", $to_id, "b/$file");
+ } elsif ($status eq "D") {
+ print "<div class=\"diff_info\">" . file_type($from_mode) . ":" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;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($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");
+ }
+ }
+ }
+ print "<br/>\n" .
+ "</div>";
+ git_footer_html();
+}
+
+sub git_commitdiff_plain {
+ mkdir($git_temp, 0700);
+ open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed.");
+ my (@difftree) = map { chomp; $_ } <$fd>;
+ close $fd or die_error(undef, "Reading diff-tree failed.");
+
+ # try to figure out the next tag after this commit
+ my $tagname;
+ my $refs = read_info_ref("tags");
+ open $fd, "-|", "$GIT rev-list HEAD";
+ chomp (my (@commits) = <$fd>);
+ close $fd;
+ foreach my $commit (@commits) {
+ if (defined $refs->{$commit}) {
+ $tagname = $refs->{$commit}
+ }
+ if ($commit eq $hash) {
+ last;
+ }
+ }
+
+ 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" .
+ "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n".
+ "Subject: $co{'title'}\n";
+ if (defined $tagname) {
+ print "X-Git-Tag: $tagname\n";
+ }
+ print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
+ "\n";
+
+ foreach my $line (@$comment) {;
+ print "$line\n";
+ }
+ 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(.*)$/;
+ my $from_id = $3;
+ my $to_id = $4;
+ my $status = $5;
+ my $file = $6;
+ if ($status eq "A") {
+ git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain");
+ } elsif ($status eq "D") {
+ git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain");
+ } elsif ($status eq "M") {
+ git_diff_print($from_id, "a/$file", $to_id, "b/$file", "plain");
+ }
+ }
+}
+
+sub git_history {
+ if (!defined $hash_base) {
+ $hash_base = git_read_head($project);
+ }
+ my %co = git_read_commit($hash_base);
+ if (!%co) {
+ die_error(undef, "Unknown commit object.");
+ }
+ my $refs = read_info_ref();
+ git_header_html();
+ print "<div class=\"page_nav\">\n" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") .
+ "<br/><br/>\n" .
+ "</div>\n";
+ print "<div>\n" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" .
+ "</div>\n";
+ if (!defined $hash && defined $file_name) {
+ $hash = git_get_hash_by_path($hash_base, $file_name);
+ }
+ if (defined $hash) {
+ my $ftype = git_get_type($hash);
+
+ if ($ftype =~ "blob") {
+ print "<div class=\"page_path\"><b>/" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($file_name)) . "</b><br/></div>\n";
+ } else {
+ print "<div class=\"page_path\"><b>/" . esc_html($file_name) . "</b><br/></div>\n";
+ }
+ } else {
+ print "<div class=\"page_path\"><b>/" . esc_html($file_name) . "</b><br/></div>\n";
+ }
+
+ open my $fd, "-|",
+ "$GIT rev-list --full-history $hash_base -- \'$file_name\'";
+ print "<table cellspacing=\"0\">\n";
+ my $alternate = 0;
+ while (my $line = <$fd>) {
+ if ($line =~ m/^([0-9a-fA-F]{40})/){
+ my $commit = $1;
+ my %co = git_read_commit($commit);
+ if (!%co) {
+ next;
+ }
+ my $ref = "";
+ if (defined $refs->{$commit}) {
+ $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>";
+ }
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" .
+ "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" .
+ esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=$commit;f=$file_name")}, "blob");
+ my $blob = git_get_hash_by_path($hash_base, $file_name);
+ my $blob_parent = git_get_hash_by_path($commit, $file_name);
+ if (defined $blob && defined $blob_parent && $blob ne $blob_parent) {
+ print " | " .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")},
+ "diff to current");
+ }
+ print "</td>\n" .
+ "</tr>\n";
+ }
+ }
+ print "</table>\n";
+ close $fd;
+ git_footer_html();
+}
+
+sub git_search {
+ if (!defined $searchtext) {
+ die_error("", "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.");
+ }
+ # pickaxe may take all resources of your box and run for several minutes
+ # with every query - so decide by yourself how public you make this feature :)
+ my $commit_search = 1;
+ my $author_search = 0;
+ my $committer_search = 0;
+ my $pickaxe_search = 0;
+ if ($searchtext =~ s/^author\\://i) {
+ $author_search = 1;
+ } elsif ($searchtext =~ s/^committer\\://i) {
+ $committer_search = 1;
+ } elsif ($searchtext =~ s/^pickaxe\\://i) {
+ $commit_search = 0;
+ $pickaxe_search = 1;
+ }
+ git_header_html();
+ print "<div class=\"page_nav\">\n" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary;h=$hash")}, "summary") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") .
+ "<br/><br/>\n" .
+ "</div>\n";
+
+ print "<div>\n" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" .
+ "</div>\n";
+ print "<table cellspacing=\"0\">\n";
+ my $alternate = 0;
+ if ($commit_search) {
+ $/ = "\0";
+ open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next;
+ while (my $commit_text = <$fd>) {
+ if (!grep m/$searchtext/i, $commit_text) {
+ next;
+ }
+ if ($author_search && !grep m/\nauthor .*$searchtext/i, $commit_text) {
+ next;
+ }
+ if ($committer_search && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
+ next;
+ }
+ my @commit_lines = split "\n", $commit_text;
+ my %co = git_read_commit(undef, \@commit_lines);
+ if (!%co) {
+ next;
+ }
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+ "<td>" .
+ $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/>");
+ my $comment = $co{'comment'};
+ foreach my $line (@$comment) {
+ if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
+ my $lead = esc_html($1) || "";
+ $lead = chop_str($lead, 30, 10);
+ my $match = esc_html($2) || "";
+ my $trail = esc_html($3) || "";
+ $trail = chop_str($trail, 30, 10);
+ my $text = "$lead<span class=\"match\">$match</span>$trail";
+ print chop_str($text, 80, 5) . "<br/>\n";