printf qq!
%4i %s
\n!, $nr, esc_attr(href(-replay => 1)), $nr, $nr, $highlight ? sanitize($line) : esc_html($line, -nbsp=>1); } } close $fd or print "Reading blob failed.\n"; print ""; git_footer_html(); } sub git_tree { if (!defined $hash_base) { $hash_base = "HEAD"; } if (!defined $hash) { if (defined $file_name) { $hash = git_get_hash_by_path($hash_base, $file_name, "tree"); } else { $hash = $hash_base; } } die_error(404, "No such tree") unless defined($hash); my $show_sizes = gitweb_check_feature('show-sizes'); my $have_blame = gitweb_check_feature('blame'); my @entries = (); { local $/ = "\0"; open my $fd, "-|", git_cmd(), "ls-tree", '-z', ($show_sizes ? '-l' : ()), @extra_options, $hash or die_error(500, "Open git-ls-tree failed"); @entries = map { chomp; $_ } <$fd>; close $fd or die_error(404, "Reading tree failed"); } my $refs = git_get_references(); my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $basedir = ''; if (defined $hash_base && (my %co = parse_commit($hash_base))) { my @views_nav = (); if (defined $file_name) { push @views_nav, $cgi->a({-href => href(action=>"history", -replay=>1)}, "history"), $cgi->a({-href => href(action=>"tree", hash_base=>"HEAD", file_name=>$file_name)}, "HEAD"), } my $snapshot_links = format_snapshot_links($hash); if (defined $snapshot_links) { # FIXME: Should be available when we have no hash base as well. push @views_nav, $snapshot_links; } git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { undef $hash_base; print "
\n"; print "

\n"; print "
".esc_html($hash)."
\n"; } if (defined $file_name) { $basedir = $file_name; if ($basedir ne '' && substr($basedir, -1) ne '/') { $basedir .= '/'; } git_print_page_path($file_name, 'tree', $hash_base); } print "
\n"; print "\n"; my $alternate = 1; # '..' (top directory) link if possible if (defined $hash_base && defined $file_name && $file_name =~ m![^/]+$!) { if ($alternate) { print "\n"; } else { print "\n"; } $alternate ^= 1; my $up = $file_name; $up =~ s!/?[^/]+$!!; undef $up unless $up; # based on git_print_tree_entry print '\n"; print ''."\n" if $show_sizes; print '\n"; print "\n"; print "\n"; } foreach my $line (@entries) { my %t = parse_ls_tree_line($line, -z => 1, -l => $show_sizes); if ($alternate) { print "\n"; } else { print "\n"; } $alternate ^= 1; git_print_tree_entry(\%t, $basedir, $hash_base, $have_blame); print "\n"; } print "
' . mode_str('040000') . " '; print $cgi->a({-href => href(action=>"tree", hash_base=>$hash_base, file_name=>$up)}, ".."); print "
\n" . "
"; git_footer_html(); } sub sanitize_for_filename { my $name = shift; $name =~ s!/!-!g; $name =~ s/[^[:alnum:]_.-]//g; return $name; } sub snapshot_name { my ($project, $hash) = @_; # path/to/project.git -> project # path/to/project/.git -> project my $name = to_utf8($project); $name =~ s,([^/])/*\.git$,$1,; $name = sanitize_for_filename(basename($name)); my $ver = $hash; if ($hash =~ /^[0-9a-fA-F]+$/) { # shorten SHA-1 hash my $full_hash = git_get_full_hash($project, $hash); if ($full_hash =~ /^$hash/ && length($hash) > 7) { $ver = git_get_short_hash($project, $hash); } } elsif ($hash =~ m!^refs/tags/(.*)$!) { # tags don't need shortened SHA-1 hash $ver = $1; } else { # branches and other need shortened SHA-1 hash my $strip_refs = join '|', map { quotemeta } get_branch_refs(); if ($hash =~ m!^refs/($strip_refs|remotes)/(.*)$!) { my $ref_dir = (defined $1) ? $1 : ''; $ver = $2; $ref_dir = sanitize_for_filename($ref_dir); # for refs neither in heads nor remotes we want to # add a ref dir to archive name if ($ref_dir ne '' and $ref_dir ne 'heads' and $ref_dir ne 'remotes') { $ver = $ref_dir . '-' . $ver; } } $ver .= '-' . git_get_short_hash($project, $hash); } # special case of sanitization for filename - we change # slashes to dots instead of dashes # in case of hierarchical branch names $ver =~ s!/!.!g; $ver =~ s/[^[:alnum:]_.-]//g; # name = project-version_string $name = "$name-$ver"; return wantarray ? ($name, $name) : $name; } sub exit_if_unmodified_since { my ($latest_epoch) = @_; our $cgi; my $if_modified = $cgi->http('IF_MODIFIED_SINCE'); if (defined $if_modified) { my $since; if (eval { require HTTP::Date; 1; }) { $since = HTTP::Date::str2time($if_modified); } elsif (eval { require Time::ParseDate; 1; }) { $since = Time::ParseDate::parsedate($if_modified, GMT => 1); } if (defined $since && $latest_epoch <= $since) { my %latest_date = parse_date($latest_epoch); print $cgi->header( -last_modified => $latest_date{'rfc2822'}, -status => '304 Not Modified'); goto DONE_GITWEB; } } } sub git_snapshot { my $format = $input_params{'snapshot_format'}; if (!@snapshot_fmts) { die_error(403, "Snapshots not allowed"); } # default to first supported snapshot format $format ||= $snapshot_fmts[0]; if ($format !~ m/^[a-z0-9]+$/) { die_error(400, "Invalid snapshot format parameter"); } elsif (!exists($known_snapshot_formats{$format})) { die_error(400, "Unknown snapshot format"); } elsif ($known_snapshot_formats{$format}{'disabled'}) { die_error(403, "Snapshot format not allowed"); } elsif (!grep($_ eq $format, @snapshot_fmts)) { die_error(403, "Unsupported snapshot format"); } my $type = git_get_type("$hash^{}"); if (!$type) { die_error(404, 'Object does not exist'); } elsif ($type eq 'blob') { die_error(400, 'Object is not a tree-ish'); } my ($name, $prefix) = snapshot_name($project, $hash); my $filename = "$name$known_snapshot_formats{$format}{'suffix'}"; my %co = parse_commit($hash); exit_if_unmodified_since($co{'committer_epoch'}) if %co; my $cmd = quote_command( git_cmd(), 'archive', "--format=$known_snapshot_formats{$format}{'format'}", "--prefix=$prefix/", $hash); if (exists $known_snapshot_formats{$format}{'compressor'}) { $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}}); } $filename =~ s/(["\\])/\\$1/g; my %latest_date; if (%co) { %latest_date = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); } print $cgi->header( -type => $known_snapshot_formats{$format}{'type'}, -content_disposition => 'inline; filename="' . $filename . '"', %co ? (-last_modified => $latest_date{'rfc2822'}) : (), -status => '200 OK'); open my $fd, "-|", $cmd or die_error(500, "Execute git-archive failed"); binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi close $fd; } sub git_log_generic { my ($fmt_name, $body_subr, $base, $parent, $file_name, $file_hash) = @_; my $head = git_get_head_hash($project); if (!defined $base) { $base = $head; } if (!defined $page) { $page = 0; } my $refs = git_get_references(); my $commit_hash = $base; if (defined $parent) { $commit_hash = "$parent..$base"; } my @commitlist = parse_commits($commit_hash, 101, (100 * $page), defined $file_name ? ($file_name, "--full-history") : ()); my $ftype; if (!defined $file_hash && defined $file_name) { # some commits could have deleted file in question, # and not have it in tree, but one of them has to have it for (my $i = 0; $i < @commitlist; $i++) { $file_hash = git_get_hash_by_path($commitlist[$i]{'id'}, $file_name); last if defined $file_hash; } } if (defined $file_hash) { $ftype = git_get_type($file_hash); } if (defined $file_name && !defined $ftype) { die_error(500, "Unknown type of object"); } my %co; if (defined $file_name) { %co = parse_commit($base) or die_error(404, "Unknown commit object"); } my $paging_nav = format_paging_nav($fmt_name, $page, $#commitlist >= 100); my $next_link = ''; if ($#commitlist >= 100) { $next_link = $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); } my $patch_max = gitweb_get_feature('patches'); if ($patch_max && !defined $file_name) { if ($patch_max < 0 || @commitlist <= $patch_max) { $paging_nav .= " ⋅ " . $cgi->a({-href => href(action=>"patches", -replay=>1)}, "patches"); } } git_header_html(); git_print_page_nav($fmt_name,'', $hash,$hash,$hash, $paging_nav); if (defined $file_name) { git_print_header_div('commit', esc_html($co{'title'}), $base); } else { git_print_header_div('summary', $project) } git_print_page_path($file_name, $ftype, $hash_base) if (defined $file_name); $body_subr->(\@commitlist, 0, 99, $refs, $next_link, $file_name, $file_hash, $ftype); git_footer_html(); } sub git_log { git_log_generic('log', \&git_log_body, $hash, $hash_parent); } sub git_commit { $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash) or die_error(404, "Unknown commit object"); my $parent = $co{'parent'}; my $parents = $co{'parents'}; # listref # we need to prepare $formats_nav before any parameter munging my $formats_nav; if (!defined $parent) { # --root commitdiff $formats_nav .= '(initial)'; } elsif (@$parents == 1) { # single parent commit $formats_nav .= '(parent: ' . $cgi->a({-href => href(action=>"commit", hash=>$parent)}, esc_html(substr($parent, 0, 7))) . ')'; } else { # merge commit $formats_nav .= '(merge: ' . join(' ', map { $cgi->a({-href => href(action=>"commit", hash=>$_)}, esc_html(substr($_, 0, 7))); } @$parents ) . ')'; } if (gitweb_check_feature('patches') && @$parents <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); } if (!defined $parent) { $parent = "--root"; } my @difftree; open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", @diff_opts, (@$parents <= 1 ? $parent : '-c'), $hash, "--" or die_error(500, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(404, "Reading git-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 = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); git_header_html(undef, $expires); git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, $formats_nav); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); } else { git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "
\n" . "\n"; git_print_authorship_rows(\%co); print "\n"; print "" . "" . "" . "" . "\n"; foreach my $par (@$parents) { print "" . "" . "" . "" . "\n"; } print "
commit$co{'id'}
tree" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash), class => "list"}, $co{'tree'}) . "" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, "tree"); my $snapshot_links = format_snapshot_links($hash); if (defined $snapshot_links) { print " | " . $snapshot_links; } print "
parent" . $cgi->a({-href => href(action=>"commit", hash=>$par), class => "list"}, $par) . "" . $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "diff") . "
". "
\n"; print "
\n"; git_print_log($co{'comment'}); print "
\n"; git_difftree_body(\@difftree, $hash, @$parents); git_footer_html(); } sub git_object { # object is defined by: # - hash or hash_base alone # - hash_base and file_name my $type; # - hash or hash_base alone if ($hash || ($hash_base && !defined $file_name)) { my $object_id = $hash || $hash_base; open my $fd, "-|", quote_command( git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null' or die_error(404, "Object does not exist"); $type = <$fd>; defined $type && chomp $type; close $fd or die_error(404, "Object does not exist"); # - hash_base and file_name } elsif ($hash_base && defined $file_name) { $file_name =~ s,/+$,,; system(git_cmd(), "cat-file", '-e', $hash_base) == 0 or die_error(404, "Base object does not exist"); # here errors should not happen open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name or die_error(500, "Open git-ls-tree failed"); my $line = <$fd>; close $fd; #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) { die_error(404, "File or directory for given base does not exist"); } $type = $2; $hash = $3; } else { die_error(400, "Not enough information to find object"); } print $cgi->redirect(-uri => href(action=>$type, -full=>1, hash=>$hash, hash_base=>$hash_base, file_name=>$file_name), -status => '302 Found'); } sub git_blobdiff { my $format = shift || 'html'; my $diff_style = $input_params{'diff_style'} || 'inline'; my $fd; my @difftree; my %diffinfo; my $expires; # preparing $fd and %diffinfo for git_patchset_body # new style URI if (defined $hash_base && defined $hash_parent_base) { if (defined $file_name) { # read raw output open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, "--", (defined $file_parent ? $file_parent : ()), $file_name or die_error(500, "Open git-diff-tree failed"); @difftree = map { chomp; $_ } <$fd>; close $fd or die_error(404, "Reading git-diff-tree failed"); @difftree or die_error(404, "Blob diff not found"); } elsif (defined $hash && $hash =~ /[0-9a-fA-F]{40}/) { # try to find filename from $hash # read filtered raw output open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $hash_parent_base, $hash_base, "--" or die_error(500, "Open git-diff-tree failed"); @difftree = # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' # $hash == to_id grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ } map { chomp; $_ } <$fd>; close $fd or die_error(404, "Reading git-diff-tree failed"); @difftree or die_error(404, "Blob diff not found"); } else { die_error(400, "Missing one of the blob diff parameters"); } if (@difftree > 1) { die_error(400, "Ambiguous blob diff specification"); } %diffinfo = parse_difftree_raw_line($difftree[0]); $file_parent ||= $diffinfo{'from_file'} || $file_name; $file_name ||= $diffinfo{'to_file'}; $hash_parent ||= $diffinfo{'from_id'}; $hash ||= $diffinfo{'to_id'}; # non-textual hash id's can be cached if ($hash_base =~ m/^[0-9a-fA-F]{40}$/ && $hash_parent_base =~ m/^[0-9a-fA-F]{40}$/) { $expires = '+1d'; } # open patch output open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, '-p', ($format eq 'html' ? "--full-index" : ()), $hash_parent_base, $hash_base, "--", (defined $file_parent ? $file_parent : ()), $file_name or die_error(500, "Open git-diff-tree failed"); } # old/legacy style URI -- not generated anymore since 1.4.3. if (!%diffinfo) { die_error('404 Not Found', "Missing one of the blob diff parameters") } # header if ($format eq 'html') { my $formats_nav = $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)}, "raw"); $formats_nav .= diff_style_nav($diff_style); git_header_html(undef, $expires); if (defined $hash_base && (my %co = parse_commit($hash_base))) { 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 "

$formats_nav
\n"; print "
".esc_html("$hash vs $hash_parent")."
\n"; } if (defined $file_name) { git_print_page_path($file_name, "blob", $hash_base); } else { print "
\n"; } } elsif ($format eq 'plain') { print $cgi->header( -type => 'text/plain', -charset => 'utf-8', -expires => $expires, -content_disposition => 'inline; filename="' . "$file_name" . '.patch"'); print "X-Git-Url: " . $cgi->self_url() . "\n\n"; } else { die_error(400, "Unknown blobdiff format"); } # patch if ($format eq 'html') { print "
\n"; git_patchset_body($fd, $diff_style, [ \%diffinfo ], $hash_base, $hash_parent_base); close $fd; print "
\n"; # class="page_body" git_footer_html(); } else { while (my $line = <$fd>) { $line =~ s!a/($hash|$hash_parent)!'a/'.esc_path($diffinfo{'from_file'})!eg; $line =~ s!b/($hash|$hash_parent)!'b/'.esc_path($diffinfo{'to_file'})!eg; print $line; last if $line =~ m!^\+\+\+!; } local $/ = undef; print <$fd>; close $fd; } } sub git_blobdiff_plain { git_blobdiff('plain'); } # assumes that it is added as later part of already existing navigation, # so it returns "| foo | bar" rather than just "foo | bar" sub diff_style_nav { my ($diff_style, $is_combined) = @_; $diff_style ||= 'inline'; return "" if ($is_combined); my @styles = (inline => 'inline', 'sidebyside' => 'side by side'); my %styles = @styles; @styles = @styles[ map { $_ * 2 } 0..$#styles/2 ]; return join '', map { " | ".$_ } map { $_ eq $diff_style ? $styles{$_} : $cgi->a({-href => href(-replay=>1, diff_style => $_)}, $styles{$_}) } @styles; } sub git_commitdiff { my %params = @_; my $format = $params{-format} || 'html'; my $diff_style = $input_params{'diff_style'} || 'inline'; my ($patch_max) = gitweb_get_feature('patches'); if ($format eq 'patch') { die_error(403, "Patch view not allowed") unless $patch_max; } $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash) or die_error(404, "Unknown commit object"); # choose format for commitdiff for merge if (! defined $hash_parent && @{$co{'parents'}} > 1) { $hash_parent = '--cc'; } # we need to prepare $formats_nav before almost any parameter munging my $formats_nav; if ($format eq 'html') { $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)}, "raw"); if ($patch_max && @{$co{'parents'}} <= 1) { $formats_nav .= " | " . $cgi->a({-href => href(action=>"patch", -replay=>1)}, "patch"); } $formats_nav .= diff_style_nav($diff_style, @{$co{'parents'}} > 1); if (defined $hash_parent && $hash_parent ne '-c' && $hash_parent ne '--cc') { # commitdiff with two commits given my $hash_parent_short = $hash_parent; if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) { $hash_parent_short = substr($hash_parent, 0, 7); } $formats_nav .= ' (from'; for (my $i = 0; $i < @{$co{'parents'}}; $i++) { if ($co{'parents'}[$i] eq $hash_parent) { $formats_nav .= ' parent ' . ($i+1); last; } } $formats_nav .= ': ' . $cgi->a({-href => href(-replay=>1, hash=>$hash_parent, hash_base=>undef)}, esc_html($hash_parent_short)) . ')'; } elsif (!$co{'parent'}) { # --root commitdiff $formats_nav .= ' (initial)'; } elsif (scalar @{$co{'parents'}} == 1) { # single parent commit $formats_nav .= ' (parent: ' . $cgi->a({-href => href(-replay=>1, hash=>$co{'parent'}, hash_base=>undef)}, esc_html(substr($co{'parent'}, 0, 7))) . ')'; } else { # merge commit if ($hash_parent eq '--cc') { $formats_nav .= ' | ' . $cgi->a({-href => href(-replay=>1, hash=>$hash, hash_parent=>'-c')}, 'combined'); } else { # $hash_parent eq '-c' $formats_nav .= ' | ' . $cgi->a({-href => href(-replay=>1, hash=>$hash, hash_parent=>'--cc')}, 'compact'); } $formats_nav .= ' (merge: ' . join(' ', map { $cgi->a({-href => href(-replay=>1, hash=>$_, hash_base=>undef)}, esc_html(substr($_, 0, 7))); } @{$co{'parents'}} ) . ')'; } } my $hash_parent_param = $hash_parent; if (!defined $hash_parent_param) { # --cc for multiple parents, --root for parentless $hash_parent_param = @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root'; } # read commitdiff my $fd; my @difftree; if ($format eq 'html') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, "--no-commit-id", "--patch-with-raw", "--full-index", $hash_parent_param, $hash, "--" or die_error(500, "Open git-diff-tree failed"); while (my $line = <$fd>) { chomp $line; # empty line ends raw part of diff-tree output last unless $line; push @difftree, scalar parse_difftree_raw_line($line); } } elsif ($format eq 'plain') { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, '-p', $hash_parent_param, $hash, "--" or die_error(500, "Open git-diff-tree failed"); } elsif ($format eq 'patch') { # For commit ranges, we limit the output to the number of # patches specified in the 'patches' feature. # For single commits, we limit the output to a single patch, # diverging from the git-format-patch default. my @commit_spec = (); if ($hash_parent) { if ($patch_max > 0) { push @commit_spec, "-$patch_max"; } push @commit_spec, '-n', "$hash_parent..$hash"; } else { if ($params{-single}) { push @commit_spec, '-1'; } else { if ($patch_max > 0) { push @commit_spec, "-$patch_max"; } push @commit_spec, "-n"; } push @commit_spec, '--root', $hash; } open $fd, "-|", git_cmd(), "format-patch", @diff_opts, '--encoding=utf8', '--stdout', @commit_spec or die_error(500, "Open git-format-patch failed"); } else { die_error(400, "Unknown commitdiff format"); } # non-textual hash id's can be cached my $expires; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } # write commit message if ($format eq 'html') { my $refs = git_get_references(); my $ref = format_ref_marker($refs, $co{'id'}); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "
\n" . "\n"; git_print_authorship_rows(\%co); print "
". "
\n"; print "
\n"; if (@{$co{'comment'}} > 1) { print "
\n"; git_print_log($co{'comment'}, -final_empty_line=> 1, -remove_title => 1); print "
\n"; # class="log" } } elsif ($format eq 'plain') { my $refs = git_get_references("tags"); my $tagname = git_get_rev_name_tags($hash); my $filename = basename($project) . "-$hash.patch"; print $cgi->header( -type => 'text/plain', -charset => 'utf-8', -expires => $expires, -content_disposition => 'inline; filename="' . "$filename" . '"'); my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); print "From: " . to_utf8($co{'author'}) . "\n"; print "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n"; print "Subject: " . to_utf8($co{'title'}) . "\n"; print "X-Git-Tag: $tagname\n" if $tagname; print "X-Git-Url: " . $cgi->self_url() . "\n\n"; foreach my $line (@{$co{'comment'}}) { print to_utf8($line) . "\n"; } print "---\n\n"; } elsif ($format eq 'patch') { my $filename = basename($project) . "-$hash.patch"; print $cgi->header( -type => 'text/plain', -charset => 'utf-8', -expires => $expires, -content_disposition => 'inline; filename="' . "$filename" . '"'); } # write patch if ($format eq 'html') { my $use_parents = !defined $hash_parent || $hash_parent eq '-c' || $hash_parent eq '--cc'; git_difftree_body(\@difftree, $hash, $use_parents ? @{$co{'parents'}} : $hash_parent); print "
\n"; git_patchset_body($fd, $diff_style, \@difftree, $hash, $use_parents ? @{$co{'parents'}} : $hash_parent); close $fd; print "
\n"; # class="page_body" git_footer_html(); } elsif ($format eq 'plain') { local $/ = undef; print <$fd>; close $fd or print "Reading git-diff-tree failed\n"; } elsif ($format eq 'patch') { local $/ = undef; print <$fd>; close $fd or print "Reading git-format-patch failed\n"; } } sub git_commitdiff_plain { git_commitdiff(-format => 'plain'); } # format-patch-style patches sub git_patch { git_commitdiff(-format => 'patch', -single => 1); } sub git_patches { git_commitdiff(-format => 'patch'); } sub git_history { git_log_generic('history', \&git_history_body, $hash_base, $hash_parent_base, $file_name, $hash); } sub git_search { $searchtype ||= 'commit'; # check if appropriate features are enabled gitweb_check_feature('search') or die_error(403, "Search is disabled"); if ($searchtype eq 'pickaxe') { # 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 gitweb_check_feature('pickaxe') or die_error(403, "Pickaxe search is disabled"); } if ($searchtype eq 'grep') { # grep search might be potentially CPU-intensive, too gitweb_check_feature('grep') or die_error(403, "Grep search is disabled"); } if (!defined $searchtext) { die_error(400, "Text field is empty"); } if (!defined $hash) { $hash = git_get_head_hash($project); } my %co = parse_commit($hash); if (!%co) { die_error(404, "Unknown commit object"); } if (!defined $page) { $page = 0; } if ($searchtype eq 'commit' || $searchtype eq 'author' || $searchtype eq 'committer') { git_search_message(%co); } elsif ($searchtype eq 'pickaxe') { git_search_changes(%co); } elsif ($searchtype eq 'grep') { git_search_files(%co); } e