Merge branch 'jn/gitweb-fastcgi'
authorJunio C Hamano <gitster@pobox.com>
Mon, 21 Jun 2010 13:02:42 +0000 (06:02 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 21 Jun 2010 13:02:42 +0000 (06:02 -0700)
* jn/gitweb-fastcgi:
gitweb: Run in FastCGI mode if gitweb script has .fcgi extension
gitweb: Add support for FastCGI, using CGI::Fast
gitweb: Put all per-connection code in run() subroutine

1  2 
gitweb/gitweb.perl
diff --combined gitweb/gitweb.perl
index 2365311d94e78b894b42526c3181e45f23ce14ac,e39ef866f02d07487d36f9198be8bad5c8aeeabc..9e5e0bf3ee6a86df6329da145f7f597e7abf491f
@@@ -11,7 -11,7 +11,7 @@@ use strict
  use warnings;
  use CGI qw(:standard :escapeHTML -nosticky);
  use CGI::Util qw(unescape);
 -use CGI::Carp qw(fatalsToBrowser);
 +use CGI::Carp qw(fatalsToBrowser set_message);
  use Encode;
  use Fcntl ':mode';
  use File::Find qw();
@@@ -28,34 -28,42 +28,42 @@@ BEGIN 
        CGI->compile() if $ENV{'MOD_PERL'};
  }
  
- our $cgi = new CGI;
  our $version = "++GIT_VERSION++";
- our $my_url = $cgi->url();
- our $my_uri = $cgi->url(-absolute => 1);
  
- # Base URL for relative URLs in gitweb ($logo, $favicon, ...),
- # needed and used only for URLs with nonempty PATH_INFO
our $base_url = $my_url;
+ our ($my_url, $my_uri, $base_url, $path_info, $home_link);
+ sub evaluate_uri {
      our $cgi;
  
- # When the script is used as DirectoryIndex, the URL does not contain the name
- # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
- # have to do it ourselves. We make $path_info global because it's also used
- # later on.
- #
- # Another issue with the script being the DirectoryIndex is that the resulting
- # $my_url data is not the full script URL: this is good, because we want
- # generated links to keep implying the script name if it wasn't explicitly
- # indicated in the URL we're handling, but it means that $my_url cannot be used
- # as base URL.
- # Therefore, if we needed to strip PATH_INFO, then we know that we have
- # to build the base URL ourselves:
- our $path_info = $ENV{"PATH_INFO"};
- if ($path_info) {
-       if ($my_url =~ s,\Q$path_info\E$,, &&
-           $my_uri =~ s,\Q$path_info\E$,, &&
-           defined $ENV{'SCRIPT_NAME'}) {
-               $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+       our $my_url = $cgi->url();
+       our $my_uri = $cgi->url(-absolute => 1);
+       # Base URL for relative URLs in gitweb ($logo, $favicon, ...),
+       # needed and used only for URLs with nonempty PATH_INFO
+       our $base_url = $my_url;
+       # When the script is used as DirectoryIndex, the URL does not contain the name
+       # of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we
+       # have to do it ourselves. We make $path_info global because it's also used
+       # later on.
+       #
+       # Another issue with the script being the DirectoryIndex is that the resulting
+       # $my_url data is not the full script URL: this is good, because we want
+       # generated links to keep implying the script name if it wasn't explicitly
+       # indicated in the URL we're handling, but it means that $my_url cannot be used
+       # as base URL.
+       # Therefore, if we needed to strip PATH_INFO, then we know that we have
+       # to build the base URL ourselves:
+       our $path_info = $ENV{"PATH_INFO"};
+       if ($path_info) {
+               if ($my_url =~ s,\Q$path_info\E$,, &&
+                   $my_uri =~ s,\Q$path_info\E$,, &&
+                   defined $ENV{'SCRIPT_NAME'}) {
+                       $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'};
+               }
        }
+       # target of the home link on top of all pages
+       our $home_link = $my_uri || "/";
  }
  
  # core git executable to use
@@@ -70,9 -78,6 +78,6 @@@ our $projectroot = "++GITWEB_PROJECTROO
  # the number is relative to the projectroot
  our $project_maxdepth = "++GITWEB_PROJECT_MAXDEPTH++";
  
- # target of the home link on top of all pages
- our $home_link = $my_uri || "/";
  # string of the home link on top of all pages
  our $home_link_str = "++GITWEB_HOME_LINK_STR++";
  
@@@ -445,19 -450,6 +450,19 @@@ our %feature = 
        'javascript-actions' => {
                'override' => 0,
                'default' => [0]},
 +
 +      # Syntax highlighting support. This is based on Daniel Svensson's
 +      # and Sham Chukoury's work in gitweb-xmms2.git.
 +      # It requires the 'highlight' program present in $PATH,
 +      # and therefore is disabled by default.
 +
 +      # To enable system wide have in $GITWEB_CONFIG
 +      # $feature{'highlight'}{'default'} = [1];
 +
 +      'highlight' => {
 +              'sub' => sub { feature_bool('highlight', @_) },
 +              'override' => 0,
 +              'default' => [0]},
  );
  
  sub gitweb_get_feature {
@@@ -566,15 -558,18 +571,18 @@@ sub filter_snapshot_fmts 
                !$known_snapshot_formats{$_}{'disabled'}} @fmts;
  }
  
- our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
- our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
- # die if there are errors parsing config file
- if (-e $GITWEB_CONFIG) {
-       do $GITWEB_CONFIG;
-       die $@ if $@;
- } elsif (-e $GITWEB_CONFIG_SYSTEM) {
-       do $GITWEB_CONFIG_SYSTEM;
-       die $@ if $@;
+ our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM);
+ sub evaluate_gitweb_config {
+       our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
+       our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++";
+       # die if there are errors parsing config file
+       if (-e $GITWEB_CONFIG) {
+               do $GITWEB_CONFIG;
+               die $@ if $@;
+       } elsif (-e $GITWEB_CONFIG_SYSTEM) {
+               do $GITWEB_CONFIG_SYSTEM;
+               die $@ if $@;
+       }
  }
  
  # Get loadavg of system, to compare against $maxload.
@@@ -600,13 -595,16 +608,16 @@@ sub get_loadavg 
  }
  
  # version of the core git binary
- our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
- $number_of_git_cmds++;
- $projects_list ||= $projectroot;
+ our $git_version;
+ sub evaluate_git_version {
+       our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+       $number_of_git_cmds++;
+ }
  
- if (defined $maxload && get_loadavg() > $maxload) {
-       die_error(503, "The load average on the server is too high");
+ sub check_loadavg {
+       if (defined $maxload && get_loadavg() > $maxload) {
+               die_error(503, "The load average on the server is too high");
+       }
  }
  
  # ======================================================================
@@@ -693,11 -691,15 +704,15 @@@ our %allowed_options = 
  # should be single values, but opt can be an array. We should probably
  # build an array of parameters that can be multi-valued, but since for the time
  # being it's only this one, we just single it out
- while (my ($name, $symbol) = each %cgi_param_mapping) {
-       if ($symbol eq 'opt') {
-               $input_params{$name} = [ $cgi->param($symbol) ];
-       } else {
-               $input_params{$name} = $cgi->param($symbol);
+ sub evaluate_query_params {
+       our $cgi;
+       while (my ($name, $symbol) = each %cgi_param_mapping) {
+               if ($symbol eq 'opt') {
+                       $input_params{$name} = [ $cgi->param($symbol) ];
+               } else {
+                       $input_params{$name} = $cgi->param($symbol);
+               }
        }
  }
  
@@@ -844,164 -846,240 +859,255 @@@ sub evaluate_path_info 
                }
        }
  }
- evaluate_path_info();
  
- our $action = $input_params{'action'};
- if (defined $action) {
-       if (!validate_action($action)) {
-               die_error(400, "Invalid action parameter");
+ our ($action, $project, $file_name, $file_parent, $hash, $hash_parent, $hash_base,
+      $hash_parent_base, @extra_options, $page, $searchtype, $search_use_regexp,
+      $searchtext, $search_regexp);
+ sub evaluate_and_validate_params {
+       our $action = $input_params{'action'};
+       if (defined $action) {
+               if (!validate_action($action)) {
+                       die_error(400, "Invalid action parameter");
+               }
        }
- }
  
- # parameters which are pathnames
- our $project = $input_params{'project'};
- if (defined $project) {
-       if (!validate_project($project)) {
-               undef $project;
-               die_error(404, "No such project");
+       # parameters which are pathnames
+       our $project = $input_params{'project'};
+       if (defined $project) {
+               if (!validate_project($project)) {
+                       undef $project;
+                       die_error(404, "No such project");
+               }
        }
- }
  
- our $file_name = $input_params{'file_name'};
- if (defined $file_name) {
-       if (!validate_pathname($file_name)) {
-               die_error(400, "Invalid file parameter");
+       our $file_name = $input_params{'file_name'};
+       if (defined $file_name) {
+               if (!validate_pathname($file_name)) {
+                       die_error(400, "Invalid file parameter");
+               }
        }
- }
  
- our $file_parent = $input_params{'file_parent'};
- if (defined $file_parent) {
-       if (!validate_pathname($file_parent)) {
-               die_error(400, "Invalid file parent parameter");
+       our $file_parent = $input_params{'file_parent'};
+       if (defined $file_parent) {
+               if (!validate_pathname($file_parent)) {
+                       die_error(400, "Invalid file parent parameter");
+               }
        }
- }
  
- # parameters which are refnames
- our $hash = $input_params{'hash'};
- if (defined $hash) {
-       if (!validate_refname($hash)) {
-               die_error(400, "Invalid hash parameter");
+       # parameters which are refnames
+       our $hash = $input_params{'hash'};
+       if (defined $hash) {
+               if (!validate_refname($hash)) {
+                       die_error(400, "Invalid hash parameter");
+               }
        }
- }
  
- our $hash_parent = $input_params{'hash_parent'};
- if (defined $hash_parent) {
-       if (!validate_refname($hash_parent)) {
-               die_error(400, "Invalid hash parent parameter");
+       our $hash_parent = $input_params{'hash_parent'};
+       if (defined $hash_parent) {
+               if (!validate_refname($hash_parent)) {
+                       die_error(400, "Invalid hash parent parameter");
+               }
        }
- }
  
- our $hash_base = $input_params{'hash_base'};
- if (defined $hash_base) {
-       if (!validate_refname($hash_base)) {
-               die_error(400, "Invalid hash base parameter");
+       our $hash_base = $input_params{'hash_base'};
+       if (defined $hash_base) {
+               if (!validate_refname($hash_base)) {
+                       die_error(400, "Invalid hash base parameter");
+               }
        }
- }
  
- our @extra_options = @{$input_params{'extra_options'}};
- # @extra_options is always defined, since it can only be (currently) set from
- # CGI, and $cgi->param() returns the empty array in array context if the param
- # is not set
- foreach my $opt (@extra_options) {
-       if (not exists $allowed_options{$opt}) {
-               die_error(400, "Invalid option parameter");
-       }
-       if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
-               die_error(400, "Invalid option parameter for this action");
+       our @extra_options = @{$input_params{'extra_options'}};
+       # @extra_options is always defined, since it can only be (currently) set from
+       # CGI, and $cgi->param() returns the empty array in array context if the param
+       # is not set
+       foreach my $opt (@extra_options) {
+               if (not exists $allowed_options{$opt}) {
+                       die_error(400, "Invalid option parameter");
+               }
+               if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
+                       die_error(400, "Invalid option parameter for this action");
+               }
        }
- }
  
- our $hash_parent_base = $input_params{'hash_parent_base'};
- if (defined $hash_parent_base) {
-       if (!validate_refname($hash_parent_base)) {
-               die_error(400, "Invalid hash parent base parameter");
+       our $hash_parent_base = $input_params{'hash_parent_base'};
+       if (defined $hash_parent_base) {
+               if (!validate_refname($hash_parent_base)) {
+                       die_error(400, "Invalid hash parent base parameter");
+               }
        }
- }
  
- # other parameters
- our $page = $input_params{'page'};
- if (defined $page) {
-       if ($page =~ m/[^0-9]/) {
-               die_error(400, "Invalid page parameter");
+       # other parameters
+       our $page = $input_params{'page'};
+       if (defined $page) {
+               if ($page =~ m/[^0-9]/) {
+                       die_error(400, "Invalid page parameter");
+               }
        }
- }
  
- our $searchtype = $input_params{'searchtype'};
- if (defined $searchtype) {
-       if ($searchtype =~ m/[^a-z]/) {
-               die_error(400, "Invalid searchtype parameter");
+       our $searchtype = $input_params{'searchtype'};
+       if (defined $searchtype) {
+               if ($searchtype =~ m/[^a-z]/) {
+                       die_error(400, "Invalid searchtype parameter");
+               }
        }
- }
  
- our $search_use_regexp = $input_params{'search_use_regexp'};
      our $search_use_regexp = $input_params{'search_use_regexp'};
  
- our $searchtext = $input_params{'searchtext'};
- our $search_regexp;
- if (defined $searchtext) {
-       if (length($searchtext) < 2) {
-               die_error(403, "At least two characters are required for search parameter");
+       our $searchtext = $input_params{'searchtext'};
+       our $search_regexp;
+       if (defined $searchtext) {
+               if (length($searchtext) < 2) {
+                       die_error(403, "At least two characters are required for search parameter");
+               }
+               $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
        }
-       $search_regexp = $search_use_regexp ? $searchtext : quotemeta $searchtext;
  }
  
  # path to the current git repository
  our $git_dir;
- $git_dir = "$projectroot/$project" if $project;
- # list of supported snapshot formats
- our @snapshot_fmts = gitweb_get_feature('snapshot');
- @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
- # check that the avatar feature is set to a known provider name,
- # and for each provider check if the dependencies are satisfied.
- # if the provider name is invalid or the dependencies are not met,
- # reset $git_avatar to the empty string.
- our ($git_avatar) = gitweb_get_feature('avatar');
- if ($git_avatar eq 'gravatar') {
-       $git_avatar = '' unless (eval { require Digest::MD5; 1; });
- } elsif ($git_avatar eq 'picon') {
-       # no dependencies
- } else {
-       $git_avatar = '';
+ sub evaluate_git_dir {
+       our $git_dir = "$projectroot/$project" if $project;
+ }
+ our (@snapshot_fmts, $git_avatar);
+ sub configure_gitweb_features {
+       # list of supported snapshot formats
+       our @snapshot_fmts = gitweb_get_feature('snapshot');
+       @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+       # check that the avatar feature is set to a known provider name,
+       # and for each provider check if the dependencies are satisfied.
+       # if the provider name is invalid or the dependencies are not met,
+       # reset $git_avatar to the empty string.
+       our ($git_avatar) = gitweb_get_feature('avatar');
+       if ($git_avatar eq 'gravatar') {
+               $git_avatar = '' unless (eval { require Digest::MD5; 1; });
+       } elsif ($git_avatar eq 'picon') {
+               # no dependencies
+       } else {
+               $git_avatar = '';
+       }
  }
  
 +# custom error handler: 'die <message>' is Internal Server Error
 +sub handle_errors_html {
 +      my $msg = shift; # it is already HTML escaped
 +
 +      # to avoid infinite loop where error occurs in die_error,
 +      # change handler to default handler, disabling handle_errors_html
 +      set_message("Error occured when inside die_error:\n$msg");
 +
 +      # you cannot jump out of die_error when called as error handler;
 +      # the subroutine set via CGI::Carp::set_message is called _after_
 +      # HTTP headers are already written, so it cannot write them itself
 +      die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
 +}
 +set_message(\&handle_errors_html);
 +
  # dispatch
- if (!defined $action) {
-       if (defined $hash) {
-               $action = git_get_type($hash);
-       } elsif (defined $hash_base && defined $file_name) {
-               $action = git_get_type("$hash_base:$file_name");
-       } elsif (defined $project) {
-               $action = 'summary';
-       } else {
-               $action = 'project_list';
+ sub dispatch {
+       if (!defined $action) {
+               if (defined $hash) {
+                       $action = git_get_type($hash);
+               } elsif (defined $hash_base && defined $file_name) {
+                       $action = git_get_type("$hash_base:$file_name");
+               } elsif (defined $project) {
+                       $action = 'summary';
+               } else {
+                       $action = 'project_list';
+               }
        }
+       if (!defined($actions{$action})) {
+               die_error(400, "Unknown action");
+       }
+       if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
+           !$project) {
+               die_error(400, "Project needed");
+       }
+       $actions{$action}->();
  }
- if (!defined($actions{$action})) {
-       die_error(400, "Unknown action");
+ sub run_request {
+       our $t0 = [Time::HiRes::gettimeofday()]
+               if defined $t0;
+       evaluate_uri();
+       evaluate_gitweb_config();
+       evaluate_git_version();
+       check_loadavg();
+       # $projectroot and $projects_list might be set in gitweb config file
+       $projects_list ||= $projectroot;
+       evaluate_query_params();
+       evaluate_path_info();
+       evaluate_and_validate_params();
+       evaluate_git_dir();
+       configure_gitweb_features();
+       dispatch();
  }
- if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
-     !$project) {
-       die_error(400, "Project needed");
+ our $is_last_request = sub { 1 };
+ our ($pre_dispatch_hook, $post_dispatch_hook, $pre_listen_hook);
+ our $CGI = 'CGI';
+ our $cgi;
+ sub configure_as_fcgi {
+       require CGI::Fast;
+       our $CGI = 'CGI::Fast';
+       my $request_number = 0;
+       # let each child service 100 requests
+       our $is_last_request = sub { ++$request_number > 100 };
  }
- $actions{$action}->();
- DONE_GITWEB:
- 1;
+ sub evaluate_argv {
+       my $script_name = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'} || __FILE__;
+       configure_as_fcgi()
+               if $script_name =~ /\.fcgi$/;
+       return unless (@ARGV);
+       require Getopt::Long;
+       Getopt::Long::GetOptions(
+               'fastcgi|fcgi|f' => \&configure_as_fcgi,
+               'nproc|n=i' => sub {
+                       my ($arg, $val) = @_;
+                       return unless eval { require FCGI::ProcManager; 1; };
+                       my $proc_manager = FCGI::ProcManager->new({
+                               n_processes => $val,
+                       });
+                       our $pre_listen_hook    = sub { $proc_manager->pm_manage()        };
+                       our $pre_dispatch_hook  = sub { $proc_manager->pm_pre_dispatch()  };
+                       our $post_dispatch_hook = sub { $proc_manager->pm_post_dispatch() };
+               },
+       );
+ }
+ sub run {
+       evaluate_argv();
+       $pre_listen_hook->()
+               if $pre_listen_hook;
+  REQUEST:
+       while ($cgi = $CGI->new()) {
+               $pre_dispatch_hook->()
+                       if $pre_dispatch_hook;
+               run_request();
+               $pre_dispatch_hook->()
+                       if $post_dispatch_hook;
+               last REQUEST if ($is_last_request->());
+       }
+  DONE_GITWEB:
+       1;
+ }
+ run();
  
  ## ======================================================================
  ## action links
@@@ -2454,9 -2532,6 +2560,9 @@@ sub git_get_projects_list 
                        follow_skip => 2, # ignore duplicates
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
                        wanted => sub {
 +                              # global variables
 +                              our $project_maxdepth;
 +                              our $projectroot;
                                # skip project-list toplevel, if we get it.
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
@@@ -3192,88 -3267,26 +3298,88 @@@ sub blob_contenttype 
        return $type;
  }
  
 +# guess file syntax for syntax highlighting; return undef if no highlighting
 +# the name of syntax can (in the future) depend on syntax highlighter used
 +sub guess_file_syntax {
 +      my ($highlight, $mimetype, $file_name) = @_;
 +      return undef unless ($highlight && defined $file_name);
 +
 +      # configuration for 'highlight' (http://www.andre-simon.de/)
 +      # match by basename
 +      my %highlight_basename = (
 +              #'Program' => 'py',
 +              #'Library' => 'py',
 +              'SConstruct' => 'py', # SCons equivalent of Makefile
 +              'Makefile' => 'make',
 +      );
 +      # match by extension
 +      my %highlight_ext = (
 +              # main extensions, defining name of syntax;
 +              # see files in /usr/share/highlight/langDefs/ directory
 +              map { $_ => $_ }
 +                      qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
 +              # alternate extensions, see /etc/highlight/filetypes.conf
 +              'h' => 'c',
 +              map { $_ => 'cpp' } qw(cxx c++ cc),
 +              map { $_ => 'php' } qw(php3 php4),
 +              map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
 +              'mak' => 'make',
 +              map { $_ => 'xml' } qw(xhtml html htm),
 +      );
 +
 +      my $basename = basename($file_name, '.in');
 +      return $highlight_basename{$basename}
 +              if exists $highlight_basename{$basename};
 +
 +      $basename =~ /\.([^.]*)$/;
 +      my $ext = $1 or return undef;
 +      return $highlight_ext{$ext}
 +              if exists $highlight_ext{$ext};
 +
 +      return undef;
 +}
 +
 +# run highlighter and return FD of its output,
 +# or return original FD if no highlighting
 +sub run_highlighter {
 +      my ($fd, $highlight, $syntax) = @_;
 +      return $fd unless ($highlight && defined $syntax);
 +
 +      close $fd
 +              or die_error(404, "Reading blob failed");
 +      open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
 +                "highlight --xhtml --fragment --syntax $syntax |"
 +              or die_error(500, "Couldn't open file or run syntax highlighter");
 +      return $fd;
 +}
 +
  ## ======================================================================
  ## functions printing HTML: header, footer, error page
  
 +sub get_page_title {
 +      my $title = to_utf8($site_name);
 +
 +      return $title unless (defined $project);
 +      $title .= " - " . to_utf8($project);
 +
 +      return $title unless (defined $action);
 +      $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
 +
 +      return $title unless (defined $file_name);
 +      $title .= " - " . esc_path($file_name);
 +      if ($action eq "tree" && $file_name !~ m|/$|) {
 +              $title .= "/";
 +      }
 +
 +      return $title;
 +}
 +
  sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
 +      my %opts = @_;
  
 -      my $title = "$site_name";
 -      if (defined $project) {
 -              $title .= " - " . to_utf8($project);
 -              if (defined $action) {
 -                      $title .= "/$action";
 -                      if (defined $file_name) {
 -                              $title .= " - " . esc_path($file_name);
 -                              if ($action eq "tree" && $file_name !~ m|/$|) {
 -                                      $title .= "/";
 -                              }
 -                      }
 -              }
 -      }
 +      my $title = get_page_title();
        my $content_type;
        # require explicit support from the UA if we are to send the page as
        # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
                $content_type = 'text/html';
        }
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
 -                         -status=> $status, -expires => $expires);
 +                         -status=> $status, -expires => $expires)
 +              unless ($opts{'-no_http_header'});
        my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
        print <<EOF;
  <?xml version="1.0" encoding="utf-8"?>
@@@ -3505,7 -3517,6 +3611,7 @@@ sub die_error 
        my $status = shift || 500;
        my $error = esc_html(shift) || "Internal Server Error";
        my $extra = shift;
 +      my %opts = @_;
  
        my %http_responses = (
                400 => '400 Bad Request',
                500 => '500 Internal Server Error',
                503 => '503 Service Unavailable',
        );
 -      git_header_html($http_responses{$status});
 +      git_header_html($http_responses{$status}, undef, %opts);
        print <<EOF;
  <div class="page_body">
  <br /><br />
@@@ -3528,8 -3539,7 +3634,8 @@@ EO
        print "</div>\n";
  
        git_footer_html();
 -      goto DONE_GITWEB;
 +      goto DONE_GITWEB
 +              unless ($opts{'-error_handler'});
  }
  
  ## ----------------------------------------------------------------------
@@@ -5448,7 -5458,6 +5554,7 @@@ sub git_blob 
        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, $mimetype, $file_name);
 +      $fd = run_highlighter($fd, $highlight, $syntax)
 +              if $syntax;
 +
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
 -                      printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
 -                              . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
 -                             $nr, $nr, $nr, esc_html($line, -nbsp=>1);
 +                      printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
 +                             $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@@ -6224,8 -6229,8 +6330,8 @@@ sub git_commitdiff 
                        }
                        push @commit_spec, '--root', $hash;
                }
 -              open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
 -                      '--stdout', @commit_spec
 +              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");