Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Thu, 15 Jul 2010 19:04:32 +0000 (12:04 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 15 Jul 2010 19:04:32 +0000 (12:04 -0700)
* maint:
Documentation: add submodule.* to the big configuration variable list
gitmodules.5: url can be a relative path
gitweb: fix esc_url

1  2 
Documentation/config.txt
gitweb/gitweb.perl
diff --combined Documentation/config.txt
index 79d54e546a7823eb7b9da2a1fc99961a26123f63,eae06e7c3e52ea4227b359c4055a91538150481e..e75434b3ef2d81fa8dafd552d4b689eb84eea441
@@@ -196,17 -196,20 +196,17 @@@ core.quotepath:
        quoted without `-z` regardless of the setting of this
        variable.
  
 -core.autocrlf::
 -      If true, makes git convert `CRLF` at the end of lines in text files to
 -      `LF` when reading from the work tree, and convert in reverse when
 -      writing to the work tree.  The variable can be set to
 -      'input', in which case the conversion happens only while
 -      reading from the work tree but files are written out to the work
 -      tree with `LF` at the end of lines.  A file is considered
 -      "text" (i.e. be subjected to the autocrlf mechanism) based on
 -      the file's `crlf` attribute, or if `crlf` is unspecified,
 -      based on the file's contents.  See linkgit:gitattributes[5].
 +core.eol::
 +      Sets the line ending type to use in the working directory for
 +      files that have the `text` property set.  Alternatives are
 +      'lf', 'crlf' and 'native', which uses the platform's native
 +      line ending.  The default value is `native`.  See
 +      linkgit:gitattributes[5] for more information on end-of-line
 +      conversion.
  
  core.safecrlf::
 -      If true, makes git check if converting `CRLF` as controlled by
 -      `core.autocrlf` is reversible.  Git will verify if a command
 +      If true, makes git check if converting `CRLF` is reversible when
 +      end-of-line conversion is active.  Git will verify if a command
        modifies a file in the work tree either directly or indirectly.
        For example, committing a file followed by checking out the
        same file should yield the original file in the work tree.  If
        irreversible conversion but continue the operation.
  +
  CRLF conversion bears a slight chance of corrupting data.
 -autocrlf=true will convert CRLF to LF during commit and LF to
 +When it is enabled, git will convert CRLF to LF during commit and LF to
  CRLF during checkout.  A file that contains a mixture of LF and
  CRLF before the commit cannot be recreated by git.  For text
  files this is the right thing to do: it corrects line endings
@@@ -240,25 -243,15 +240,25 @@@ converting CRLFs corrupts data
  +
  Note, this safety check does not mean that a checkout will generate a
  file identical to the original file for a different setting of
 -`core.autocrlf`, but only for the current one.  For example, a text
 -file with `LF` would be accepted with `core.autocrlf=input` and could
 -later be checked out with `core.autocrlf=true`, in which case the
 +`core.eol` and `core.autocrlf`, but only for the current one.  For
 +example, a text file with `LF` would be accepted with `core.eol=lf`
 +and could later be checked out with `core.eol=crlf`, in which case the
  resulting file would contain `CRLF`, although the original file
  contained `LF`.  However, in both work trees the line endings would be
  consistent, that is either all `LF` or all `CRLF`, but never mixed.  A
  file with mixed line endings would be reported by the `core.safecrlf`
  mechanism.
  
 +core.autocrlf::
 +      Setting this variable to "true" is almost the same as setting
 +      the `text` attribute to "auto" on all files except that text
 +      files are not guaranteed to be normalized: files that contain
 +      `CRLF` in the repository will not be touched.  Use this
 +      setting if you want to have `CRLF` line endings in your
 +      working directory even though the repository does not have
 +      normalized line endings.  This variable can be set to 'input',
 +      in which case no output conversion is performed.
 +
  core.symlinks::
        If false, symbolic links are checked out as small plain files that
        contain the link text. linkgit:git-update-index[1] and
@@@ -488,8 -481,6 +488,8 @@@ core.whitespace:
    error (enabled by default).
  * `indent-with-non-tab` treats a line that is indented with 8 or more
    space characters as an error (not enabled by default).
 +* `tab-in-indent` treats a tab character in the initial indent part of
 +  the line as an error (not enabled by default).
  * `blank-at-eof` treats blank lines added at the end of file as an error
    (enabled by default).
  * `trailing-space` is a short-hand to cover both `blank-at-eol` and
@@@ -690,11 -681,6 +690,11 @@@ color.diff.<slot>:
        (highlighting whitespace errors). The values of these variables may be
        specified as in color.branch.<slot>.
  
 +color.decorate.<slot>::
 +      Use customized color for 'git log --decorate' output.  `<slot>` is one
 +      of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
 +      branches, remote tracking branches, tags, stash and HEAD, respectively.
 +
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
        `never`), never.  When set to `true` or `auto`, use color only
@@@ -804,8 -790,6 +804,8 @@@ diff.mnemonicprefix:
        standard "a/" and "b/" depending on what is being compared.  When
        this configuration is in effect, reverse diff output also swaps
        the order of the prefixes:
 +diff.noprefix::
 +      If set, 'git diff' does not show any source or destination prefix.
  `git diff`;;
        compares the (i)ndex and the (w)ork tree;
  `git diff HEAD`;;
@@@ -896,12 -880,6 +896,12 @@@ format.subjectprefix:
        The default for format-patch is to output files with the '[PATCH]'
        subject prefix. Use this variable to change that prefix.
  
 +format.signature::
 +      The default for format-patch is to output a signature containing
 +      the git version number. Use this variable to change that default.
 +      Set this variable to the empty string ("") to suppress
 +      signature generation.
 +
  format.suffix::
        The default for format-patch is to output files with the suffix
        `.patch`. Use this variable to change that suffix (make sure to
@@@ -962,19 -940,13 +962,19 @@@ gc.pruneexpire:
        unreachable objects immediately.
  
  gc.reflogexpire::
 +gc.<pattern>.reflogexpire::
        'git reflog expire' removes reflog entries older than
 -      this time; defaults to 90 days.
 +      this time; defaults to 90 days.  With "<pattern>" (e.g.
 +      "refs/stash") in the middle the setting applies only to
 +      the refs that match the <pattern>.
  
  gc.reflogexpireunreachable::
 +gc.<ref>.reflogexpireunreachable::
        'git reflog expire' removes reflog entries older than
        this time and are not reachable from the current tip;
 -      defaults to 30 days.
 +      defaults to 30 days.  With "<pattern>" (e.g. "refs/stash")
 +      in the middle, the setting applies only to the refs that
 +      match the <pattern>.
  
  gc.rerereresolved::
        Records of conflicted merge you resolved earlier are
@@@ -999,15 -971,13 +999,15 @@@ gitcvs.logfile:
        various stuff. See linkgit:git-cvsserver[1].
  
  gitcvs.usecrlfattr::
 -      If true, the server will look up the `crlf` attribute for
 -      files to determine the '-k' modes to use. If `crlf` is set,
 -      the '-k' mode will be left blank, so cvs clients will
 -      treat it as text. If `crlf` is explicitly unset, the file
 +      If true, the server will look up the end-of-line conversion
 +      attributes for files to determine the '-k' modes to use. If
 +      the attributes force git to treat a file as text,
 +      the '-k' mode will be left blank so cvs clients will
 +      treat it as text. If they suppress text conversion, the file
        will be set with '-kb' mode, which suppresses any newline munging
 -      the client might otherwise do. If `crlf` is not specified,
 -      then 'gitcvs.allbinary' is used. See linkgit:gitattributes[5].
 +      the client might otherwise do. If the attributes do not allow
 +      the file type to be determined, then 'gitcvs.allbinary' is
 +      used. See linkgit:gitattributes[5].
  
  gitcvs.allbinary::
        This is used if 'gitcvs.usecrlfattr' does not resolve
@@@ -1294,13 -1264,6 +1294,13 @@@ log.date:
        following alternatives: {relative,local,default,iso,rfc,short}.
        See linkgit:git-log[1].
  
 +log.decorate::
 +      Print out the ref names of any commits that are shown by the log
 +      command. If 'short' is specified, the ref name prefixes 'refs/heads/',
 +      'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is
 +      specified, the full ref name (including prefix) will be printed.
 +      This is the same as the log commands '--decorate' option.
 +
  log.showroot::
        If true, the initial commit will be shown as a big creation event.
        This is equivalent to a diff against an empty tree.
@@@ -1499,16 -1462,6 +1499,16 @@@ pager.<cmd>:
        it takes precedence over this option.  To disable pagination for
        all commands, set `core.pager` or `GIT_PAGER` to `cat`.
  
 +pretty.<name>::
 +      Alias for a --pretty= format string, as specified in
 +      linkgit:git-log[1]. Any aliases defined here can be used just
 +      as the built-in pretty formats could. For example,
 +      running `git config pretty.changelog "format:{asterisk} %H %s"`
 +      would cause the invocation `git log --pretty=changelog`
 +      to be equivalent to running `git log "--pretty=format:{asterisk} %H %s"`.
 +      Note that an alias with the same name as a built-in format
 +      will be silently ignored.
 +
  pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@@ -1621,9 -1574,7 +1621,9 @@@ remote.<name>.uploadpack:
  
  remote.<name>.tagopt::
        Setting this value to \--no-tags disables automatic tag following when
 -      fetching from remote <name>
 +      fetching from remote <name>. Setting it to \--tags will fetch every
 +      tag from remote <name>, even if they are not reachable from remote
 +      branch heads.
  
  remote.<name>.vcs::
        Setting this to a value <vcs> will cause git to interact with
@@@ -1734,6 -1685,15 +1734,15 @@@ status.submodulesummary:
        summary of commits for modified submodules will be shown (see
        --summary-limit option of linkgit:git-submodule[1]).
  
+ submodule.<name>.path::
+ submodule.<name>.url::
+ submodule.<name>.update::
+       The path within this project, URL, and the updating strategy
+       for a submodule.  These variables are initially populated
+       by 'git submodule init'; edit them to override the
+       URL and other values found in the `.gitmodules` file.  See
+       linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  tar.umask::
        This variable can be used to restrict the permission bits of
        tar archive entries.  The default is 0002, which turns off the
diff --combined gitweb/gitweb.perl
index 1f611d22d4c4d5b00491f98eff5c4749900333e9,a97ce03444e4ca24273dff32d9e7b9bbc874aa41..cedc3573136922d424971ef2a16feeb82496c37d
@@@ -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,42 -28,34 +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
@@@ -78,6 -70,9 +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++";
  
@@@ -450,19 -445,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 {
@@@ -571,18 -553,15 +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.
@@@ -608,16 -587,13 +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");
 +      }
  }
  
  # ======================================================================
@@@ -704,15 -680,11 +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);
 +              }
        }
  }
  
@@@ -859,277 -831,152 +859,277 @@@ 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;
  }
  
 -# 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';
 +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 {
 -              $action = 'project_list';
 +              $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
 +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}->();
 +}
 +
 +sub reset_timer {
 +      our $t0 = [Time::HiRes::gettimeofday()]
 +              if defined $t0;
 +      our $number_of_git_cmds = 0;
 +}
 +
 +sub run_request {
 +      reset_timer();
 +
 +      evaluate_uri();
 +      check_loadavg();
 +
 +      evaluate_query_params();
 +      evaluate_path_info();
 +      evaluate_and_validate_params();
 +      evaluate_git_dir();
 +
 +      configure_gitweb_features();
 +
 +      dispatch();
 +}
 +
 +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 };
  }
 -if (!defined($actions{$action})) {
 -      die_error(400, "Unknown action");
 +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() };
 +              },
 +      );
  }
 -if ($action !~ m/^(?:opml|project_list|project_index)$/ &&
 -    !$project) {
 -      die_error(400, "Project needed");
 +
 +sub run {
 +      evaluate_argv();
 +      evaluate_gitweb_config();
 +      evaluate_git_version();
 +
 +      # $projectroot and $projects_list might be set in gitweb config file
 +      $projects_list ||= $projectroot;
 +
 +      $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();
 +
 +if (defined caller) {
 +      # wrapped in a subroutine processing requests,
 +      # e.g. mod_perl with ModPerl::Registry, or PSGI with Plack::App::WrapCGI
 +      return;
 +} else {
 +      # pure CGI script, serving single request
 +      exit;
  }
 -$actions{$action}->();
 -exit;
  
  ## ======================================================================
  ## action links
  
 +# possible values of extra options
 +# -full => 0|1      - use absolute/full URL ($my_uri/$my_url as base)
 +# -replay => 1      - start from a current view (replay with modifications)
 +# -path_info => 0|1 - don't use/use path_info URL (if possible)
  sub href {
        my %params = @_;
        # default is to use -absolute url() i.e. $my_uri
        }
  
        my $use_pathinfo = gitweb_check_feature('pathinfo');
 -      if ($use_pathinfo and defined $params{'project'}) {
 +      if (defined $params{'project'} &&
 +          (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
                # try to put as many parameters as possible in PATH_INFO:
                #   - project name
                #   - action
@@@ -1327,8 -1173,7 +1327,7 @@@ sub esc_param 
  sub esc_url {
        my $str = shift;
        return undef unless defined $str;
-       $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
-       $str =~ s/\+/%2B/g;
+       $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&= ]+)/CGI::escape($1)/eg;
        $str =~ s/ /\+/g;
        return $str;
  }
@@@ -2574,9 -2419,6 +2573,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
@@@ -3312,88 -3154,26 +3311,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"?>
@@@ -3625,7 -3404,6 +3624,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 />
@@@ -3648,8 -3426,7 +3647,8 @@@ EO
        print "</div>\n";
  
        git_footer_html();
 -      exit;
 +      goto DONE_GITWEB
 +              unless ($opts{'-error_handler'});
  }
  
  ## ----------------------------------------------------------------------
@@@ -5568,7 -5345,6 +5567,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
@@@ -6344,8 -6116,8 +6343,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");