Merge branch 'maint'
authorJunio C Hamano <junkio@cox.net>
Fri, 4 May 2007 06:26:54 +0000 (23:26 -0700)
committerJunio C Hamano <junkio@cox.net>
Fri, 4 May 2007 06:26:54 +0000 (23:26 -0700)
* maint:
gitweb: use decode_utf8 directly
posix compatibility for t4200
Document 'opendiff' value in config.txt and git-mergetool.txt
Allow PERL_PATH="/usr/bin/env perl"
Make xstrndup common
diff.c: fix "size cache" handling.
http-fetch: Disable use of curl multi support for libcurl < 7.16.

1  2 
Documentation/config.txt
commit.c
diff.c
git-compat-util.h
gitweb/gitweb.perl
diff --combined Documentation/config.txt
index c257cdf525e2daa3b90140430327e6f5d2ea6bda,a7daa08731715846d68b35c4e9da740e878ed05a..24f9655fef8b9acbeb239554d55585480a0eede7
@@@ -300,10 -300,6 +300,10 @@@ branch.<name>.merge:
        branch.<name>.merge to the desired branch, and use the special setting
        `.` (a period) for branch.<name>.remote.
  
 +clean.requireForce::
 +      A boolean to make git-clean do nothing unless given -f or -n.  Defaults
 +      to false.
 +
  color.branch::
        A boolean to enable/disable color in the output of
        gitlink:git-branch[1]. May be set to `true` (or `always`),
@@@ -427,34 -423,8 +427,34 @@@ gitcvs.allbinary:
        causes the client to treat all files as binary files which suppresses
        any newline munging it otherwise might do. A work-around for the
        fact that there is no way yet to set single files to mode '-kb'.
 +
 +gitcvs.dbname::
 +      Database used by git-cvsserver to cache revision information
 +      derived from the git repository. The exact meaning depends on the
 +      used database driver, for SQLite (which is the default driver) this
 +      is a filename. Supports variable substitution (see
 +      gitlink:git-cvsserver[1] for details). May not contain semicolons (`;`).
 +      Default: '%Ggitcvs.%m.sqlite'
 +
 +gitcvs.dbdriver::
 +      Used Perl DBI driver. You can specify any available driver
 +        for this here, but it might not work. git-cvsserver is tested
 +      with 'DBD::SQLite', reported to work with 'DBD::Pg', and
 +      reported *not* to work with 'DBD::mysql'. Experimental feature.
 +      May not contain double colons (`:`). Default: 'SQLite'.
        See gitlink:git-cvsserver[1].
  
 +gitcvs.dbuser, gitcvs.dbpass::
 +      Database user and password. Only useful if setting 'gitcvs.dbdriver',
 +      since SQLite has no concept of database users and/or passwords.
 +      'gitcvs.dbuser' supports variable substitution (see
 +      gitlink:git-cvsserver[1] for details).
 +
 +All gitcvs variables except for 'gitcvs.allbinary' can also specifed
 +as 'gitcvs.<access_method>.<varname>' (where 'access_method' is one
 +of "ext" and "pserver") to make them apply only for the given access
 +method.
 +
  http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
        over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
@@@ -520,7 -490,7 +520,7 @@@ merge.summary:
  merge.tool::
        Controls which merge resolution program is used by
        gitlink:git-mergetool[l].  Valid values are: "kdiff3", "tkdiff",
-       "meld", "xxdiff", "emerge", "vimdiff"
+       "meld", "xxdiff", "emerge", "vimdiff", and "opendiff"
  
  merge.verbosity::
        Controls the amount of output shown by the recursive merge
        conflicts, 2 outputs conflicts and file changes.  Level 5 and
        above outputs debugging information.  The default is level 2.
  
 +merge.<driver>.name::
 +      Defines a human readable name for a custom low-level
 +      merge driver.  See gitlink:gitattributes[5] for details.
 +
 +merge.<driver>.driver::
 +      Defines the command that implements a custom low-level
 +      merge driver.  See gitlink:gitattributes[5] for details.
 +
 +merge.<driver>.recursive::
 +      Names a low-level merge driver to be used when
 +      performing an internal merge between common ancestors.
 +      See gitlink:gitattributes[5] for details.
 +
  pack.window::
        The size of the window used by gitlink:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
@@@ -610,8 -567,8 +610,8 @@@ tar.umask:
  
  user.email::
        Your email address to be recorded in any newly created commits.
 -      Can be overridden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
 -      environment variables.  See gitlink:git-commit-tree[1].
 +      Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
 +      'EMAIL' environment variables.  See gitlink:git-commit-tree[1].
  
  user.name::
        Your full name to be recorded in any newly created commits.
diff --combined commit.c
index f1ba972d9abcb218b9aae0680a753edeb3666bc3,eb911f44d7b0d78d884e901430cc34a300f7af21..aa7059c1c6dd7650aa0623443372cfa804a0787c
+++ b/commit.c
@@@ -4,8 -4,6 +4,8 @@@
  #include "pkt-line.h"
  #include "utf8.h"
  #include "interpolate.h"
 +#include "diff.h"
 +#include "revision.h"
  
  int save_commit_buffer = 1;
  
@@@ -98,8 -96,12 +98,8 @@@ struct commit *lookup_commit_reference(
  struct commit *lookup_commit(const unsigned char *sha1)
  {
        struct object *obj = lookup_object(sha1);
 -      if (!obj) {
 -              struct commit *ret = alloc_commit_node();
 -              created_object(sha1, &ret->object);
 -              ret->object.type = OBJ_COMMIT;
 -              return ret;
 -      }
 +      if (!obj)
 +              return create_object(sha1, OBJ_COMMIT, alloc_commit_node());
        if (!obj->type)
                obj->type = OBJ_COMMIT;
        return check_commit(obj, sha1, 0);
@@@ -526,7 -528,7 +526,7 @@@ static int add_rfc2047(char *buf, cons
  }
  
  static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
 -                       const char *line, int relative_date,
 +                       const char *line, enum date_mode dmode,
                         const char *encoding)
  {
        char *date;
        switch (fmt) {
        case CMIT_FMT_MEDIUM:
                ret += sprintf(buf + ret, "Date:   %s\n",
 -                             show_date(time, tz, relative_date));
 +                             show_date(time, tz, dmode));
                break;
        case CMIT_FMT_EMAIL:
                ret += sprintf(buf + ret, "Date: %s\n",
                break;
        case CMIT_FMT_FULLER:
                ret += sprintf(buf + ret, "%sDate: %s\n", what,
 -                             show_date(time, tz, relative_date));
 +                             show_date(time, tz, dmode));
                break;
        default:
                /* notin' */
@@@ -718,14 -720,6 +718,6 @@@ static char *logmsg_reencode(const stru
        return out;
  }
  
- static char *xstrndup(const char *text, int len)
- {
-       char *result = xmalloc(len + 1);
-       memcpy(result, text, len);
-       result[len] = '\0';
-       return result;
- }
  static void fill_person(struct interp *table, const char *msg, int len)
  {
        int start, end, tz = 0;
@@@ -806,8 -800,7 +798,8 @@@ static long format_commit_message(cons
                { "%Cgreen" },  /* green */
                { "%Cblue" },   /* blue */
                { "%Creset" },  /* reset color */
 -              { "%n" }        /* newline */
 +              { "%n" },       /* newline */
 +              { "%m" },       /* left/right/bottom */
        };
        enum interp_index {
                IHASH = 0, IHASH_ABBREV,
                ISUBJECT,
                IBODY,
                IRED, IGREEN, IBLUE, IRESET_COLOR,
 -              INEWLINE
 +              INEWLINE,
 +              ILEFT_RIGHT,
        };
        struct commit_list *p;
        char parents[1024];
        int i;
        enum { HEADER, SUBJECT, BODY } state;
  
 -      if (INEWLINE + 1 != ARRAY_SIZE(table))
 +      if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
                die("invalid interp table!");
  
        /* these are independent of the commit */
        interp_set_entry(table, ITREE_ABBREV,
                        find_unique_abbrev(commit->tree->object.sha1,
                                DEFAULT_ABBREV));
 +      interp_set_entry(table, ILEFT_RIGHT,
 +                       (commit->object.flags & BOUNDARY)
 +                       ? "-"
 +                       : (commit->object.flags & SYMMETRIC_LEFT)
 +                       ? "<"
 +                       : ">");
  
        parents[1] = 0;
        for (i = 0, p = commit->parents;
@@@ -919,7 -905,7 +911,7 @@@ unsigned long pretty_print_commit(enum 
                                  char *buf, unsigned long space,
                                  int abbrev, const char *subject,
                                  const char *after_subject,
 -                                int relative_date)
 +                                enum date_mode dmode)
  {
        int hdr = 1, body = 0, seen_title = 0;
        unsigned long offset = 0;
                                offset += add_user_info("Author", fmt,
                                                        buf + offset,
                                                        line + 7,
 -                                                      relative_date,
 +                                                      dmode,
                                                        encoding);
                        if (!memcmp(line, "committer ", 10) &&
                            (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
                                offset += add_user_info("Commit", fmt,
                                                        buf + offset,
                                                        line + 10,
 -                                                      relative_date,
 +                                                      dmode,
                                                        encoding);
                        continue;
                }
diff --combined diff.c
index 9dfded76642c1136701fb05557c182dc92448394,b28933f870d7dd11596fd3715280e1488ac2430f..7bbe7590b2e6b8c1f153902e0375df193c5268f2
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -8,7 -8,6 +8,7 @@@
  #include "delta.h"
  #include "xdiff-interface.h"
  #include "color.h"
 +#include "attr.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -52,49 -51,6 +52,49 @@@ static int parse_diff_color_slot(const 
        die("bad config variable '%s'", var);
  }
  
 +static struct ll_diff_driver {
 +      const char *name;
 +      struct ll_diff_driver *next;
 +      char *cmd;
 +} *user_diff, **user_diff_tail;
 +
 +/*
 + * Currently there is only "diff.<drivername>.command" variable;
 + * because there are "diff.color.<slot>" variables, we are parsing
 + * this in a bit convoluted way to allow low level diff driver
 + * called "color".
 + */
 +static int parse_lldiff_command(const char *var, const char *ep, const char *value)
 +{
 +      const char *name;
 +      int namelen;
 +      struct ll_diff_driver *drv;
 +
 +      name = var + 5;
 +      namelen = ep - name;
 +      for (drv = user_diff; drv; drv = drv->next)
 +              if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
 +                      break;
 +      if (!drv) {
 +              char *namebuf;
 +              drv = xcalloc(1, sizeof(struct ll_diff_driver));
 +              namebuf = xmalloc(namelen + 1);
 +              memcpy(namebuf, name, namelen);
 +              namebuf[namelen] = 0;
 +              drv->name = namebuf;
 +              drv->next = NULL;
 +              if (!user_diff_tail)
 +                      user_diff_tail = &user_diff;
 +              *user_diff_tail = drv;
 +              user_diff_tail = &(drv->next);
 +      }
 +
 +      if (!value)
 +              return error("%s: lacks value", var);
 +      drv->cmd = strdup(value);
 +      return 0;
 +}
 +
  /*
   * These are to give UI layer defaults.
   * The core-level commands such as git-diff-files should
@@@ -121,18 -77,11 +121,18 @@@ int git_diff_ui_config(const char *var
                        diff_detect_rename_default = DIFF_DETECT_RENAME;
                return 0;
        }
 +      if (!prefixcmp(var, "diff.")) {
 +              const char *ep = strrchr(var, '.');
 +
 +              if (ep != var + 4 && !strcmp(ep, ".command"))
 +                      return parse_lldiff_command(var, ep, value);
 +      }
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
                color_parse(value, var, diff_colors[slot]);
                return 0;
        }
 +
        return git_default_config(var, value);
  }
  
@@@ -862,12 -811,7 +862,12 @@@ static void show_stats(struct diffstat_
  
                if (data->files[i]->is_binary) {
                        show_name(prefix, name, len, reset, set);
 -                      printf("  Bin\n");
 +                      printf("  Bin ");
 +                      printf("%s%d%s", del_c, deleted, reset);
 +                      printf(" -> ");
 +                      printf("%s%d%s", add_c, added, reset);
 +                      printf(" bytes");
 +                      printf("\n");
                        goto free_diffstat_file;
                }
                else if (data->files[i]->is_unmerged) {
@@@ -1102,39 -1046,13 +1102,39 @@@ static void emit_binary_diff(mmfile_t *
        emit_binary_diff_body(two, one);
  }
  
 +static void setup_diff_attr_check(struct git_attr_check *check)
 +{
 +      static struct git_attr *attr_diff;
 +
 +      if (!attr_diff)
 +              attr_diff = git_attr("diff", 4);
 +      check->attr = attr_diff;
 +}
 +
  #define FIRST_FEW_BYTES 8000
 -static int mmfile_is_binary(mmfile_t *mf)
 +static int file_is_binary(struct diff_filespec *one)
  {
 -      long sz = mf->size;
 +      unsigned long sz;
 +      struct git_attr_check attr_diff_check;
 +
 +      setup_diff_attr_check(&attr_diff_check);
 +      if (!git_checkattr(one->path, 1, &attr_diff_check)) {
 +              const char *value = attr_diff_check.value;
 +              if (ATTR_TRUE(value))
 +                      return 0;
 +              else if (ATTR_FALSE(value))
 +                      return 1;
 +      }
 +
 +      if (!one->data) {
 +              if (!DIFF_FILE_VALID(one))
 +                      return 0;
 +              diff_populate_filespec(one, 0);
 +      }
 +      sz = one->size;
        if (FIRST_FEW_BYTES < sz)
                sz = FIRST_FEW_BYTES;
 -      return !!memchr(mf->ptr, 0, sz);
 +      return !!memchr(one->data, 0, sz);
  }
  
  static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
 -      if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
 +      if (!o->text && (file_is_binary(one) || file_is_binary(two))) {
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
@@@ -1267,11 -1185,9 +1267,11 @@@ static void builtin_diffstat(const cha
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
 -      if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
 +      if (file_is_binary(one) || file_is_binary(two)) {
                data->is_binary = 1;
 -      else {
 +              data->added = mf2.size;
 +              data->deleted = mf1.size;
 +      } else {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
@@@ -1305,7 -1221,7 +1305,7 @@@ static void builtin_checkdiff(const cha
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
 -      if (mmfile_is_binary(&mf2))
 +      if (file_is_binary(two))
                return;
        else {
                /* Crazy xdl interfaces.. */
@@@ -1474,22 -1390,6 +1474,22 @@@ static int populate_from_stdin(struct d
        return 0;
  }
  
 +static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 +{
 +      int len;
 +      char *data = xmalloc(100);
 +      len = snprintf(data, 100,
 +              "Subproject commit %s\n", sha1_to_hex(s->sha1));
 +      s->data = data;
 +      s->size = len;
 +      s->should_free = 1;
 +      if (size_only) {
 +              s->data = NULL;
 +              free(data);
 +      }
 +      return 0;
 +}
 +
  /*
   * While doing rename detection and pickaxe operation, we may need to
   * grab the data for the blob (or file) for our own in-core comparison.
@@@ -1508,10 -1408,6 +1508,10 @@@ int diff_populate_filespec(struct diff_
  
        if (s->data)
                return err;
 +
 +      if (S_ISDIRLNK(s->mode))
 +              return diff_populate_gitlink(s, size_only);
 +
        if (!s->sha1_valid ||
            reuse_worktree_file(s->path, s->sha1, 0)) {
                struct stat st;
                /*
                 * Convert from working tree format to canonical git format
                 */
 -              buf = s->data;
                size = s->size;
 -              if (convert_to_git(s->path, &buf, &size)) {
 +              buf = convert_to_git(s->path, s->data, &size);
 +              if (buf) {
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
                        s->data = buf;
                enum object_type type;
                struct sha1_size_cache *e;
  
+               if (size_only && use_size_cache &&
+                   (e = locate_size_cache(s->sha1, 1, 0)) != NULL) {
+                       s->size = e->size;
+                       return 0;
+               }
                if (size_only) {
-                       e = locate_size_cache(s->sha1, 1, 0);
-                       if (e) {
-                               s->size = e->size;
-                               return 0;
-                       }
                        type = sha1_object_info(s->sha1, &s->size);
-                       if (type < 0)
+                       if (use_size_cache && 0 < type)
                                locate_size_cache(s->sha1, 0, s->size);
                }
                else {
@@@ -1797,30 -1694,6 +1798,30 @@@ static void run_external_diff(const cha
        }
  }
  
 +static const char *external_diff_attr(const char *name)
 +{
 +      struct git_attr_check attr_diff_check;
 +
 +      setup_diff_attr_check(&attr_diff_check);
 +      if (!git_checkattr(name, 1, &attr_diff_check)) {
 +              const char *value = attr_diff_check.value;
 +              if (!ATTR_TRUE(value) &&
 +                  !ATTR_FALSE(value) &&
 +                  !ATTR_UNSET(value)) {
 +                      struct ll_diff_driver *drv;
 +
 +                      if (!user_diff_tail) {
 +                              user_diff_tail = &user_diff;
 +                              git_config(git_diff_ui_config);
 +                      }
 +                      for (drv = user_diff; drv; drv = drv->next)
 +                              if (!strcmp(drv->name, value))
 +                                      return drv->cmd;
 +              }
 +      }
 +      return NULL;
 +}
 +
  static void run_diff_cmd(const char *pgm,
                         const char *name,
                         const char *other,
                         struct diff_options *o,
                         int complete_rewrite)
  {
 +      if (!o->allow_external)
 +              pgm = NULL;
 +      else {
 +              const char *cmd = external_diff_attr(name);
 +              if (cmd)
 +                      pgm = cmd;
 +      }
 +
        if (pgm) {
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
                                  complete_rewrite);
@@@ -1934,8 -1799,8 +1935,8 @@@ static void run_diff(struct diff_filepa
  
                if (o->binary) {
                        mmfile_t mf;
 -                      if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
 -                          (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
 +                      if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
 +                          (!fill_mmfile(&mf, two) && file_is_binary(two)))
                                abbrev = 40;
                }
                len += snprintf(msg + len, sizeof(msg) - len,
@@@ -2830,7 -2695,7 +2831,7 @@@ static int diff_get_patch_id(struct dif
                        return error("unable to read files to diff");
  
                /* Maybe hash p->two? into the patch id? */
 -              if (mmfile_is_binary(&mf2))
 +              if (file_is_binary(p->two))
                        continue;
  
                len1 = remove_space(p->one->path, strlen(p->one->path));
diff --combined git-compat-util.h
index 2c84016ac9942eb62c24d143a551c8f1c8f48bb6,bd93b6257885d56fda7268ae88ea82f721a6707f..c08688c8f355ccf61a746bbe1ac9a6788bdea7e3
  
  #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
  
 +#ifdef __GNUC__
 +#define TYPEOF(x) (__typeof__(x))
 +#else
 +#define TYPEOF(x)
 +#endif
 +
 +#define MSB(x, bits) ((x) & TYPEOF(x)(~0ULL << (sizeof(x) * 8 - (bits))))
 +
  #if !defined(__APPLE__) && !defined(__FreeBSD__)
  #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
  #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
@@@ -197,6 -189,19 +197,19 @@@ static inline void *xmalloc(size_t size
        return ret;
  }
  
+ static inline char *xstrndup(const char *str, size_t len)
+ {
+       char *p;
+       p = memchr(str, '\0', len);
+       if (p)
+               len = p - str;
+       p = xmalloc(len + 1);
+       memcpy(p, str, len);
+       p[len] = '\0';
+       return p;
+ }
  static inline void *xrealloc(void *ptr, size_t size)
  {
        void *ret = realloc(ptr, size);
diff --combined gitweb/gitweb.perl
index cbd8d03e64baa6324192e0be8a910c508b196d05,12c2e6685ec9378d43433d1f73b6f2c13c4f7ffd..ba5cc43e5b7ed6c9e980c60202c062d010bf170a
@@@ -19,7 -19,7 +19,7 @@@ use File::Basename qw(basename)
  binmode STDOUT, ':utf8';
  
  BEGIN {
 -      CGI->compile() if $ENV{MOD_PERL};
 +      CGI->compile() if $ENV{'MOD_PERL'};
  }
  
  our $cgi = new CGI;
@@@ -71,10 -71,6 +71,10 @@@ our $logo_label = "git homepage"
  # source of projects list
  our $projects_list = "++GITWEB_LIST++";
  
 +# default order of projects list
 +# valid values are none, project, descr, owner, and age
 +our $default_projects_order = "project";
 +
  # show repository only if this file exists
  # (only effective if this variable evaluates to true)
  our $export_ok = "++GITWEB_EXPORT_OK++";
@@@ -180,8 -176,8 +180,8 @@@ our %feature = 
        # projects matching $projname/*.git will not be shown in the main
        # projects list, instead a '+' mark will be added to $projname
        # there and a 'forks' view will be enabled for the project, listing
 -      # all the forks. This feature is supported only if project list
 -      # is taken from a directory, not file.
 +      # all the forks. If project list is taken from a file, forks have
 +      # to be listed after the main project.
  
        # To enable system wide have in $GITWEB_CONFIG
        # $feature{'forks'}{'default'} = [1];
@@@ -564,12 -560,6 +564,6 @@@ sub validate_refname 
        return $input;
  }
  
- # very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT);
- sub to_utf8 {
-       my $str = shift;
-       return decode("utf8", $str, Encode::FB_DEFAULT);
- }
  # quote unsafe chars, but keep the slash, even when it's not
  # correct, but quoted slashes look too horrible in bookmarks
  sub esc_param {
@@@ -594,7 -584,7 +588,7 @@@ sub esc_html ($;%) 
        my $str = shift;
        my %opts = @_;
  
-       $str = to_utf8($str);
+       $str = decode_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
                $str =~ s/ /&nbsp;/g;
@@@ -608,7 -598,7 +602,7 @@@ sub esc_path 
        my $str = shift;
        my %opts = @_;
  
-       $str = to_utf8($str);
+       $str = decode_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
                $str =~ s/ /&nbsp;/g;
@@@ -891,7 -881,7 +885,7 @@@ sub format_subject_html 
  
        if (length($short) < length($long)) {
                return $cgi->a({-href => $href, -class => "list subject",
-                               -title => to_utf8($long)},
+                               -title => decode_utf8($long)},
                       esc_html($short) . $extra);
        } else {
                return $cgi->a({-href => $href, -class => "list subject"},
@@@ -1051,8 -1041,6 +1045,8 @@@ sub git_get_projects_list 
        $filter ||= '';
        $filter =~ s/\.git$//;
  
 +      my ($check_forks) = gitweb_check_feature('forks');
 +
        if (-d $projects_list) {
                # search in directory
                my $dir = $projects_list . ($filter ? "/$filter" : '');
                $dir =~ s!/+$!!;
                my $pfxlen = length("$dir");
  
 -              my ($check_forks) = gitweb_check_feature('forks');
 -
                File::Find::find({
                        follow_fast => 1, # follow symbolic links
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
                # 'git%2Fgit.git Linus+Torvalds'
                # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
                # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman'
 +              my %paths;
                open my ($fd), $projects_list or return;
 +      PROJECT:
                while (my $line = <$fd>) {
                        chomp $line;
                        my ($path, $owner) = split ' ', $line;
                                # looking for forks;
                                my $pfx = substr($path, 0, length($filter));
                                if ($pfx ne $filter) {
 -                                      next;
 +                                      next PROJECT;
                                }
                                my $sfx = substr($path, length($filter));
                                if ($sfx !~ /^\/.*\.git$/) {
 -                                      next;
 +                                      next PROJECT;
 +                              }
 +                      } elsif ($check_forks) {
 +                      PATH:
 +                              foreach my $filter (keys %paths) {
 +                                      # looking for forks;
 +                                      my $pfx = substr($path, 0, length($filter));
 +                                      if ($pfx ne $filter) {
 +                                              next PATH;
 +                                      }
 +                                      my $sfx = substr($path, length($filter));
 +                                      if ($sfx !~ /^\/.*\.git$/) {
 +                                              next PATH;
 +                                      }
 +                                      # is a fork, don't include it in
 +                                      # the list
 +                                      next PROJECT;
                                }
                        }
                        if (check_export_ok("$projectroot/$path")) {
                                my $pr = {
                                        path => $path,
-                                       owner => to_utf8($owner),
+                                       owner => decode_utf8($owner),
                                };
 -                              push @list, $pr
 +                              push @list, $pr;
 +                              (my $forks_path = $path) =~ s/\.git$//;
 +                              $paths{$forks_path}++;
                        }
                }
                close $fd;
        }
 -      @list = sort {$a->{'path'} cmp $b->{'path'}} @list;
        return @list;
  }
  
@@@ -1156,7 -1127,7 +1150,7 @@@ sub git_get_project_owner 
                        $pr = unescape($pr);
                        $ow = unescape($ow);
                        if ($pr eq $project) {
-                               $owner = to_utf8($ow);
+                               $owner = decode_utf8($ow);
                                last;
                        }
                }
@@@ -1630,7 -1601,7 +1624,7 @@@ sub get_file_owner 
        }
        my $owner = $gcos;
        $owner =~ s/[,;].*$//;
-       return to_utf8($owner);
+       return decode_utf8($owner);
  }
  
  ## ......................................................................
@@@ -1713,7 -1684,7 +1707,7 @@@ sub git_header_html 
  
        my $title = "$site_name";
        if (defined $project) {
-               $title .= " - " . to_utf8($project);
+               $title .= " - " . decode_utf8($project);
                if (defined $action) {
                        $title .= "/$action";
                        if (defined $file_name) {
@@@ -1823,7 -1794,7 +1817,7 @@@ EO
                      $cgi->hidden(-name => "a") . "\n" .
                      $cgi->hidden(-name => "h") . "\n" .
                      $cgi->popup_menu(-name => 'st', -default => 'commit',
 -                                     -values => ['commit', 'author', 'committer', 'pickaxe']) .
 +                                     -values => ['commit', 'author', 'committer', 'pickaxe']) .
                      $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
                      " search:\n",
                      $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
@@@ -1893,16 -1864,16 +1887,16 @@@ sub git_print_page_nav 
        my %arg = map { $_ => {action=>$_} } @navs;
        if (defined $head) {
                for (qw(commit commitdiff)) {
 -                      $arg{$_}{hash} = $head;
 +                      $arg{$_}{'hash'} = $head;
                }
                if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) {
                        for (qw(shortlog log)) {
 -                              $arg{$_}{hash} = $head;
 +                              $arg{$_}{'hash'} = $head;
                        }
                }
        }
 -      $arg{tree}{hash} = $treehead if defined $treehead;
 -      $arg{tree}{hash_base} = $treebase if defined $treebase;
 +      $arg{'tree'}{'hash'} = $treehead if defined $treehead;
 +      $arg{'tree'}{'hash_base'} = $treebase if defined $treebase;
  
        print "<div class=\"page_nav\">\n" .
                (join " | ",
@@@ -1950,9 -1921,9 +1944,9 @@@ sub git_print_header_div 
        my ($action, $title, $hash, $hash_base) = @_;
        my %args = ();
  
 -      $args{action} = $action;
 -      $args{hash} = $hash if $hash;
 -      $args{hash_base} = $hash_base if $hash_base;
 +      $args{'action'} = $action;
 +      $args{'hash'} = $hash if $hash;
 +      $args{'hash_base'} = $hash_base if $hash_base;
  
        print "<div class=\"header\">\n" .
              $cgi->a({-href => href(%args), -class => "title"},
@@@ -1986,7 -1957,7 +1980,7 @@@ sub git_print_page_path 
  
        print "<div class=\"page_path\">";
        print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
-                     -title => 'tree root'}, to_utf8("[$project]"));
+                     -title => 'tree root'}, decode_utf8("[$project]"));
        print " / ";
        if (defined $name) {
                my @dirname = split '/', $name;
@@@ -2398,7 -2369,6 +2392,7 @@@ sub git_patchset_body 
        my ($fd, $difftree, $hash, $hash_parent) = @_;
  
        my $patch_idx = 0;
 +      my $patch_number = 0;
        my $patch_line;
        my $diffinfo;
        my (%from, %to);
                # git diff header
                #assert($patch_line =~ m/^diff /) if DEBUG;
                #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
 +              $patch_number++;
                push @diff_header, $patch_line;
  
                # extended diff header
        } continue {
                print "</div>\n"; # class="patch"
        }
 +      print "<div class=\"diff nodifferences\">No differences found</div>\n" if (!$patch_number);
  
        print "</div>\n"; # class="patchset"
  }
@@@ -2604,7 -2572,7 +2598,7 @@@ sub git_project_list_body 
                ($pr->{'age'}, $pr->{'age_string'}) = @aa;
                if (!defined $pr->{'descr'}) {
                        my $descr = git_get_project_description($pr->{'path'}) || "";
-                       $pr->{'descr_long'} = to_utf8($descr);
+                       $pr->{'descr_long'} = decode_utf8($descr);
                        $pr->{'descr'} = chop_str($descr, 25, 5);
                }
                if (!defined $pr->{'owner'}) {
                push @projects, $pr;
        }
  
 -      $order ||= "project";
 +      $order ||= $default_projects_order;
        $from = 0 unless defined $from;
        $to = $#projects if (!defined $to || $#projects < $to);
  
@@@ -2983,7 -2951,7 +2977,7 @@@ sub git_search_grep_body 
  
  sub git_project_list {
        my $order = $cgi->param('o');
 -      if (defined $order && $order !~ m/project|descr|owner|age/) {
 +      if (defined $order && $order !~ m/none|project|descr|owner|age/) {
                die_error(undef, "Unknown order parameter");
        }
  
  
  sub git_forks {
        my $order = $cgi->param('o');
 -      if (defined $order && $order !~ m/project|descr|owner|age/) {
 +      if (defined $order && $order !~ m/none|project|descr|owner|age/) {
                die_error(undef, "Unknown order parameter");
        }
  
@@@ -3121,7 -3089,7 +3115,7 @@@ sub git_summary 
                git_project_list_body(\@forklist, undef, 0, 15,
                                      $#forklist <= 15 ? undef :
                                      $cgi->a({-href => href(action=>"forks")}, "..."),
 -                                    'noheader');
 +                                    'noheader');
        }
  
        git_footer_html();
@@@ -3228,7 -3196,7 +3222,7 @@@ HTM
                my $rev = substr($full_rev, 0, 8);
                my $author = $meta->{'author'};
                my %date = parse_date($meta->{'author-time'},
 -                                    $meta->{'author-tz'});
 +                                    $meta->{'author-tz'});
                my $date = $date{'iso-tz'};
                if ($group_size) {
                        $current_color = ++$current_color % $num_colors;
                        print " rowspan=\"$group_size\"" if ($group_size > 1);
                        print ">";
                        print $cgi->a({-href => href(action=>"commit",
 -                                                   hash=>$full_rev,
 -                                                   file_name=>$file_name)},
 -                                    esc_html($rev));
 +                                                   hash=>$full_rev,
 +                                                   file_name=>$file_name)},
 +                                    esc_html($rev));
                        print "</td>\n";
                }
                open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
                close $dd;
                chomp($parent_commit);
                my $blamed = href(action => 'blame',
 -                                file_name => $meta->{'filename'},
 -                                hash_base => $parent_commit);
 +                                file_name => $meta->{'filename'},
 +                                hash_base => $parent_commit);
                print "<td class=\"linenr\">";
                print $cgi->a({ -href => "$blamed#l$orig_lineno",
 -                              -id => "l$lineno",
 -                              -class => "linenr" },
 -                            esc_html($lineno));
 +                              -id => "l$lineno",
 +                              -class => "linenr" },
 +                            esc_html($lineno));
                print "</td>";
                print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
                print "</tr>\n";
@@@ -3636,7 -3604,7 +3630,7 @@@ sub git_snapshot 
                $hash = git_get_head_hash($project);
        }
  
-       my $filename = to_utf8(basename($project)) . "-$hash.tar.$suffix";
+       my $filename = decode_utf8(basename($project)) . "-$hash.tar.$suffix";
  
        print $cgi->header(
                -type => "application/$ctype",
        my $name = $project;
        $name =~ s/\047/\047\\\047\047/g;
        open my $fd, "-|",
 -      "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
 +              "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
                or die_error(undef, "Execute git-tar-tree failed");
        binmode STDOUT, ':raw';
        print <$fd>;
@@@ -3760,7 -3728,7 +3754,7 @@@ sub git_commit 
                # difftree output is not printed for merges
                open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
                        @diff_opts, $parent, $hash, "--"
 -                              or die_error(undef, "Open git-diff-tree failed");
 +                      or die_error(undef, "Open git-diff-tree failed");
                @difftree = map { chomp; $_ } <$fd>;
                close $fd or die_error(undef, "Reading git-diff-tree failed");
        }
@@@ -4332,13 -4300,13 +4326,13 @@@ sub git_search 
                if ($page > 0) {
                        $paging_nav .=
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype)},
 -                                      "first");
 +                                                     searchtext=>$searchtext, searchtype=>$searchtype)},
 +                                      "first");
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 -                                                     page=>$page-1),
 -                                       -accesskey => "p", -title => "Alt-p"}, "prev");
 +                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 +                                                     page=>$page-1),
 +                                       -accesskey => "p", -title => "Alt-p"}, "prev");
                } else {
                        $paging_nav .= "first";
                        $paging_nav .= " &sdot; prev";
                if ($#commitlist >= 100) {
                        $paging_nav .= " &sdot; " .
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 -                                                     page=>$page+1),
 -                                       -accesskey => "n", -title => "Alt-n"}, "next");
 +                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 +                                                     page=>$page+1),
 +                                       -accesskey => "n", -title => "Alt-n"}, "next");
                } else {
                        $paging_nav .= " &sdot; next";
                }
                if ($#commitlist >= 100) {
                        $next_link =
                                $cgi->a({-href => href(action=>"search", hash=>$hash,
 -                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 -                                                     page=>$page+1),
 -                                       -accesskey => "n", -title => "Alt-n"}, "next");
 +                                                     searchtext=>$searchtext, searchtype=>$searchtype,
 +                                                     page=>$page+1),
 +                                       -accesskey => "n", -title => "Alt-n"}, "next");
                }
  
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);