+ close $fd or return;
+ return \%refs;
+}
+
+sub git_get_referencing {
+ my ($refs, $id) = @_;
+
+ if (defined $refs->{$id}) {
+ return ' <span class="tag">' . esc_html($refs->{$id}) . '</span>';
+ } else {
+ return "";
+ }
+}
+
+sub git_read_refs {
+ my $ref_dir = shift;
+ my @reflist;
+
+ my @refs;
+ opendir my $dh, "$projectroot/$project/$ref_dir";
+ while (my $dir = readdir($dh)) {
+ if ($dir =~ m/^\./) {
+ next;
+ }
+ if (-d "$projectroot/$project/$ref_dir/$dir") {
+ opendir my $dh2, "$projectroot/$project/$ref_dir/$dir";
+ my @subdirs = grep !m/^\./, readdir $dh2;
+ closedir($dh2);
+ foreach my $subdir (@subdirs) {
+ push @refs, "$dir/$subdir"
+ }
+ next;
+ }
+ push @refs, $dir;
+ }
+ closedir($dh);
+ foreach my $ref_file (@refs) {
+ my $ref_id = git_read_hash("$project/$ref_dir/$ref_file");
+ my $type = git_get_type($ref_id) || next;
+ my %ref_item;
+ my %co;
+ $ref_item{'type'} = $type;
+ $ref_item{'id'} = $ref_id;
+ $ref_item{'epoch'} = 0;
+ $ref_item{'age'} = "unknown";
+ if ($type eq "tag") {
+ my %tag = git_read_tag($ref_id);
+ $ref_item{'comment'} = $tag{'comment'};
+ if ($tag{'type'} eq "commit") {
+ %co = git_read_commit($tag{'object'});
+ $ref_item{'epoch'} = $co{'committer_epoch'};
+ $ref_item{'age'} = $co{'age_string'};
+ } elsif (defined($tag{'epoch'})) {
+ my $age = time - $tag{'epoch'};
+ $ref_item{'epoch'} = $tag{'epoch'};
+ $ref_item{'age'} = age_string($age);
+ }
+ $ref_item{'reftype'} = $tag{'type'};
+ $ref_item{'name'} = $tag{'name'};
+ $ref_item{'refid'} = $tag{'object'};
+ } elsif ($type eq "commit"){
+ %co = git_read_commit($ref_id);
+ $ref_item{'reftype'} = "commit";
+ $ref_item{'name'} = $ref_file;
+ $ref_item{'title'} = $co{'title'};
+ $ref_item{'refid'} = $ref_id;
+ $ref_item{'epoch'} = $co{'committer_epoch'};
+ $ref_item{'age'} = $co{'age_string'};
+ } else {
+ $ref_item{'reftype'} = $type;
+ $ref_item{'name'} = $ref_file;
+ $ref_item{'refid'} = $ref_id;
+ }
+
+ push @reflist, \%ref_item;
+ }
+ # sort tags by age
+ @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist;
+ return \@reflist;
+}
+
+sub git_shortlog_body {
+ # uses global variable $project
+ my ($revlist, $from, $to, $refs, $extra) = @_;
+ $from = 0 unless defined $from;
+ $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
+
+ print "<table class=\"shortlog\" cellspacing=\"0\">\n";
+ my $alternate = 0;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $commit = $revlist->[$i];
+ #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 {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" .
+ print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+ "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" .
+ "<td>";
+ if (length($co{'title_short'}) < length($co{'title'})) {
+ print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
+ -class => "list", -title => "$co{'title'}"},
+ "<b>" . esc_html($co{'title_short'}) . "$ref</b>");
+ } else {
+ print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"),
+ -class => "list"},
+ "<b>" . esc_html($co{'title'}) . "$ref</b>");
+ }
+ print "</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") .
+ "</td>\n" .
+ "</tr>\n";
+ }
+ if (defined $extra) {
+ print "<tr>\n" .
+ "<td colspan=\"4\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_tags_body {
+ # uses global variable $project
+ my ($taglist, $from, $to, $extra) = @_;
+ $from = 0 unless defined $from;
+ $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
+
+ print "<table class=\"tags\" cellspacing=\"0\">\n";
+ my $alternate = 0;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $entry = $taglist->[$i];
+ my %tag = %$entry;
+ my $comment_lines = $tag{'comment'};
+ my $comment = shift @$comment_lines;
+ my $comment_short;
+ if (defined $comment) {
+ $comment_short = chop_str($comment, 30, 5);
+ }
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td><i>$tag{'age'}</i></td>\n" .
+ "<td>" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"),
+ -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
+ "</td>\n" .
+ "<td>";
+ if (defined $comment) {
+ if (length($comment_short) < length($comment)) {
+ print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
+ -class => "list", -title => $comment}, $comment_short);
+ } else {
+ print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}"),
+ -class => "list"}, $comment);
+ }
+ }
+ print "</td>\n" .
+ "<td class=\"selflink\">";
+ if ($tag{'type'} eq "tag") {
+ print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag");
+ } else {
+ print " ";
+ }
+ print "</td>\n" .
+ "<td class=\"link\">" . " | " .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'});
+ if ($tag{'reftype'} eq "commit") {
+ print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log");
+ } elsif ($tag{'reftype'} eq "blob") {
+ print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$tag{'refid'}")}, "raw");
+ }
+ print "</td>\n" .
+ "</tr>";
+ }
+ if (defined $extra) {
+ print "<tr>\n" .
+ "<td colspan=\"5\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_heads_body {
+ # uses global variable $project
+ my ($taglist, $head, $from, $to, $extra) = @_;
+ $from = 0 unless defined $from;
+ $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
+
+ print "<table class=\"heads\" cellspacing=\"0\">\n";
+ my $alternate = 0;
+ for (my $i = $from; $i <= $to; $i++) {
+ my $entry = $taglist->[$i];
+ my %tag = %$entry;
+ my $curr = $tag{'id'} eq $head;
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td><i>$tag{'age'}</i></td>\n" .
+ ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"),
+ -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") .
+ "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . " | " .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") .
+ "</td>\n" .
+ "</tr>";
+ }
+ if (defined $extra) {
+ print "<tr>\n" .
+ "<td colspan=\"3\">$extra</td>\n" .
+ "</tr>\n";
+ }
+ print "</table>\n";
+}
+
+sub git_summary {
+ my $descr = git_read_description($project) || "none";
+ my $head = git_read_head($project);
+ my %co = git_read_commit($head);
+ my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'});
+
+ my $owner;
+ if (-f $projects_list) {
+ open (my $fd , $projects_list);
+ while (my $line = <$fd>) {
+ chomp $line;
+ my ($pr, $ow) = split ' ', $line;
+ $pr = unescape($pr);
+ $ow = unescape($ow);
+ if ($pr eq $project) {
+ $owner = decode("utf8", $ow, Encode::FB_DEFAULT);
+ last;
+ }
+ }
+ close $fd;
+ }
+ if (!defined $owner) {
+ $owner = get_file_owner("$projectroot/$project");
+ }
+
+ my $refs = read_info_ref();
+ git_header_html();
+ git_page_nav('summary','', $head);
+
+ print "<div class=\"title\"> </div>\n";
+ print "<table cellspacing=\"0\">\n" .
+ "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
+ "<tr><td>owner</td><td>$owner</td></tr>\n" .
+ "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" .
+ "</table>\n";
+
+ open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_read_head($project)
+ or die_error(undef, "Open git-rev-list failed.");
+ my @revlist = map { chomp; $_ } <$fd>;
+ close $fd;
+ git_header_div('shortlog');
+ git_shortlog_body(\@revlist, 0, 15, $refs,
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "..."));
+
+ my $taglist = git_read_refs("refs/tags");
+ if (defined @$taglist) {
+ git_header_div('tags');
+ git_tags_body($taglist, 0, 15,
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "..."));
+ }
+
+ my $headlist = git_read_refs("refs/heads");
+ if (defined @$headlist) {
+ git_header_div('heads');
+ git_heads_body($taglist, $head, 0, 15,
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "..."));
+ }
+
+ git_footer_html();
+}
+
+sub git_print_page_path {
+ my $name = shift;
+ my $type = shift;
+
+ if (!defined $name) {
+ print "<div class=\"page_path\"><b>/</b></div>\n";
+ } elsif ($type =~ "blob") {
+ print "<div class=\"page_path\"><b>" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n";
+ } else {
+ print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n";
+ }
+}
+
+sub git_tag {
+ my $head = git_read_head($project);
+ git_header_html();
+ git_page_nav('','', $head,undef,$head);
+ my %tag = git_read_tag($hash);
+ git_header_div('commit', esc_html($tag{'name'}), $hash);
+ print "<div class=\"title_text\">\n" .
+ "<table cellspacing=\"0\">\n" .
+ "<tr>\n" .
+ "<td>object</td>\n" .
+ "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" .
+ "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" .
+ "</tr>\n";
+ if (defined($tag{'author'})) {
+ my %ad = date_str($tag{'epoch'}, $tag{'tz'});
+ print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n";
+ print "<tr><td></td><td>" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "</td></tr>\n";
+ }
+ print "</table>\n\n" .
+ "</div>\n";
+ print "<div class=\"page_body\">";
+ my $comment = $tag{'comment'};
+ foreach my $line (@$comment) {
+ print esc_html($line) . "<br/>\n";
+ }
+ print "</div>\n";
+ git_footer_html();
+}
+
+sub git_blame2 {
+ my $fd;
+ my $ftype;
+ 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);
+ my %co = git_read_commit($hash_base)
+ 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 looking up file");
+ }
+ $ftype = git_get_type($hash);
+ if ($ftype !~ "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.");
+ 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") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
+ 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 $num_colors = scalar(@rev_color);
+ my $current_color = 0;
+ my $last_rev;
+ print "<div class=\"page_body\">\n";
+ print "<table class=\"blame\">\n";
+ print "<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n";
+ while (<$fd>) {
+ /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
+ my $full_rev = $1;
+ my $rev = substr($full_rev, 0, 8);
+ my $lineno = $2;
+ my $data = $3;
+
+ if (!defined $last_rev) {
+ $last_rev = $full_rev;
+ } elsif ($last_rev ne $full_rev) {
+ $last_rev = $full_rev;
+ $current_color = ++$current_color % $num_colors;
+ }
+ print "<tr class=\"$rev_color[$current_color]\">\n";
+ print "<td class=\"sha1\">" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n";
+ print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n";
+ print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
+ print "</tr>\n";
+ }
+ print "</table>\n";
+ print "</div>";
+ close $fd or print "Reading blob failed\n";
+ git_footer_html();
+}
+
+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);
+ $hash_base ||= git_read_head($project);
+ die_error(undef, "Reading commit failed.") unless ($hash_base);
+ my %co = git_read_commit($hash_base)
+ 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.");
+ }
+ open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base)
+ 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") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head");
+ 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);
+ print "<div class=\"page_body\">\n";
+ print <<HTML;
+<table class="blame">
+ <tr>
+ <th>Commit</th>
+ <th>Age</th>
+ <th>Author</th>
+ <th>Line</th>
+ <th>Data</th>
+ </tr>
+HTML
+ my @line_class = (qw(light dark));
+ my $line_class_len = scalar (@line_class);
+ my $line_class_num = $#line_class;
+ while (my $line = <$fd>) {
+ my $long_rev;
+ my $short_rev;
+ my $author;
+ my $time;
+ my $lineno;
+ my $data;
+ my $age;
+ my $age_str;
+ my $age_class;
+
+ chomp $line;
+ $line_class_num = ($line_class_num + 1) % $line_class_len;
+
+ if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) {
+ $long_rev = $1;
+ $author = $2;
+ $time = $3;
+ $lineno = $4;
+ $data = $5;
+ } else {
+ print qq( <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n);
+ next;
+ }
+ $short_rev = substr ($long_rev, 0, 8);
+ $age = time () - $time;
+ $age_str = age_string ($age);
+ $age_str =~ s/ / /g;
+ $age_class = age_class($age);
+ $author = esc_html ($author);
+ $author =~ s/ / /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 = esc_html ($data);
+
+ print <<HTML;
+ <tr class="$line_class[$line_class_num]">
+ <td class="sha1"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td>
+ <td class="$age_class">$age_str</td>
+ <td>$author</td>
+ <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
+ <td class="pre">$data</td>
+ </tr>
+HTML
+ } # while (my $line = <$fd>)
+ print "</table>\n\n";
+ close $fd or print "Reading blob failed.\n";
+ print "</div>";
+ git_footer_html();
+}
+
+sub git_tags {
+ my $head = git_read_head($project);
+ git_header_html();
+ git_page_nav('','', $head,undef,$head);
+ git_header_div('summary', $project);
+
+ my $taglist = git_read_refs("refs/tags");
+ if (defined @$taglist) {
+ git_tags_body($taglist);
+ }
+ git_footer_html();
+}
+
+sub git_heads {
+ my $head = git_read_head($project);
+ git_header_html();
+ git_page_nav('','', $head,undef,$head);
+ git_header_div('summary', $project);
+
+ my $taglist = git_read_refs("refs/heads");
+ my $alternate = 0;
+ if (defined @$taglist) {
+ git_heads_body($taglist, $head);
+ }
+ git_footer_html();
+}
+
+sub git_get_hash_by_path {
+ my $base = shift;
+ my $path = shift || return undef;
+
+ my $tree = $base;
+
+ open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path
+ or die_error(undef, "Open git-ls-tree failed.");
+ my $line = <$fd>;
+ close $fd or return undef;
+
+ #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
+ $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+ return $3;
+}
+
+sub mimetype_guess_file {
+ my $filename = shift;
+ my $mimemap = shift;
+ -r $mimemap or return undef;
+
+ my %mimemap;
+ open(MIME, $mimemap) or return undef;
+ while (<MIME>) {
+ my ($mime, $exts) = split(/\t+/);
+ my @exts = split(/\s+/, $exts);
+ foreach my $ext (@exts) {
+ $mimemap{$ext} = $mime;
+ }
+ }
+ close(MIME);
+
+ $filename =~ /\.(.*?)$/;
+ return $mimemap{$1};
+}
+
+sub mimetype_guess {
+ my $filename = shift;
+ my $mime;
+ $filename =~ /\./ or return undef;
+
+ if ($mimetypes_file) {
+ my $file = $mimetypes_file;
+ #$file =~ m#^/# or $file = "$projectroot/$path/$file";
+ $mime = mimetype_guess_file($filename, $file);
+ }
+ $mime ||= mimetype_guess_file($filename, '/etc/mime.types');
+ return $mime;
+}
+
+sub git_blob_plain_mimetype {
+ my $fd = shift;
+ my $filename = shift;
+
+ if ($filename) {
+ my $mime = mimetype_guess($filename);
+ $mime and return $mime;
+ }
+
+ # just in case
+ return $default_blob_plain_mimetype unless $fd;
+
+ if (-T $fd) {
+ return 'text/plain' .
+ ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : '');
+ } elsif (! $filename) {
+ return 'application/octet-stream';
+ } elsif ($filename =~ m/\.png$/i) {
+ return 'image/png';
+ } elsif ($filename =~ m/\.gif$/i) {
+ return 'image/gif';
+ } elsif ($filename =~ m/\.jpe?g$/i) {
+ return 'image/jpeg';
+ } else {
+ return 'application/octet-stream';
+ }
+}
+
+sub git_blob_plain {
+ if (!defined $hash) {
+ 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.");
+ } else {
+ 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");
+
+ $type ||= git_blob_plain_mimetype($fd, $file_name);
+
+ # save as filename, even when no $file_name is given
+ my $save_as = "$hash";
+ if (defined $file_name) {
+ $save_as = $file_name;
+ } elsif ($type =~ m/^text\//) {
+ $save_as .= '.txt';
+ }
+
+ print $cgi->header(-type => "$type", '-content-disposition' => "inline; filename=\"$save_as\"");
+ undef $/;
+ binmode STDOUT, ':raw';
+ print <$fd>;
+ binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
+ $/ = "\n";
+ close $fd;
+}
+
+sub git_blob {
+ if (!defined $hash) {
+ 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.");
+ } else {
+ 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.");
+ my $mimetype = git_blob_plain_mimetype($fd, $file_name);
+ if ($mimetype !~ m/^text\//) {
+ close $fd;
+ return git_blob_plain($mimetype);
+ }
+ git_header_html();
+ my $formats_nav = '';
+ if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
+ if (defined $file_name) {
+ if ($have_blame) {
+ $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | ";
+ }
+ $formats_nav .=
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head");
+ } else {
+ $formats_nav .= $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain");
+ }
+ git_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
+ git_header_div('commit', esc_html($co{'title'}), $hash_base);
+ } else {
+ print "<div class=\"page_nav\">\n" .
+ "<br/><br/></div>\n" .
+ "<div class=\"title\">$hash</div>\n";
+ }
+ git_print_page_path($file_name, "blob");
+ print "<div class=\"page_body\">\n";
+ my $nr;
+ 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/;
+ }
+ }
+ 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";
+ print "</div>";
+ git_footer_html();
+}
+
+sub git_tree {
+ if (!defined $hash) {
+ $hash = git_read_head($project);
+ if (defined $file_name) {
+ my $base = $hash_base || $hash;
+ $hash = git_get_hash_by_path($base, $file_name, "tree");
+ }
+ if (!defined $hash_base) {
+ $hash_base = $hash;
+ }
+ }
+ $/ = "\0";
+ open my $fd, "-|", $GIT, "ls-tree", '-z', $hash
+ or die_error(undef, "Open git-ls-tree failed.");
+ my @entries = map { chomp; $_ } <$fd>;
+ close $fd or die_error(undef, "Reading tree failed.");
+ $/ = "\n";
+
+ my $refs = read_info_ref();
+ my $ref = git_get_referencing($refs, $hash_base);
+ git_header_html();
+ my $base_key = "";
+ my $base = "";
+ if (defined $hash_base && (my %co = git_read_commit($hash_base))) {
+ $base_key = ";hb=$hash_base";
+ git_page_nav('tree','', $hash_base);
+ git_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
+ } else {
+ print "<div class=\"page_nav\">\n";
+ print "<br/><br/></div>\n";
+ print "<div class=\"title\">$hash</div>\n";
+ }
+ if (defined $file_name) {
+ $base = esc_html("$file_name/");
+ }
+ git_print_page_path($file_name);
+ print "<div class=\"page_body\">\n";
+ print "<table cellspacing=\"0\">\n";
+ my $alternate = 0;
+ foreach my $line (@entries) {
+ #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c'
+ $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/;
+ my $t_mode = $1;
+ my $t_type = $2;
+ my $t_hash = $3;
+ my $t_name = validate_input($4);
+ if ($alternate) {
+ print "<tr class=\"dark\">\n";
+ } else {
+ print "<tr class=\"light\">\n";
+ }
+ $alternate ^= 1;
+ print "<td class=\"mode\">" . mode_str($t_mode) . "</td>\n";
+ if ($t_type eq "blob") {
+ print "<td class=\"list\">" .
+ $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_plain;h=$t_hash;f=$base$t_name")}, "raw") .
+ "</td>\n";
+ } elsif ($t_type eq "tree") {
+ print "<td class=\"list\">" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, esc_html($t_name)) .
+ "</td>\n" .
+ "<td class=\"link\">" .
+ $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$t_hash$base_key;f=$base$t_name")}, "tree") .
+ " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash_base;f=$base$t_name")}, "history") .
+ "</td>\n";
+ }
+ print "</tr>\n";
+ }
+ print "</table>\n" .
+ "</div>";
+ git_footer_html();
+}
+
+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.");
+ my @revlist = map { chomp; $_ } <$fd>;
+ close $fd or die_error(undef, "Reading 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";
+ print "<channel>\n";
+ print "<title>$project</title>\n".
+ "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n".
+ "<description>$project log</description>\n".
+ "<language>en</language>\n";