->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") . " | " . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); print "\n" . "\n"; } if (defined $extra) { print "\n" . "$extra\n" . "\n"; } print "\n"; } ## ====================================================================== ## ====================================================================== ## actions sub git_project_list { my $order = $input_params{'order'}; if (defined $order && $order !~ m/none|project|descr|owner|age/) { die_error(400, "Unknown order parameter"); } my @list = git_get_projects_list($project_filter, $strict_export); if (!@list) { die_error(404, "No projects found"); } git_header_html(); if (defined $home_text && -f $home_text) { print "
\n"; insert_file($home_text); print "
\n"; } git_project_search_form($searchtext, $search_use_regexp); git_project_list_body(\@list, $order); git_footer_html(); } sub git_forks { my $order = $input_params{'order'}; if (defined $order && $order !~ m/none|project|descr|owner|age/) { die_error(400, "Unknown order parameter"); } my $filter = $project; $filter =~ s/\.git$//; my @list = git_get_projects_list($filter); if (!@list) { die_error(404, "No forks found"); } git_header_html(); git_print_page_nav('',''); git_print_header_div('summary', "$project forks"); git_project_list_body(\@list, $order); git_footer_html(); } sub git_project_index { my @projects = git_get_projects_list($project_filter, $strict_export); if (!@projects) { die_error(404, "No projects found"); } print $cgi->header( -type => 'text/plain', -charset => 'utf-8', -content_disposition => 'inline; filename="index.aux"'); foreach my $pr (@projects) { if (!exists $pr->{'owner'}) { $pr->{'owner'} = git_get_project_owner("$pr->{'path'}"); } my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'}); # quote as in CGI::Util::encode, but keep the slash, and use '+' for ' ' $path =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg; $owner =~ s/([^a-zA-Z0-9_.\-\/ ])/sprintf("%%%02X", ord($1))/eg; $path =~ s/ /\+/g; $owner =~ s/ /\+/g; print "$path $owner\n"; } } sub git_summary { my $descr = git_get_project_description($project) || "none"; my %co = parse_commit("HEAD"); my %cd = %co ? parse_date($co{'committer_epoch'}, $co{'committer_tz'}) : (); my $head = $co{'id'}; my $remote_heads = gitweb_check_feature('remote_heads'); my $owner = git_get_project_owner($project); my $refs = git_get_references(); # These get_*_list functions return one more to allow us to see if # there are more ... my @taglist = git_get_tags_list(16); my @headlist = git_get_heads_list(16); my %remotedata = $remote_heads ? git_get_remotes_list() : (); my @forklist; my $check_forks = gitweb_check_feature('forks'); if ($check_forks) { # find forks of a project my $filter = $project; $filter =~ s/\.git$//; @forklist = git_get_projects_list($filter); # filter out forks of forks @forklist = filter_forks_from_projects_list(\@forklist) if (@forklist); } git_header_html(); git_print_page_nav('summary','', $head); print "
 
\n"; print "\n" . "\n"; if ($owner and not $omit_owner) { print "\n"; } if (defined $cd{'rfc2822'}) { print "" . "\n"; } # use per project git URL list in $projectroot/$project/cloneurl # or make project git URL from git base URL and project name my $url_tag = "URL"; my @url_list = git_get_project_url_list($project); @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; foreach my $git_url (@url_list) { next unless $git_url; print format_repo_url($url_tag, $git_url); $url_tag = ""; } # Tag cloud my $show_ctags = gitweb_check_feature('ctags'); if ($show_ctags) { my $ctags = git_get_project_ctags($project); if (%$ctags) { # without ability to add tags, don't show if there are none my $cloud = git_populate_project_tagcloud($ctags); print "" . "" . "" . "\n"; } } print "
description" . esc_html($descr) . "
owner" . esc_html($owner) . "
last change".format_timestamp_html(\%cd)."
content tags".git_show_project_tagcloud($cloud, 48)."
\n"; # If XSS prevention is on, we don't include README.html. # TODO: Allow a readme in some safe format. if (!$prevent_xss && -s "$projectroot/$project/README.html") { print "
readme
\n" . "
\n"; insert_file("$projectroot/$project/README.html"); print "\n
\n"; # class="readme" } # we need to request one more than 16 (0..15) to check if # those 16 are all my @commitlist = $head ? parse_commits($head, 17) : (); if (@commitlist) { git_print_header_div('shortlog'); git_shortlog_body(\@commitlist, 0, 15, $refs, $#commitlist <= 15 ? undef : $cgi->a({-href => href(action=>"shortlog")}, "...")); } if (@taglist) { git_print_header_div('tags'); git_tags_body(\@taglist, 0, 15, $#taglist <= 15 ? undef : $cgi->a({-href => href(action=>"tags")}, "...")); } if (@headlist) { git_print_header_div('heads'); git_heads_body(\@headlist, $head, 0, 15, $#headlist <= 15 ? undef : $cgi->a({-href => href(action=>"heads")}, "...")); } if (%remotedata) { git_print_header_div('remotes'); git_remotes_body(\%remotedata, 15, $head); } if (@forklist) { git_print_header_div('forks'); git_project_list_body(\@forklist, 'age', 0, 15, $#forklist <= 15 ? undef : $cgi->a({-href => href(action=>"forks")}, "..."), 'no_header'); } git_footer_html(); } sub git_tag { my %tag = parse_tag($hash); if (! %tag) { die_error(404, "Unknown tag object"); } my $head = git_get_head_hash($project); git_header_html(); git_print_page_nav('','', $head,undef,$head); git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "
\n" . "\n" . "\n" . "\n" . "\n" . "\n" . "\n"; if (defined($tag{'author'})) { git_print_authorship_rows(\%tag, 'author'); } print "
object" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'object'}) . "" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'type'}) . "
\n\n" . "
\n"; print "
"; my $comment = $tag{'comment'}; foreach my $line (@$comment) { chomp $line; print esc_html($line, -nbsp=>1) . "
\n"; } print "
\n"; git_footer_html(); } sub git_blame_common { my $format = shift || 'porcelain'; if ($format eq 'porcelain' && $input_params{'javascript'}) { $format = 'incremental'; $action = 'blame_incremental'; # for page title etc } # permissions gitweb_check_feature('blame') or die_error(403, "Blame view not allowed"); # error checking die_error(400, "No file name given") unless $file_name; $hash_base ||= git_get_head_hash($project); die_error(404, "Couldn't find base commit") unless $hash_base; my %co = parse_commit($hash_base) or die_error(404, "Commit not found"); my $ftype = "blob"; if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") or die_error(404, "Error looking up file"); } else { $ftype = git_get_type($hash); if ($ftype !~ "blob") { die_error(400, "Object is not a blob"); } } my $fd; if ($format eq 'incremental') { # get file contents (as base) open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash or die_error(500, "Open git-cat-file failed"); } elsif ($format eq 'data') { # run git-blame --incremental open $fd, "-|", git_cmd(), "blame", "--incremental", $hash_base, "--", $file_name or die_error(500, "Open git-blame --incremental failed"); } else { # run git-blame --porcelain open $fd, "-|", git_cmd(), "blame", '-p', $hash_base, '--', $file_name or die_error(500, "Open git-blame --porcelain failed"); } binmode $fd, ':utf8'; # incremental blame data returns early if ($format eq 'data') { print $cgi->header( -type=>"text/plain", -charset => "utf-8", -status=> "200 OK"); local $| = 1; # output autoflush while (my $line = <$fd>) { print to_utf8($line); } close $fd or print "ERROR $!\n"; print 'END'; if (defined $t0 && gitweb_check_feature('timed')) { print ' '. tv_interval($t0, [ gettimeofday() ]). ' '.$number_of_git_cmds; } print "\n"; return; } # page header git_header_html(); my $formats_nav = $cgi->a({-href => href(action=>"blob", -replay=>1)}, "blob") . " | "; if ($format eq 'incremental') { $formats_nav .= $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)}, "blame") . " (non-incremental)"; } else { $formats_nav .= $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)}, "blame") . " (incremental)"; } $formats_nav .= " | " . $cgi->a({-href => href(action=>"history", -replay=>1)}, "history") . " | " . $cgi->a({-href => href(action=>$action, file_name=>$file_name)}, "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); # page body if ($format eq 'incremental') { print "\n"; print qq!
\n!; } print qq!
\n!; print qq!
... / ...
\n! if ($format eq 'incremental'); print qq!\n!. #qq!\n!. qq!\n!. qq!\n!. qq!\n!. qq!\n!; my @rev_color = qw(light dark); my $num_colors = scalar(@rev_color); my $current_color = 0; if ($format eq 'incremental') { my $color_class = $rev_color[$current_color]; #contents of a file my $linenr = 0; LINE: while (my $line = <$fd>) { chomp $line; $linenr++; print qq!!. qq!!. qq!!; print qq!\n"; print qq!\n!; } } else { # porcelain, i.e. ordinary blame my %metainfo = (); # saves information about commits # blame data LINE: while (my $line = <$fd>) { chomp $line; # the header: [] # no for subsequent lines in group of lines my ($full_rev, $orig_lineno, $lineno, $group_size) = ($line =~ /^($oid_regex) (\d+) (\d+)(?: (\d+))?$/); if (!exists $metainfo{$full_rev}) { $metainfo{$full_rev} = { 'nprevious' => 0 }; } my $meta = $metainfo{$full_rev}; my $data; while ($data = <$fd>) { chomp $data; last if ($data =~ s/^\t//); # contents of line if ($data =~ /^(\S+)(?: (.*))?$/) { $meta->{$1} = $2 unless exists $meta->{$1}; } if ($data =~ /^previous /) { $meta->{'nprevious'}++; } } my $short_rev = substr($full_rev, 0, 8); my $author = $meta->{'author'}; my %date = parse_date($meta->{'author-time'}, $meta->{'author-tz'}); my $date = $date{'iso-tz'}; if ($group_size) { $current_color = ($current_color + 1) % $num_colors; } my $tr_class = $rev_color[$current_color]; $tr_class .= ' boundary' if (exists $meta->{'boundary'}); $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); print "\n"; if ($group_size) { print "\n"; } # 'previous' if (exists $meta->{'previous'} && $meta->{'previous'} =~ /^($oid_regex) (.*)$/) { $meta->{'parent'} = $1; $meta->{'file_parent'} = unquote($2); } my $linenr_commit = exists($meta->{'parent'}) ? $meta->{'parent'} : $full_rev; my $linenr_filename = exists($meta->{'file_parent'}) ? $meta->{'file_parent'} : unquote($meta->{'filename'}); my $blamed = href(action => 'blame', file_name => $linenr_filename, hash_base => $linenr_commit); print ""; print "\n"; print "\n"; } # end while } # footer print "\n". "
CommitLineData
!. qq!$linenr! . esc_html($line) . "
1); print ">"; print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($short_rev)); if ($group_size >= 2) { my @author_initials = ($author =~ /\b([[:upper:]])\B/g); if (@author_initials) { print "
" . esc_html(join('', @author_initials)); # or join('.', ...) } } print "
"; print $cgi->a({ -href => "$blamed#l$orig_lineno", -class => "linenr" }, esc_html($lineno)); print "" . esc_html($data) . "
\n"; # class="blame" print "
\n"; # class="blame_body" close $fd or print "Reading blob failed\n"; git_footer_html(); } sub git_blame { git_blame_common(); } sub git_blame_incremental { git_blame_common('incremental'); } sub git_blame_data { git_blame_common('data'); } sub git_tags { my $head = git_get_head_hash($project); git_header_html(); git_print_page_nav('','', $head,undef,$head,format_ref_views('tags')); git_print_header_div('summary', $project); my @tagslist = git_get_tags_list(); if (@tagslist) { git_tags_body(\@tagslist); } git_footer_html(); } sub git_heads { my $head = git_get_head_hash($project); git_header_html(); git_print_page_nav('','', $head,undef,$head,format_ref_views('heads')); git_print_header_div('summary', $project); my @headslist = git_get_heads_list(); if (@headslist) { git_heads_body(\@headslist, $head); } git_footer_html(); } # used both for single remote view and for list of all the remotes sub git_remotes { gitweb_check_feature('remote_heads') or die_error(403, "Remote heads view is disabled"); my $head = git_get_head_hash($project); my $remote = $input_params{'hash'}; my $remotedata = git_get_remotes_list($remote); die_error(500, "Unable to get remote information") unless defined $remotedata; unless (%$remotedata) { die_error(404, defined $remote ? "Remote $remote not found" : "No remotes found"); } git_header_html(undef, undef, -action_extra => $remote); git_print_page_nav('', '', $head, undef, $head, format_ref_views($remote ? '' : 'remotes')); fill_remote_heads($remotedata); if (defined $remote) { git_print_header_div('remotes', "$remote remote for $project"); git_remote_block($remote, $remotedata->{$remote}, undef, $head); } else { git_print_header_div('summary', "$project remotes"); git_remotes_body($remotedata, undef, $head); } git_footer_html(); } sub git_blob_plain { my $type = shift; my $expires; if (!defined $hash) { if (defined $file_name) { my $base = $hash_base || git_get_head_hash($project); $hash = git_get_hash_by_path($base, $file_name, "blob") or die_error(404, "Cannot find file"); } else { die_error(400, "No file name defined"); } } elsif ($hash =~ m/^$oid_regex$/) { # blobs defined by non-textual hash id's can be cached $expires = "+1d"; } open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(500, "Open git-cat-file blob '$hash' failed"); # content-type (can include charset) $type = blob_contenttype($fd, $file_name, $type); # "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'; } # With XSS prevention on, blobs of all types except a few known safe # ones are served with "Content-Disposition: attachment" to make sure # they don't run in our security domain. For certain image types, # blob view writes an tag referring to blob_plain view, and we # want to be sure not to break that by serving the image as an # attachment (though Firefox 3 doesn't seem to care). my $sandbox = $prevent_xss && $type !~ m!^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)!; # serve text/* as text/plain if ($prevent_xss && ($type =~ m!^text/[a-z]+\b(.*)$! || ($type =~ m!^[a-z]+/[a-z]\+xml\b(.*)$! && -T $fd))) { my $rest = $1; $rest = defined $rest ? $rest : ''; $type = "text/plain$rest"; } print $cgi->header( -type => $type, -expires => $expires, -content_disposition => ($sandbox ? 'attachment' : 'inline') . '; filename="' . $save_as . '"'); local $/ = undef; local *FCGI::Stream::PRINT = $FCGI_Stream_PRINT_raw; binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi close $fd; } sub git_blob { my $expires; if (!defined $hash) { if (defined $file_name) { my $base = $hash_base || git_get_head_hash($project); $hash = git_get_hash_by_path($base, $file_name, "blob") or die_error(404, "Cannot find file"); } else { die_error(400, "No file name defined"); } } elsif ($hash =~ m/^$oid_regex$/) { # blobs defined by non-textual hash id's can be cached $expires = "+1d"; } my $have_blame = gitweb_check_feature('blame'); open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(500, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); # use 'blob_plain' (aka 'raw') view for files that cannot be displayed if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) { close $fd; return git_blob_plain($mimetype); } # we can have blame only for text/* mimetype $have_blame &&= ($mimetype =~ m!^text/!); my $highlight = gitweb_check_feature('highlight'); my $syntax = guess_file_syntax($highlight, $file_name); $fd = run_highlighter($fd, $highlight, $syntax); git_header_html(undef, $expires); my $formats_nav = ''; if (defined $hash_base && (my %co = parse_commit($hash_base))) { if (defined $file_name) { if ($have_blame) { $formats_nav .= $cgi->a({-href => href(action=>"blame", -replay=>1)}, "blame") . " | "; } $formats_nav .= $cgi->a({-href => href(action=>"history", -replay=>1)}, "history") . " | " . $cgi->a({-href => href(action=>"blob_plain", -replay=>1)}, "raw") . " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, "HEAD"); } else { $formats_nav .= $cgi->a({-href => href(action=>"blob_plain", -replay=>1)}, "raw"); } git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "
\n" . "

\n" . "
".esc_html($hash)."
\n"; } git_print_page_path($file_name, "blob", $hash_base); print "
\n"; if ($mimetype =~ m!^image/!) { print qq!!.esc_attr($file_name).qq!$hash, hash_base=>$hash_base, file_name=>$file_name)) . qq!" />\n!; } else { my $nr; while (my $line = <$fd>) { chomp $line; $nr++; $line = untabify($line);