]> gitweb.ps.run Git - ps-cgit/commitdiff
Merge branch 'stable'
authorLars Hjemli <hjemli@gmail.com>
Sat, 5 Mar 2011 13:01:59 +0000 (14:01 +0100)
committerLars Hjemli <hjemli@gmail.com>
Sat, 5 Mar 2011 13:01:59 +0000 (14:01 +0100)
1  2 
Makefile
cgit.c
html.c
ui-shared.c

diff --combined Makefile
index a9887517a14f72235f04a86eb3ae4d8ec67a156d,304521e9439a6eb3701ba185ccfce927a10e330f..14b4df41dcf2ae829cab816e2b60769b83cd5262
+++ b/Makefile
@@@ -1,38 -1,18 +1,38 @@@
- CGIT_VERSION = v0.8.3.4
+ CGIT_VERSION = v0.8.3.5
  CGIT_SCRIPT_NAME = cgit.cgi
  CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
  CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
  CGIT_CONFIG = /etc/cgitrc
  CACHE_ROOT = /var/cache/cgit
 +prefix = /usr
 +libdir = $(prefix)/lib
 +filterdir = $(libdir)/cgit/filters
 +docdir = $(prefix)/share/doc/cgit
 +htmldir = $(docdir)
 +pdfdir = $(docdir)
 +mandir = $(prefix)/share/man
  SHA1_HEADER = <openssl/sha.h>
 -GIT_VER = 1.7.3
 +GIT_VER = 1.7.4
  GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
  INSTALL = install
 +MAN5_TXT = $(wildcard *.5.txt)
 +MAN_TXT  = $(MAN5_TXT)
 +DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
 +DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
 +DOC_PDF  = $(patsubst %.txt,%.pdf,$(MAN_TXT))
  
  # Define NO_STRCASESTR if you don't have strcasestr.
  #
 +# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
 +# implementation (slower).
 +#
  # Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
  #
 +# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
 +# do not support the 'size specifiers' introduced by C99, namely ll, hh,
 +# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
 +# some C compilers supported these specifiers prior to C99 as an extension.
 +#
  
  #-include config.mak
  
@@@ -79,7 -59,7 +79,7 @@@ endi
  # Define a pattern rule for automatic dependency building
  #
  %.d: %.c
 -      $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
 +      $(QUIET_MM)$(CC) $(CFLAGS) -MM -MP $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
  
  #
  # Define a pattern rule for silent object building
@@@ -88,7 -68,7 +88,7 @@@
        $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
  
  
 -EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto -lpthread
 +EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread
  OBJECTS =
  OBJECTS += cache.o
  OBJECTS += cgit.o
@@@ -110,12 -90,10 +110,12 @@@ OBJECTS += ui-refs.
  OBJECTS += ui-repolist.o
  OBJECTS += ui-shared.o
  OBJECTS += ui-snapshot.o
 +OBJECTS += ui-ssdiff.o
  OBJECTS += ui-stats.o
  OBJECTS += ui-summary.o
  OBJECTS += ui-tag.o
  OBJECTS += ui-tree.o
 +OBJECTS += vector.o
  
  ifdef NEEDS_LIBICONV
        EXTLIBS += -liconv
@@@ -123,8 -101,7 +123,8 @@@ endi
  
  
  .PHONY: all libgit test install uninstall clean force-version get-git \
 -      doc man-doc html-doc clean-doc
 +      doc clean-doc install-doc install-man install-html install-pdf \
 +      uninstall-doc uninstall-man uninstall-html uninstall-pdf
  
  all: cgit
  
@@@ -140,36 -117,23 +140,36 @@@ CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG
  CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
  CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
  
 +GIT_OPTIONS = prefix=/usr
 +
  ifdef NO_ICONV
        CFLAGS += -DNO_ICONV
  endif
  ifdef NO_STRCASESTR
        CFLAGS += -DNO_STRCASESTR
  endif
 +ifdef NO_C99_FORMAT
 +      CFLAGS += -DNO_C99_FORMAT
 +endif
 +ifdef NO_OPENSSL
 +      CFLAGS += -DNO_OPENSSL
 +      GIT_OPTIONS += NO_OPENSSL=1
 +else
 +      EXTLIBS += -lcrypto
 +endif
  
  cgit: $(OBJECTS) libgit
        $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
  
  cgit.o: VERSION
  
 --include $(OBJECTS:.o=.d)
 +ifneq "$(MAKECMDGOALS)" "clean"
 +  -include $(OBJECTS:.o=.d)
 +endif
  
  libgit:
 -      $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a
 -      $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a
 +      $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
 +      $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
  
  test: all
        $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
@@@ -180,58 -144,21 +180,58 @@@ install: al
        $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
        $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
        $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
 +      $(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir)
 +      $(INSTALL) -m 0755 filters/* $(DESTDIR)$(filterdir)
 +
 +install-doc: install-man install-html install-pdf
 +
 +install-man: doc-man
 +      $(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5
 +      $(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5
 +
 +install-html: doc-html
 +      $(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir)
 +      $(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir)
 +
 +install-pdf: doc-pdf
 +      $(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir)
 +      $(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir)
  
  uninstall:
        rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
        rm -f $(CGIT_DATA_PATH)/cgit.css
        rm -f $(CGIT_DATA_PATH)/cgit.png
  
 -doc: man-doc html-doc pdf-doc
 +uninstall-doc: uninstall-man uninstall-html uninstall-pdf
 +
 +uninstall-man:
 +      @for i in $(DOC_MAN5); do \
 +          rm -fv $(DESTDIR)$(mandir)/man5/$$i; \
 +      done
 +
 +uninstall-html:
 +      @for i in $(DOC_HTML); do \
 +          rm -fv $(DESTDIR)$(htmldir)/$$i; \
 +      done
 +
 +uninstall-pdf:
 +      @for i in $(DOC_PDF); do \
 +          rm -fv $(DESTDIR)$(pdfdir)/$$i; \
 +      done
 +
 +doc: doc-man doc-html doc-pdf
 +doc-man: doc-man5
 +doc-man5: $(DOC_MAN5)
 +doc-html: $(DOC_HTML)
 +doc-pdf: $(DOC_PDF)
  
 -man-doc: cgitrc.5.txt
 -      a2x -f manpage cgitrc.5.txt
 +%.5 : %.5.txt
 +      a2x -f manpage $<
  
 -html-doc: cgitrc.5.txt
 -      a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
 +$(DOC_HTML): %.html : %.txt
 +      a2x -f xhtml --stylesheet=cgit-doc.css $<
  
 -pdf-doc: cgitrc.5.txt
 +$(DOC_PDF): %.pdf : %.txt
        a2x -f pdf cgitrc.5.txt
  
  clean: clean-doc
@@@ -241,4 -168,4 +241,4 @@@ clean-doc
        rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
  
  get-git:
-       curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
+       curl $(GIT_URL) | tar -xjf - && rm -rf git && mv git-$(GIT_VER) git
diff --combined cgit.c
index 916feb4f10dd3f128a10381d39f139cfa318b9a9,af9832fa99f69099fa2e02c47b2dacf1eb6dc7d4..f4dd6ef93c4b1b73d1583df8cab06627e34ae491
--- 1/cgit.c
--- 2/cgit.c
+++ b/cgit.c
@@@ -1,7 -1,6 +1,7 @@@
  /* cgit.c: cgi for the git scm
   *
   * Copyright (C) 2006 Lars Hjemli
 + * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
   *
   * Licensed under GNU General Public License v2
   *   (see COPYING for full license text)
@@@ -57,29 -56,22 +57,29 @@@ void repo_config(struct cgit_repo *repo
                repo->defbranch = xstrdup(value);
        else if (!strcmp(name, "snapshots"))
                repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
 +      else if (!strcmp(name, "enable-commit-graph"))
 +              repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
        else if (!strcmp(name, "enable-log-filecount"))
                repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
        else if (!strcmp(name, "enable-log-linecount"))
                repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
 +      else if (!strcmp(name, "enable-remote-branches"))
 +              repo->enable_remote_branches = atoi(value);
 +      else if (!strcmp(name, "enable-subject-links"))
 +              repo->enable_subject_links = atoi(value);
        else if (!strcmp(name, "max-stats"))
                repo->max_stats = cgit_find_stats_period(value, NULL);
        else if (!strcmp(name, "module-link"))
                repo->module_link= xstrdup(value);
        else if (!strcmp(name, "section"))
                repo->section = xstrdup(value);
 -      else if (!strcmp(name, "readme") && value != NULL) {
 -              if (*value == '/')
 -                      repo->readme = xstrdup(value);
 -              else
 -                      repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
 -      } else if (ctx.cfg.enable_filter_overrides) {
 +      else if (!strcmp(name, "readme") && value != NULL)
 +              repo->readme = xstrdup(value);
 +      else if (!strcmp(name, "logo") && value != NULL)
 +              repo->logo = xstrdup(value);
 +      else if (!strcmp(name, "logo-link") && value != NULL)
 +              repo->logo_link = xstrdup(value);
 +      else if (ctx.cfg.enable_filter_overrides) {
                if (!strcmp(name, "about-filter"))
                        repo->about_filter = new_filter(value, 0);
                else if (!strcmp(name, "commit-filter"))
@@@ -99,8 -91,6 +99,8 @@@ void config_cb(const char *name, const 
                ctx.repo->path = trim_end(value, '/');
        else if (ctx.repo && !prefixcmp(name, "repo."))
                repo_config(ctx.repo, name + 5, value);
 +      else if (!strcmp(name, "readme"))
 +              ctx.cfg.readme = xstrdup(value);
        else if (!strcmp(name, "root-title"))
                ctx.cfg.root_title = xstrdup(value);
        else if (!strcmp(name, "root-desc"))
                ctx.cfg.logo_link = xstrdup(value);
        else if (!strcmp(name, "module-link"))
                ctx.cfg.module_link = xstrdup(value);
 +      else if (!strcmp(name, "strict-export"))
 +              ctx.cfg.strict_export = xstrdup(value);
        else if (!strcmp(name, "virtual-root")) {
                ctx.cfg.virtual_root = trim_end(value, '/');
                if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
                ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
        else if (!strcmp(name, "enable-filter-overrides"))
                ctx.cfg.enable_filter_overrides = atoi(value);
 +      else if (!strcmp(name, "enable-gitweb-owner"))
 +              ctx.cfg.enable_gitweb_owner = atoi(value);
        else if (!strcmp(name, "enable-index-links"))
                ctx.cfg.enable_index_links = atoi(value);
 +      else if (!strcmp(name, "enable-commit-graph"))
 +              ctx.cfg.enable_commit_graph = atoi(value);
        else if (!strcmp(name, "enable-log-filecount"))
                ctx.cfg.enable_log_filecount = atoi(value);
        else if (!strcmp(name, "enable-log-linecount"))
                ctx.cfg.enable_log_linecount = atoi(value);
 +      else if (!strcmp(name, "enable-remote-branches"))
 +              ctx.cfg.enable_remote_branches = atoi(value);
 +      else if (!strcmp(name, "enable-subject-links"))
 +              ctx.cfg.enable_subject_links = atoi(value);
        else if (!strcmp(name, "enable-tree-linenumbers"))
                ctx.cfg.enable_tree_linenumbers = atoi(value);
        else if (!strcmp(name, "max-stats"))
        else if (!strcmp(name, "cache-size"))
                ctx.cfg.cache_size = atoi(value);
        else if (!strcmp(name, "cache-root"))
 -              ctx.cfg.cache_root = xstrdup(value);
 +              ctx.cfg.cache_root = xstrdup(expand_macros(value));
        else if (!strcmp(name, "cache-root-ttl"))
                ctx.cfg.cache_root_ttl = atoi(value);
        else if (!strcmp(name, "cache-repo-ttl"))
                ctx.cfg.commit_filter = new_filter(value, 0);
        else if (!strcmp(name, "embedded"))
                ctx.cfg.embedded = atoi(value);
 +      else if (!strcmp(name, "max-atom-items"))
 +              ctx.cfg.max_atom_items = atoi(value);
        else if (!strcmp(name, "max-message-length"))
                ctx.cfg.max_msg_len = atoi(value);
        else if (!strcmp(name, "max-repodesc-length"))
                ctx.cfg.max_repodesc_len = atoi(value);
 +      else if (!strcmp(name, "max-blob-size"))
 +              ctx.cfg.max_blob_size = atoi(value);
        else if (!strcmp(name, "max-repo-count"))
                ctx.cfg.max_repo_count = atoi(value);
        else if (!strcmp(name, "max-commit-count"))
                ctx.cfg.max_commit_count = atoi(value);
 +      else if (!strcmp(name, "project-list"))
 +              ctx.cfg.project_list = xstrdup(expand_macros(value));
        else if (!strcmp(name, "scan-path"))
                if (!ctx.cfg.nocache && ctx.cfg.cache_size)
 -                      process_cached_repolist(value);
 +                      process_cached_repolist(expand_macros(value));
 +              else if (ctx.cfg.project_list)
 +                      scan_projects(expand_macros(value),
 +                                    ctx.cfg.project_list, repo_config);
                else
 -                      scan_tree(value, repo_config);
 +                      scan_tree(expand_macros(value), repo_config);
 +      else if (!strcmp(name, "scan-hidden-path"))
 +              ctx.cfg.scan_hidden_path = atoi(value);
 +      else if (!strcmp(name, "section-from-path"))
 +              ctx.cfg.section_from_path = atoi(value);
        else if (!strcmp(name, "source-filter"))
                ctx.cfg.source_filter = new_filter(value, 1);
        else if (!strcmp(name, "summary-log"))
                ctx.cfg.summary_branches = atoi(value);
        else if (!strcmp(name, "summary-tags"))
                ctx.cfg.summary_tags = atoi(value);
 +      else if (!strcmp(name, "side-by-side-diffs"))
 +              ctx.cfg.ssdiff = atoi(value);
        else if (!strcmp(name, "agefile"))
                ctx.cfg.agefile = xstrdup(value);
        else if (!strcmp(name, "renamelimit"))
                ctx.cfg.renamelimit = atoi(value);
 +      else if (!strcmp(name, "remove-suffix"))
 +              ctx.cfg.remove_suffix = atoi(value);
        else if (!strcmp(name, "robots"))
                ctx.cfg.robots = xstrdup(value);
        else if (!strcmp(name, "clone-prefix"))
        else if (!prefixcmp(name, "mimetype."))
                add_mimetype(name + 9, value);
        else if (!strcmp(name, "include"))
 -              parse_configfile(value, config_cb);
 +              parse_configfile(expand_macros(value), config_cb);
  }
  
  static void querystring_cb(const char *name, const char *value)
        } else if (!strcmp(name, "p")) {
                ctx.qry.page = xstrdup(value);
        } else if (!strcmp(name, "url")) {
 +              if (*value == '/')
 +                      value++;
                ctx.qry.url = xstrdup(value);
                cgit_parse_url(value);
        } else if (!strcmp(name, "qt")) {
                ctx.qry.showmsg = atoi(value);
        } else if (!strcmp(name, "period")) {
                ctx.qry.period = xstrdup(value);
 +      } else if (!strcmp(name, "ss")) {
 +              ctx.qry.ssdiff = atoi(value);
 +      } else if (!strcmp(name, "all")) {
 +              ctx.qry.show_all = atoi(value);
 +      } else if (!strcmp(name, "context")) {
 +              ctx.qry.context = atoi(value);
 +      } else if (!strcmp(name, "ignorews")) {
 +              ctx.qry.ignorews = atoi(value);
        }
  }
  
@@@ -309,30 -262,23 +309,30 @@@ static void prepare_context(struct cgit
        ctx->cfg.css = "/cgit.css";
        ctx->cfg.logo = "/cgit.png";
        ctx->cfg.local_time = 0;
 +      ctx->cfg.enable_gitweb_owner = 1;
        ctx->cfg.enable_tree_linenumbers = 1;
        ctx->cfg.max_repo_count = 50;
        ctx->cfg.max_commit_count = 50;
        ctx->cfg.max_lock_attempts = 5;
        ctx->cfg.max_msg_len = 80;
        ctx->cfg.max_repodesc_len = 80;
 +      ctx->cfg.max_blob_size = 0;
        ctx->cfg.max_stats = 0;
        ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
 +      ctx->cfg.project_list = NULL;
        ctx->cfg.renamelimit = -1;
 +      ctx->cfg.remove_suffix = 0;
        ctx->cfg.robots = "index, nofollow";
        ctx->cfg.root_title = "Git repository browser";
        ctx->cfg.root_desc = "a fast webinterface for the git dscm";
 +      ctx->cfg.scan_hidden_path = 0;
        ctx->cfg.script_name = CGIT_SCRIPT_NAME;
        ctx->cfg.section = "";
        ctx->cfg.summary_branches = 10;
        ctx->cfg.summary_log = 10;
        ctx->cfg.summary_tags = 10;
 +      ctx->cfg.max_atom_items = 10;
 +      ctx->cfg.ssdiff = 0;
        ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
        ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
        ctx->env.https = xstrdupn(getenv("HTTPS"));
@@@ -464,12 -410,6 +464,12 @@@ static void process_request(void *cbdat
                return;
        }
  
 +      /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
 +       * in-project path limit to be made available at ctx->qry.vpath.
 +       * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
 +       */
 +      ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
 +
        if (cmd->want_repo && !ctx->repo) {
                cgit_print_http_headers(ctx);
                cgit_print_docstart(ctx);
@@@ -551,8 -491,6 +551,8 @@@ void print_repo(FILE *f, struct cgit_re
                fprintf(f, "repo.section=%s\n", repo->section);
        if (repo->clone_url)
                fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
 +      fprintf(f, "repo.enable-commit-graph=%d\n",
 +              repo->enable_commit_graph);
        fprintf(f, "repo.enable-log-filecount=%d\n",
                repo->enable_log_filecount);
        fprintf(f, "repo.enable-log-linecount=%d\n",
@@@ -603,10 -541,7 +603,10 @@@ static int generate_cached_repolist(con
                return errno;
        }
        idx = cgit_repolist.count;
 -      scan_tree(path, repo_config);
 +      if (ctx.cfg.project_list)
 +              scan_projects(path, ctx.cfg.project_list, repo_config);
 +      else
 +              scan_tree(path, repo_config);
        print_repolist(f, &cgit_repolist, idx);
        if (rename(locked_rc, cached_rc))
                fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
@@@ -620,25 -555,17 +620,25 @@@ static void process_cached_repolist(con
        struct stat st;
        char *cached_rc;
        time_t age;
 +      unsigned long hash;
  
 -      cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
 -              hash_str(path)));
 +      hash = hash_str(path);
 +      if (ctx.cfg.project_list)
 +              hash += hash_str(ctx.cfg.project_list);
 +      cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
  
        if (stat(cached_rc, &st)) {
                /* Nothing is cached, we need to scan without forking. And
                 * if we fail to generate a cached repolist, we need to
                 * invoke scan_tree manually.
                 */
 -              if (generate_cached_repolist(path, cached_rc))
 -                      scan_tree(path, repo_config);
 +              if (generate_cached_repolist(path, cached_rc)) {
 +                      if (ctx.cfg.project_list)
 +                              scan_projects(path, ctx.cfg.project_list,
 +                                            repo_config);
 +                      else
 +                              scan_tree(path, repo_config);
 +              }
                return;
        }
  
@@@ -747,15 -674,16 +747,16 @@@ int main(int argc, const char **argv
        cgit_repolist.repos = NULL;
  
        cgit_parse_args(argc, argv);
 -      parse_configfile(ctx.env.cgit_config, config_cb);
 +      parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
        ctx.repo = NULL;
        http_parse_querystring(ctx.qry.raw, querystring_cb);
  
        /* If virtual-root isn't specified in cgitrc, lets pretend
-        * that virtual-root equals SCRIPT_NAME.
+        * that virtual-root equals SCRIPT_NAME, minus any possibly
+        * trailing slashes.
         */
        if (!ctx.cfg.virtual_root)
-               ctx.cfg.virtual_root = ctx.cfg.script_name;
+               ctx.cfg.virtual_root = trim_end(ctx.cfg.script_name, '/');
  
        /* If no url parameter is specified on the querystring, lets
         * use PATH_INFO as url. This allows cgit to work with virtual
diff --combined html.c
index a1fe87d759cee3345d544f2e9ebca49b34f36347,5336596270db92ac1bb8d38b3e5b6e43a4a1a73a..a60bc1386f46a94a2128932d3df8de4760d6590a
--- 1/html.c
--- 2/html.c
+++ b/html.c
  #include <string.h>
  #include <errno.h>
  
 +/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
 +static const char* url_escape_table[256] = {
 +      "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
 +      "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13",
 +      "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d",
 +      "%1e", "%1f", "+", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0,
 +      "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d",
 +      "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b",
 +      "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85",
 +      "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
 +      "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99",
 +      "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3",
 +      "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad",
 +      "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
 +      "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1",
 +      "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb",
 +      "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5",
 +      "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
 +      "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9",
 +      "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3",
 +      "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd",
 +      "%fe", "%ff"
 +};
 +
  int htmlfd = STDOUT_FILENO;
  
  char *fmt(const char *format, ...)
@@@ -89,13 -63,13 +89,13 @@@ void html_status(int code, const char *
                html("\n");
  }
  
 -void html_txt(char *txt)
 +void html_txt(const char *txt)
  {
 -      char *t = txt;
 +      const char *t = txt;
        while(t && *t){
                int c = *t;
                if (c=='<' || c=='>' || c=='&') {
 -                      write(htmlfd, txt, t - txt);
 +                      html_raw(txt, t - txt);
                        if (c=='>')
                                html("&gt;");
                        else if (c=='<')
                html(txt);
  }
  
 -void html_ntxt(int len, char *txt)
 +void html_ntxt(int len, const char *txt)
  {
 -      char *t = txt;
 +      const char *t = txt;
        while(t && *t && len--){
                int c = *t;
                if (c=='<' || c=='>' || c=='&') {
 -                      write(htmlfd, txt, t - txt);
 +                      html_raw(txt, t - txt);
                        if (c=='>')
                                html("&gt;");
                        else if (c=='<')
                t++;
        }
        if (t!=txt)
 -              write(htmlfd, txt, t - txt);
 +              html_raw(txt, t - txt);
        if (len<0)
                html("...");
  }
  
 -void html_attr(char *txt)
 +void html_attr(const char *txt)
  {
 -      char *t = txt;
 +      const char *t = txt;
        while(t && *t){
                int c = *t;
                if (c=='<' || c=='>' || c=='\'' || c=='\"') {
 -                      write(htmlfd, txt, t - txt);
 +                      html_raw(txt, t - txt);
                        if (c=='>')
                                html("&gt;");
                        else if (c=='<')
                html(txt);
  }
  
 -void html_url_path(char *txt)
 +void html_url_path(const char *txt)
  {
 -      char *t = txt;
 +      const char *t = txt;
        while(t && *t){
                int c = *t;
 -              if (c=='"' || c=='#' || c=='\'' || c=='?') {
 -                      write(htmlfd, txt, t - txt);
 -                      write(htmlfd, fmt("%%%2x", c), 3);
 +              const char *e = url_escape_table[c];
 +              if (e && c!='+' && c!='&' && c!='+') {
 +                      html_raw(txt, t - txt);
 +                      html_raw(e, 3);
                        txt = t+1;
                }
                t++;
                html(txt);
  }
  
 -void html_url_arg(char *txt)
 +void html_url_arg(const char *txt)
  {
 -      char *t = txt;
 +      const char *t = txt;
        while(t && *t){
                int c = *t;
 -              if (c=='"' || c=='#' || c=='%' || c=='&' || c=='\'' || c=='+' || c=='?') {
 -                      write(htmlfd, txt, t - txt);
 -                      write(htmlfd, fmt("%%%2x", c), 3);
 +              const char *e = url_escape_table[c];
 +              if (e) {
 +                      html_raw(txt, t - txt);
 +                      html_raw(e, strlen(e));
                        txt = t+1;
                }
                t++;
                html(txt);
  }
  
 -void html_hidden(char *name, char *value)
 +void html_hidden(const char *name, const char *value)
  {
        html("<input type='hidden' name='");
        html_attr(name);
        html("'/>");
  }
  
 -void html_option(char *value, char *text, char *selected_value)
 +void html_option(const char *value, const char *text, const char *selected_value)
  {
        html("<option value='");
        html_attr(value);
        html("</option>\n");
  }
  
 -void html_link_open(char *url, char *title, char *class)
 +void html_link_open(const char *url, const char *title, const char *class)
  {
        html("<a href='");
        html_attr(url);
@@@ -249,7 -221,7 +249,7 @@@ int html_include(const char *filename
                return -1;
        }
        while((len = fread(buf, 1, 4096, f)) > 0)
 -              write(htmlfd, buf, len);
 +              html_raw(buf, len);
        fclose(f);
        return 0;
  }
@@@ -277,7 -249,7 +277,7 @@@ char *convert_query_hexchar(char *txt
        d1 = hextoint(*(txt+1));
        d2 = hextoint(*(txt+2));
        if (d1<0 || d2<0) {
-               memmove(txt, txt+3, n-3);
+               memmove(txt, txt+3, n-2);
                return txt-1;
        } else {
                *txt = d1 * 16 + d2;
        }
  }
  
 -int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value))
 +int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value))
  {
 -      char *t, *value = NULL, c;
 +      char *t, *txt, *value = NULL, c;
  
 -      if (!txt)
 +      if (!txt_)
                return 0;
  
 -      t = txt = strdup(txt);
 +      t = txt = strdup(txt_);
        if (t == NULL) {
                printf("Out of memory\n");
                exit(1);
diff --combined ui-shared.c
index 7efae7ab04982bf6d5f557cba1d9cdc9b91f9ede,d1b020b38c083673a10d932c958560325afda2bd..5aa911956aa3cbf344b437e15aad761f5cd11d86
@@@ -27,7 -27,7 +27,7 @@@ static char *http_date(time_t t
                   tm->tm_hour, tm->tm_min, tm->tm_sec);
  }
  
 -void cgit_print_error(char *msg)
 +void cgit_print_error(const char *msg)
  {
        html("<div class='error'>");
        html_txt(msg);
@@@ -133,7 -133,7 +133,7 @@@ char *cgit_currurl(
                return fmt("%s/", ctx.cfg.virtual_root);
  }
  
 -static void site_url(char *page, char *search, int ofs)
 +static void site_url(const char *page, const char *search, int ofs)
  {
        char *delim = "?";
  
        }
  }
  
 -static void site_link(char *page, char *name, char *title, char *class,
 -                    char *search, int ofs)
 +static void site_link(const char *page, const char *name, const char *title,
 +                    const char *class, const char *search, int ofs)
  {
        html("<a");
        if (title) {
        html("</a>");
  }
  
 -void cgit_index_link(char *name, char *title, char *class, char *pattern,
 -                   int ofs)
 +void cgit_index_link(const char *name, const char *title, const char *class,
 +                   const char *pattern, int ofs)
  {
        site_link(NULL, name, title, class, pattern, ofs);
  }
  
 -static char *repolink(char *title, char *class, char *page, char *head,
 -                    char *path)
 +static char *repolink(const char *title, const char *class, const char *page,
 +                    const char *head, const char *path)
  {
        char *delim = "?";
  
        return fmt("%s", delim);
  }
  
 -static void reporevlink(char *page, char *name, char *title, char *class,
 -                      char *head, char *rev, char *path)
 +static void reporevlink(const char *page, const char *name, const char *title,
 +                      const char *class, const char *head, const char *rev,
 +                      const char *path)
  {
        char *delim;
  
        html("</a>");
  }
  
 -void cgit_summary_link(char *name, char *title, char *class, char *head)
 +void cgit_summary_link(const char *name, const char *title, const char *class,
 +                     const char *head)
  {
        reporevlink(NULL, name, title, class, head, NULL, NULL);
  }
  
 -void cgit_tag_link(char *name, char *title, char *class, char *head,
 -                 char *rev)
 +void cgit_tag_link(const char *name, const char *title, const char *class,
 +                 const char *head, const char *rev)
  {
        reporevlink("tag", name, title, class, head, rev, NULL);
  }
  
 -void cgit_tree_link(char *name, char *title, char *class, char *head,
 -                  char *rev, char *path)
 +void cgit_tree_link(const char *name, const char *title, const char *class,
 +                  const char *head, const char *rev, const char *path)
  {
        reporevlink("tree", name, title, class, head, rev, path);
  }
  
 -void cgit_plain_link(char *name, char *title, char *class, char *head,
 -                   char *rev, char *path)
 +void cgit_plain_link(const char *name, const char *title, const char *class,
 +                   const char *head, const char *rev, const char *path)
  {
        reporevlink("plain", name, title, class, head, rev, path);
  }
  
 -void cgit_log_link(char *name, char *title, char *class, char *head,
 -                 char *rev, char *path, int ofs, char *grep, char *pattern,
 -                 int showmsg)
 +void cgit_log_link(const char *name, const char *title, const char *class,
 +                 const char *head, const char *rev, const char *path,
 +                 int ofs, const char *grep, const char *pattern, int showmsg)
  {
        char *delim;
  
        html("</a>");
  }
  
 -void cgit_commit_link(char *name, char *title, char *class, char *head,
 -                    char *rev)
 +void cgit_commit_link(char *name, const char *title, const char *class,
 +                    const char *head, const char *rev, const char *path,
 +                    int toggle_ssdiff)
  {
        if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
                name[ctx.cfg.max_msg_len] = '\0';
                name[ctx.cfg.max_msg_len - 2] = '.';
                name[ctx.cfg.max_msg_len - 3] = '.';
        }
 -      reporevlink("commit", name, title, class, head, rev, NULL);
 +
 +      char *delim;
 +
 +      delim = repolink(title, class, "commit", head, path);
 +      if (rev && strcmp(rev, ctx.qry.head)) {
 +              html(delim);
 +              html("id=");
 +              html_url_arg(rev);
 +              delim = "&amp;";
 +      }
 +      if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
 +              html(delim);
 +              html("ss=1");
 +              delim = "&amp;";
 +      }
 +      if (ctx.qry.context > 0 && ctx.qry.context != 3) {
 +              html(delim);
 +              html("context=");
 +              htmlf("%d", ctx.qry.context);
 +              delim = "&amp;";
 +      }
 +      if (ctx.qry.ignorews) {
 +              html(delim);
 +              html("ignorews=1");
 +              delim = "&amp;";
 +      }
 +      html("'>");
 +      html_txt(name);
 +      html("</a>");
  }
  
 -void cgit_refs_link(char *name, char *title, char *class, char *head,
 -                  char *rev, char *path)
 +void cgit_refs_link(const char *name, const char *title, const char *class,
 +                  const char *head, const char *rev, const char *path)
  {
        reporevlink("refs", name, title, class, head, rev, path);
  }
  
 -void cgit_snapshot_link(char *name, char *title, char *class, char *head,
 -                      char *rev, char *archivename)
 +void cgit_snapshot_link(const char *name, const char *title, const char *class,
 +                      const char *head, const char *rev,
 +                      const char *archivename)
  {
        reporevlink("snapshot", name, title, class, head, rev, archivename);
  }
  
 -void cgit_diff_link(char *name, char *title, char *class, char *head,
 -                  char *new_rev, char *old_rev, char *path)
 +void cgit_diff_link(const char *name, const char *title, const char *class,
 +                  const char *head, const char *new_rev, const char *old_rev,
 +                  const char *path, int toggle_ssdiff)
  {
        char *delim;
  
                html(delim);
                html("id2=");
                html_url_arg(old_rev);
 +              delim = "&amp;";
 +      }
 +      if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
 +              html(delim);
 +              html("ss=1");
 +              delim = "&amp;";
 +      }
 +      if (ctx.qry.context > 0 && ctx.qry.context != 3) {
 +              html(delim);
 +              html("context=");
 +              htmlf("%d", ctx.qry.context);
 +              delim = "&amp;";
 +      }
 +      if (ctx.qry.ignorews) {
 +              html(delim);
 +              html("ignorews=1");
 +              delim = "&amp;";
        }
        html("'>");
        html_txt(name);
        html("</a>");
  }
  
 -void cgit_patch_link(char *name, char *title, char *class, char *head,
 -                   char *rev)
 +void cgit_patch_link(const char *name, const char *title, const char *class,
 +                   const char *head, const char *rev, const char *path)
  {
 -      reporevlink("patch", name, title, class, head, rev, NULL);
 +      reporevlink("patch", name, title, class, head, rev, path);
  }
  
 -void cgit_stats_link(char *name, char *title, char *class, char *head,
 -                   char *path)
 +void cgit_stats_link(const char *name, const char *title, const char *class,
 +                   const char *head, const char *path)
  {
        reporevlink("stats", name, title, class, head, NULL, path);
  }
  
 +void cgit_self_link(char *name, const char *title, const char *class,
 +                  struct cgit_context *ctx)
 +{
 +      if (!strcmp(ctx->qry.page, "repolist"))
 +              return cgit_index_link(name, title, class, ctx->qry.search,
 +                                     ctx->qry.ofs);
 +      else if (!strcmp(ctx->qry.page, "summary"))
 +              return cgit_summary_link(name, title, class, ctx->qry.head);
 +      else if (!strcmp(ctx->qry.page, "tag"))
 +              return cgit_tag_link(name, title, class, ctx->qry.head,
 +                                   ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
 +      else if (!strcmp(ctx->qry.page, "tree"))
 +              return cgit_tree_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
 +                                    ctx->qry.path);
 +      else if (!strcmp(ctx->qry.page, "plain"))
 +              return cgit_plain_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
 +                                    ctx->qry.path);
 +      else if (!strcmp(ctx->qry.page, "log"))
 +              return cgit_log_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
 +                                    ctx->qry.path, ctx->qry.ofs,
 +                                    ctx->qry.grep, ctx->qry.search,
 +                                    ctx->qry.showmsg);
 +      else if (!strcmp(ctx->qry.page, "commit"))
 +              return cgit_commit_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
 +                                    ctx->qry.path, 0);
 +      else if (!strcmp(ctx->qry.page, "patch"))
 +              return cgit_patch_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
 +                                    ctx->qry.path);
 +      else if (!strcmp(ctx->qry.page, "refs"))
 +              return cgit_refs_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
 +                                    ctx->qry.path);
 +      else if (!strcmp(ctx->qry.page, "snapshot"))
 +              return cgit_snapshot_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
 +                                    ctx->qry.path);
 +      else if (!strcmp(ctx->qry.page, "diff"))
 +              return cgit_diff_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.sha1, ctx->qry.sha2,
 +                                    ctx->qry.path, 0);
 +      else if (!strcmp(ctx->qry.page, "stats"))
 +              return cgit_stats_link(name, title, class, ctx->qry.head,
 +                                    ctx->qry.path);
 +
 +      /* Don't known how to make link for this page */
 +      repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
 +      html("><!-- cgit_self_link() doesn't know how to make link for page '");
 +      html_txt(ctx->qry.page);
 +      html("' -->");
 +      html_txt(name);
 +      html("</a>");
 +}
 +
  void cgit_object_link(struct object *obj)
  {
        char *page, *shortrev, *fullrev, *name;
        shortrev[10] = '\0';
        if (obj->type == OBJ_COMMIT) {
                  cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
 -                               ctx.qry.head, fullrev);
 +                               ctx.qry.head, fullrev, NULL, 0);
                return;
        } else if (obj->type == OBJ_TREE)
                page = "tree";
        reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
  }
  
 -void cgit_print_date(time_t secs, char *format, int local_time)
 +void cgit_print_date(time_t secs, const char *format, int local_time)
  {
        char buf[64];
        struct tm *time;
        html_txt(buf);
  }
  
 -void cgit_print_age(time_t t, time_t max_relative, char *format)
 +void cgit_print_age(time_t t, time_t max_relative, const char *format)
  {
        time_t now, secs;
  
@@@ -574,7 -466,7 +574,7 @@@ void cgit_print_http_headers(struct cgi
        else if (ctx->page.mimetype)
                htmlf("Content-Type: %s\n", ctx->page.mimetype);
        if (ctx->page.size)
-               htmlf("Content-Length: %ld\n", ctx->page.size);
+               htmlf("Content-Length: %zd\n", ctx->page.size);
        if (ctx->page.filename)
                htmlf("Content-Disposition: inline; filename=\"%s\"\n",
                      ctx->page.filename);
@@@ -617,7 -509,7 +617,7 @@@ void cgit_print_docstart(struct cgit_co
                html("<link rel='alternate' title='Atom feed' href='");
                html(cgit_httpscheme());
                html_attr(cgit_hosturl());
 -              html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
 +              html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
                                       fmt("h=%s", ctx->qry.head)));
                html("' type='application/atom+xml'/>\n");
        }
@@@ -697,15 -589,14 +697,15 @@@ int print_archive_ref(const char *refna
        return 0;
  }
  
 -void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
 +void cgit_add_hidden_formfields(int incl_head, int incl_search,
 +                              const char *page)
  {
        char *url;
  
        if (!ctx.cfg.virtual_root) {
                url = fmt("%s/%s", ctx.qry.repo, page);
 -              if (ctx.qry.path)
 -                      url = fmt("%s/%s", url, ctx.qry.path);
 +              if (ctx.qry.vpath)
 +                      url = fmt("%s/%s", url, ctx.qry.vpath);
                html_hidden("url", url);
        }
  
        }
  }
  
 -const char *fallback_cmd = "repolist";
 +static const char *hc(struct cgit_context *ctx, const char *page)
 +{
 +      return strcmp(ctx->qry.page, page) ? NULL : "active";
 +}
  
 -char *hc(struct cgit_cmd *cmd, const char *page)
 +static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
  {
 -      return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
 +      char *old_path = ctx->qry.path;
 +      char *p = path, *q, *end = path + strlen(path);
 +
 +      ctx->qry.path = NULL;
 +      cgit_self_link("root", NULL, NULL, ctx);
 +      ctx->qry.path = p = path;
 +      while (p < end) {
 +              if (!(q = strchr(p, '/')))
 +                      q = end;
 +              *q = '\0';
 +              html_txt("/");
 +              cgit_self_link(p, NULL, NULL, ctx);
 +              if (q < end)
 +                      *q = '/';
 +              p = q + 1;
 +      }
 +      ctx->qry.path = old_path;
  }
  
  static void print_header(struct cgit_context *ctx)
  {
 +      char *logo = NULL, *logo_link = NULL;
 +
        html("<table id='header'>\n");
        html("<tr>\n");
  
 -      if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
 +      if (ctx->repo && ctx->repo->logo && *ctx->repo->logo)
 +              logo = ctx->repo->logo;
 +      else
 +              logo = ctx->cfg.logo;
 +      if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link)
 +              logo_link = ctx->repo->logo_link;
 +      else
 +              logo_link = ctx->cfg.logo_link;
 +      if (logo && *logo) {
                html("<td class='logo' rowspan='2'><a href='");
 -              if (ctx->cfg.logo_link)
 -                      html_attr(ctx->cfg.logo_link);
 +              if (logo_link && *logo_link)
 +                      html_attr(logo_link);
                else
                        html_attr(cgit_rooturl());
                html("'><img src='");
 -              html_attr(ctx->cfg.logo);
 +              html_attr(logo);
                html("' alt='cgit logo'/></a></td>\n");
        }
  
  
  void cgit_print_pageheader(struct cgit_context *ctx)
  {
 -      struct cgit_cmd *cmd = cgit_get_cmd(ctx);
 -
 -      if (!cmd && ctx->repo)
 -              fallback_cmd = "summary";
 -
        html("<div id='cgit'>");
        if (!ctx->cfg.noheader)
                print_header(ctx);
  
        html("<table class='tabs'><tr><td>\n");
        if (ctx->repo) {
 -              cgit_summary_link("summary", NULL, hc(cmd, "summary"),
 +              cgit_summary_link("summary", NULL, hc(ctx, "summary"),
                                  ctx->qry.head);
 -              cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
 +              cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
                               ctx->qry.sha1, NULL);
 -              cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
 -                            NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
 -              cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
 -                             ctx->qry.sha1, NULL);
 -              cgit_commit_link("commit", NULL, hc(cmd, "commit"),
 -                               ctx->qry.head, ctx->qry.sha1);
 -              cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
 -                             ctx->qry.sha1, ctx->qry.sha2, NULL);
 +              cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
 +                            NULL, ctx->qry.vpath, 0, NULL, NULL,
 +                            ctx->qry.showmsg);
 +              cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
 +                             ctx->qry.sha1, ctx->qry.vpath);
 +              cgit_commit_link("commit", NULL, hc(ctx, "commit"),
 +                               ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
 +              cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
 +                             ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
                if (ctx->repo->max_stats)
 -                      cgit_stats_link("stats", NULL, hc(cmd, "stats"),
 -                                      ctx->qry.head, NULL);
 +                      cgit_stats_link("stats", NULL, hc(ctx, "stats"),
 +                                      ctx->qry.head, ctx->qry.vpath);
                if (ctx->repo->readme)
                        reporevlink("about", "about", NULL,
 -                                  hc(cmd, "about"), ctx->qry.head, NULL,
 +                                  hc(ctx, "about"), ctx->qry.head, NULL,
                                    NULL);
                html("</td><td class='form'>");
                html("<form class='right' method='get' action='");
                if (ctx->cfg.virtual_root)
                        html_url_path(cgit_fileurl(ctx->qry.repo, "log",
 -                                                 ctx->qry.path, NULL));
 +                                                 ctx->qry.vpath, NULL));
                html("'>\n");
                cgit_add_hidden_formfields(1, 0, "log");
                html("<select name='qt'>\n");
                html_option("grep", "log msg", ctx->qry.grep);
                html_option("author", "author", ctx->qry.grep);
                html_option("committer", "committer", ctx->qry.grep);
 +              html_option("range", "range", ctx->qry.grep);
                html("</select>\n");
                html("<input class='txt' type='text' size='10' name='q' value='");
                html_attr(ctx->qry.search);
                html("<input type='submit' value='search'/>\n");
                html("</form>\n");
        } else {
 -              site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
 +              site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
                if (ctx->cfg.root_readme)
 -                      site_link("about", "about", NULL, hc(cmd, "about"),
 +                      site_link("about", "about", NULL, hc(ctx, "about"),
                                  NULL, 0);
                html("</td><td class='form'>");
                html("<form method='get' action='");
                html("</form>");
        }
        html("</td></tr></table>\n");
 +      if (ctx->qry.vpath) {
 +              html("<div class='path'>");
 +              html("path: ");
 +              cgit_print_path_crumbs(ctx, ctx->qry.vpath);
 +              html("</div>");
 +      }
        html("<div class='content'>");
  }
  
@@@ -901,18 -760,13 +901,18 @@@ void cgit_print_snapshot_links(const ch
                               const char *hex, int snapshots)
  {
        const struct cgit_snapshot_format* f;
 +      char *prefix;
        char *filename;
 +      unsigned char sha1[20];
  
 +      if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
 +          (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
 +              hex++;
 +      prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
        for (f = cgit_snapshot_formats; f->suffix; f++) {
                if (!(snapshots & f->bit))
                        continue;
 -              filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
 -                             f->suffix);
 +              filename = fmt("%s%s", prefix, f->suffix);
                cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
                html("<br/>");
        }