X-Git-Url: http://git.pld-linux.org/?p=packages%2Fcgit.git;a=blobdiff_plain;f=git.patch;fp=git.patch;h=24807e0dc30736ef8543d3fa9f627ebc0d2b47b0;hp=0000000000000000000000000000000000000000;hb=579c7c9de633fec4b5ca7dd2cee2a33ad8f746bd;hpb=6e6013969d0665bc1c4a2923270b00c08b8c3f45 diff --git a/git.patch b/git.patch new file mode 100644 index 0000000..24807e0 --- /dev/null +++ b/git.patch @@ -0,0 +1,2151 @@ +diff --git a/Makefile b/Makefile +index bbce29d..1127961 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,3 +1,5 @@ ++all:: ++ + CGIT_VERSION = v0.9.1 + CGIT_SCRIPT_NAME = cgit.cgi + CGIT_SCRIPT_PATH = /var/www/htdocs/cgit +@@ -12,8 +14,8 @@ htmldir = $(docdir) + pdfdir = $(docdir) + mandir = $(prefix)/share/man + SHA1_HEADER = +-GIT_VER = 1.7.4 +-GIT_URL = https://github.com/git/git/archive/v$(GIT_VER).tar.gz ++GIT_VER = 1.7.12.4 ++GIT_URL = https://git-core.googlecode.com/files/git-$(GIT_VER).tar.gz + INSTALL = install + MAN5_TXT = $(wildcard *.5.txt) + MAN_TXT = $(MAN5_TXT) +@@ -40,22 +42,14 @@ DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT)) + # Platform specific tweaks + # + ++VERSION: force-version ++ @./gen-version.sh "$(CGIT_VERSION)" ++-include VERSION ++ + uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') + uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') + uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') + +-ifeq ($(uname_O),Cygwin) +- NO_STRCASESTR = YesPlease +- NEEDS_LIBICONV = YesPlease +-endif +- +-ifeq ($(uname_S),$(filter $(uname_S),FreeBSD OpenBSD)) +- # Apparantly libiconv is installed in /usr/local on BSD +- LDFLAGS ?= -L/usr/local/lib +- CFLAGS ?= -I/usr/local/include +- NEEDS_LIBICONV = yes +-endif +- + # + # Let the user override the above settings. + # +@@ -76,30 +70,62 @@ endif + + ifndef V + QUIET_CC = @echo ' ' CC $@; +- QUIET_MM = @echo ' ' MM $@; ++ QUIET_LINK = @echo ' ' LINK $@; + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir + QUIET_TAGS = @echo ' ' TAGS $@; ++ export V + endif + +-# +-# Define a pattern rule for automatic dependency building +-# +-%.d: %.c +- $(QUIET_MM)$(CC) $(CFLAGS) -MM -MP $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ ++LDFLAGS ?= ++CFLAGS ?= -g -Wall ++CFLAGS += -Igit ++CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' ++CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' ++CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' ++CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' ++CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' + +-# +-# Define a pattern rule for silent object building +-# +-%.o: %.c +- $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< ++ifeq ($(uname_O),Cygwin) ++ NO_STRCASESTR = YesPlease ++ NEEDS_LIBICONV = YesPlease ++endif + ++ifeq ($(uname_S),$(filter $(uname_S),FreeBSD OpenBSD)) ++ # Apparantly libiconv is installed in /usr/local on BSD ++ LDFLAGS += -L/usr/local/lib ++ CFLAGS += -I/usr/local/include ++ NEEDS_LIBICONV = yes ++endif + +-EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread ++GIT_OPTIONS = prefix=/usr NO_GETTEXT=1 + OBJECTS = +-OBJECTS += cache.o ++ ++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 ++ LDLIBS += -lcrypto ++endif ++ ++ifdef NEEDS_LIBICONV ++ LDLIBS += -liconv ++endif ++ ++LDLIBS += git/libgit.a git/xdiff/lib.a -lz -lpthread ++ + OBJECTS += cgit.o ++OBJECTS += cache.o + OBJECTS += cmd.o + OBJECTS += configfile.o + OBJECTS += html.o +@@ -125,55 +151,30 @@ OBJECTS += ui-tag.o + OBJECTS += ui-tree.o + OBJECTS += vector.o + +-ifdef NEEDS_LIBICONV +- EXTLIBS += -liconv +-endif +- +- +-.PHONY: all libgit test install uninstall clean force-version get-git \ +- doc clean-doc install-doc install-man install-html install-pdf \ +- uninstall-doc uninstall-man uninstall-html uninstall-pdf tags ++dep_files := $(foreach f,$(OBJECTS),$(dir $f).deps/$(notdir $f).d) ++dep_dirs := $(addsuffix .deps,$(sort $(dir $OBJECTS))) + +-all: cgit ++$(dep_dirs): ++ @mkdir -p $@ + +-VERSION: force-version +- @./gen-version.sh "$(CGIT_VERSION)" +--include VERSION ++missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs)) ++dep_file = $(dir $@).deps/$(notdir $@).d ++dep_args = -MF $(dep_file) -MMD -MP + ++.SUFFIXES: + +-CFLAGS += -g -Wall -Igit +-CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' +-CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' +-CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' +-CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' +-CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' +- +-GIT_OPTIONS = prefix=/usr ++$(OBJECTS): %.o: %.c $(missing_dep_dirs) ++ $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(CFLAGS) $< + +-ifdef NO_ICONV +- CFLAGS += -DNO_ICONV ++dep_files_present := $(wildcard $(dep_files)) ++ifneq ($(dep_files_present),) ++include $(dep_files_present) + 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 ++all:: cgit + +-ifneq "$(MAKECMDGOALS)" "clean" +- -include $(OBJECTS:.o=.d) +-endif ++cgit: VERSION $(OBJECTS) libgit ++ $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJECTS) $(LDLIBS) + + libgit: + $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a +@@ -243,13 +244,24 @@ $(DOC_PDF): %.pdf : %.txt + a2x -f pdf cgitrc.5.txt + + clean: clean-doc +- rm -f cgit VERSION *.o *.d tags ++ $(RM) cgit VERSION *.o tags ++ $(RM) -r .deps ++ ++cleanall: clean ++ $(MAKE) -C git clean + + clean-doc: +- rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo ++ $(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo + + get-git: + curl -L $(GIT_URL) | tar -xzf - && rm -rf git && mv git-$(GIT_VER) git + + tags: + $(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags ++ ++.PHONY: all cgit get-git libgit force-version ++.PHONY: clean clean-doc cleanall ++.PHONY: doc doc-html doc-man doc-pdf ++.PHONY: install install-doc install-html install-man install-pdf ++.PHONY: tags test ++.PHONY: uninstall uninstall-doc uninstall-html uninstall-man uninstall-pdf +diff --git a/cache.c b/cache.c +index d7a8d5a..47cdcb4 100644 +--- a/cache.c ++++ b/cache.c +@@ -105,7 +105,7 @@ static int is_expired(struct cache_slot *slot) + if (slot->ttl < 0) + return 0; + else +- return slot->cache_st.st_mtime + slot->ttl*60 < time(NULL); ++ return slot->cache_st.st_mtime + slot->ttl * 60 < time(NULL); + } + + /* Check if the slot has been modified since we opened it. +@@ -141,8 +141,8 @@ static int close_lock(struct cache_slot *slot) + */ + static int lock_slot(struct cache_slot *slot) + { +- slot->lock_fd = open(slot->lock_name, O_RDWR|O_CREAT|O_EXCL, +- S_IRUSR|S_IWUSR); ++ slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT | O_EXCL, ++ S_IRUSR | S_IWUSR); + if (slot->lock_fd == -1) + return errno; + if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0) +@@ -214,7 +214,7 @@ unsigned long hash_str(const char *str) + if (!s) + return h; + +- while(*s) { ++ while (*s) { + h *= FNV_PRIME; + h ^= *s++; + } +@@ -342,7 +342,7 @@ int cache_process(int size, const char *path, const char *key, int ttl, + strcpy(filename, path); + if (filename[len - 1] != '/') + filename[len++] = '/'; +- for(i = 0; i < 8; i++) { ++ for (i = 0; i < 8; i++) { + sprintf(filename + len++, "%x", + (unsigned char)(hash & 0xf)); + hash >>= 4; +@@ -407,7 +407,7 @@ int cache_ls(const char *path) + *name = '\0'; + } + slot.cache_name = fullname; +- while((ent = readdir(dir)) != NULL) { ++ while ((ent = readdir(dir)) != NULL) { + if (strlen(ent->d_name) != 8) + continue; + strcpy(name, ent->d_name); +diff --git a/cgit.c b/cgit.c +index a97ed69..2ccf864 100644 +--- a/cgit.c ++++ b/cgit.c +@@ -172,6 +172,8 @@ void config_cb(const char *name, const char *value) + ctx.cfg.enable_http_clone = atoi(value); + else if (!strcmp(name, "enable-index-links")) + ctx.cfg.enable_index_links = atoi(value); ++ else if (!strcmp(name, "enable-index-owner")) ++ ctx.cfg.enable_index_owner = atoi(value); + else if (!strcmp(name, "enable-commit-graph")) + ctx.cfg.enable_commit_graph = atoi(value); + else if (!strcmp(name, "enable-log-filecount")) +@@ -313,7 +315,7 @@ static void querystring_cb(const char *name, const char *value) + ctx.qry.name = xstrdup(value); + } else if (!strcmp(name, "mimetype")) { + ctx.qry.mimetype = xstrdup(value); +- } else if (!strcmp(name, "s")){ ++ } else if (!strcmp(name, "s")) { + ctx.qry.sort = xstrdup(value); + } else if (!strcmp(name, "showmsg")) { + ctx.qry.showmsg = atoi(value); +@@ -354,6 +356,7 @@ static void prepare_context(struct cgit_context *ctx) + ctx->cfg.logo = "/cgit.png"; + ctx->cfg.local_time = 0; + ctx->cfg.enable_http_clone = 1; ++ ctx->cfg.enable_index_owner = 1; + ctx->cfg.enable_tree_linenumbers = 1; + ctx->cfg.enable_git_config = 0; + ctx->cfg.max_repo_count = 50; +@@ -442,12 +445,12 @@ char *find_default_branch(struct cgit_repo *repo) + return ref; + } + +-static char *guess_defbranch(const char *repo_path) ++static char *guess_defbranch(void) + { + const char *ref; + unsigned char sha1[20]; + +- ref = resolve_ref("HEAD", sha1, 0, NULL); ++ ref = resolve_ref_unsafe("HEAD", sha1, 0, NULL); + if (!ref || prefixcmp(ref, "refs/heads/")) + return "master"; + return xstrdup(ref + 11); +@@ -480,7 +483,7 @@ static int prepare_repo_cmd(struct cgit_context *ctx) + ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); + + if (!ctx->repo->defbranch) +- ctx->repo->defbranch = guess_defbranch(ctx->repo->path); ++ ctx->repo->defbranch = guess_defbranch(); + + if (!ctx->qry.head) { + ctx->qry.nohead = 1; +@@ -650,7 +653,7 @@ void print_repolist(FILE *f, struct cgit_repolist *list, int start) + { + int i; + +- for(i = start; i < list->count; i++) ++ for (i = start; i < list->count; i++) + print_repo(f, &list->repos[i]); + } + +@@ -738,7 +741,7 @@ static void cgit_parse_args(int argc, const char **argv) + + for (i = 1; i < argc; i++) { + if (!strncmp(argv[i], "--cache=", 8)) { +- ctx.cfg.cache_root = xstrdup(argv[i]+8); ++ ctx.cfg.cache_root = xstrdup(argv[i] + 8); + } + if (!strcmp(argv[i], "--nocache")) { + ctx.cfg.nocache = 1; +@@ -747,24 +750,24 @@ static void cgit_parse_args(int argc, const char **argv) + ctx.env.no_http = "1"; + } + if (!strncmp(argv[i], "--query=", 8)) { +- ctx.qry.raw = xstrdup(argv[i]+8); ++ ctx.qry.raw = xstrdup(argv[i] + 8); + } + if (!strncmp(argv[i], "--repo=", 7)) { +- ctx.qry.repo = xstrdup(argv[i]+7); ++ ctx.qry.repo = xstrdup(argv[i] + 7); + } + if (!strncmp(argv[i], "--page=", 7)) { +- ctx.qry.page = xstrdup(argv[i]+7); ++ ctx.qry.page = xstrdup(argv[i] + 7); + } + if (!strncmp(argv[i], "--head=", 7)) { +- ctx.qry.head = xstrdup(argv[i]+7); ++ ctx.qry.head = xstrdup(argv[i] + 7); + ctx.qry.has_symref = 1; + } + if (!strncmp(argv[i], "--sha1=", 7)) { +- ctx.qry.sha1 = xstrdup(argv[i]+7); ++ ctx.qry.sha1 = xstrdup(argv[i] + 7); + ctx.qry.has_sha1 = 1; + } + if (!strncmp(argv[i], "--ofs=", 6)) { +- ctx.qry.ofs = atoi(argv[i]+6); ++ ctx.qry.ofs = atoi(argv[i] + 6); + } + if (!strncmp(argv[i], "--scan-tree=", 12) || + !strncmp(argv[i], "--scan-path=", 12)) { +@@ -831,7 +834,7 @@ int main(int argc, const char **argv) + ctx.cfg.virtual_root = trim_end(ctx.cfg.script_name, '/'); + if (!ctx.cfg.virtual_root) + ctx.cfg.virtual_root = ""; +- } ++ } + + /* If no url parameter is specified on the querystring, lets + * use PATH_INFO as url. This allows cgit to work with virtual +@@ -853,7 +856,7 @@ int main(int argc, const char **argv) + } + + ttl = calc_ttl(); +- ctx.page.expires += ttl*60; ++ ctx.page.expires += ttl * 60; + if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) + ctx.cfg.nocache = 1; + if (ctx.cfg.nocache) +diff --git a/cgit.h b/cgit.h +index 7a99135..c655bd8 100644 +--- a/cgit.h ++++ b/cgit.h +@@ -11,6 +11,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -203,6 +204,7 @@ struct cgit_config { + int enable_filter_overrides; + int enable_http_clone; + int enable_index_links; ++ int enable_index_owner; + int enable_commit_graph; + int enable_log_filecount; + int enable_log_linecount; +@@ -273,6 +275,8 @@ struct cgit_context { + struct cgit_page page; + }; + ++typedef int (*write_archive_fn_t)(const char *, const char *); ++ + struct cgit_snapshot_format { + const char *suffix; + const char *mimetype; +diff --git a/cgitrc.5.txt b/cgitrc.5.txt +index 95a1049..4d27d9f 100644 +--- a/cgitrc.5.txt ++++ b/cgitrc.5.txt +@@ -120,6 +120,10 @@ enable-index-links:: + each repo in the repository index (specifically, to the "summary", + "commit" and "tree" pages). Default value: "0". + ++enable-index-owner:: ++ Flag which, when set to "1", will make cgit display the owner of ++ each repo in the repository index. Default value: "1". ++ + enable-log-filecount:: + Flag which, when set to "1", will make cgit print the number of + modified files for each commit on the repository log page. Default +@@ -244,8 +248,8 @@ mimetype-file:: + Specifies the file to use for automatic mimetype lookup. If specified + then this field is used as a fallback when no "mimetype." match is + found. If unspecified then no such lookup is performed. The typical file +- to use on a Linux system is /etc/mime.types. Default value: none. See +- also: "mimetype.". The format of the file must comply to: ++ to use on a Linux system is /etc/mime.types. The format of the file must ++ comply to: + - a comment line is an empty line or a line starting with a hash (#), + optionally preceded by whitespace + - a non-comment line starts with the mimetype (like image/png), followed +diff --git a/cmd.c b/cmd.c +index 899e913..198bf2f 100644 +--- a/cmd.c ++++ b/cmd.c +@@ -166,7 +166,7 @@ struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) + ctx->qry.page = "repolist"; + } + +- for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) ++ for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) + if (!strcmp(ctx->qry.page, cmds[i].name)) + return &cmds[i]; + return NULL; +diff --git a/configfile.c b/configfile.c +index 4908058..3fa217f 100644 +--- a/configfile.c ++++ b/configfile.c +@@ -13,9 +13,9 @@ + int next_char(FILE *f) + { + int c = fgetc(f); +- if (c=='\r') { ++ if (c == '\r') { + c = fgetc(f); +- if (c!='\n') { ++ if (c != '\n') { + ungetc(c, f); + c = '\r'; + } +@@ -27,7 +27,7 @@ void skip_line(FILE *f) + { + int c; + +- while((c=next_char(f)) && c!='\n' && c!=EOF) ++ while ((c = next_char(f)) && c != '\n' && c != EOF) + ; + } + +@@ -36,31 +36,31 @@ int read_config_line(FILE *f, char *line, const char **value, int bufsize) + int i = 0, isname = 0; + + *value = NULL; +- while(i 0) ++ while ((len = read_config_line(f, line, &value, sizeof(line))) > 0) + fn(line, value); + nesting--; + fclose(f); +diff --git a/html.c b/html.c +index 8f6e4f6..90cc1c0 100644 +--- a/html.c ++++ b/html.c +@@ -54,7 +54,7 @@ char *fmt(const char *format, ...) + va_start(args, format); + len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); + va_end(args); +- if (len>sizeof(buf[bufidx])) { ++ if (len > sizeof(buf[bufidx])) { + fprintf(stderr, "[html.c] string truncated: %s\n", format); + exit(1); + } +@@ -92,93 +92,93 @@ void html_status(int code, const char *msg, int more_headers) + void html_txt(const char *txt) + { + const char *t = txt; +- while(t && *t){ ++ while (t && *t) { + int c = *t; +- if (c=='<' || c=='>' || c=='&') { ++ if (c == '<' || c == '>' || c == '&') { + html_raw(txt, t - txt); +- if (c=='>') ++ if (c == '>') + html(">"); +- else if (c=='<') ++ else if (c == '<') + html("<"); +- else if (c=='&') ++ else if (c == '&') + html("&"); +- txt = t+1; ++ txt = t + 1; + } + t++; + } +- if (t!=txt) ++ if (t != txt) + html(txt); + } + + void html_ntxt(int len, const char *txt) + { + const char *t = txt; +- while(t && *t && len--){ ++ while (t && *t && len--) { + int c = *t; +- if (c=='<' || c=='>' || c=='&') { ++ if (c == '<' || c == '>' || c == '&') { + html_raw(txt, t - txt); +- if (c=='>') ++ if (c == '>') + html(">"); +- else if (c=='<') ++ else if (c == '<') + html("<"); +- else if (c=='&') ++ else if (c == '&') + html("&"); +- txt = t+1; ++ txt = t + 1; + } + t++; + } +- if (t!=txt) ++ if (t != txt) + html_raw(txt, t - txt); +- if (len<0) ++ if (len < 0) + html("..."); + } + + void html_attr(const char *txt) + { + const char *t = txt; +- while(t && *t){ ++ while (t && *t) { + int c = *t; +- if (c=='<' || c=='>' || c=='\'' || c=='\"' || c=='&') { ++ if (c == '<' || c == '>' || c == '\'' || c == '\"' || c == '&') { + html_raw(txt, t - txt); +- if (c=='>') ++ if (c == '>') + html(">"); +- else if (c=='<') ++ else if (c == '<') + html("<"); +- else if (c=='\'') ++ else if (c == '\'') + html("'"); +- else if (c=='"') ++ else if (c == '"') + html("""); +- else if (c=='&') ++ else if (c == '&') + html("&"); +- txt = t+1; ++ txt = t + 1; + } + t++; + } +- if (t!=txt) ++ if (t != txt) + html(txt); + } + + void html_url_path(const char *txt) + { + const char *t = txt; +- while(t && *t){ ++ while (t && *t) { + unsigned char c = *t; + const char *e = url_escape_table[c]; +- if (e && c!='+' && c!='&') { ++ if (e && c != '+' && c != '&') { + html_raw(txt, t - txt); + html(e); +- txt = t+1; ++ txt = t + 1; + } + t++; + } +- if (t!=txt) ++ if (t != txt) + html(txt); + } + + void html_url_arg(const char *txt) + { + const char *t = txt; +- while(t && *t){ ++ while (t && *t) { + unsigned char c = *t; + const char *e = url_escape_table[c]; + if (c == ' ') +@@ -186,11 +186,11 @@ void html_url_arg(const char *txt) + if (e) { + html_raw(txt, t - txt); + html(e); +- txt = t+1; ++ txt = t + 1; + } + t++; + } +- if (t!=txt) ++ if (t != txt) + html(txt); + } + +@@ -260,7 +260,7 @@ int html_include(const char *filename) + filename, strerror(errno), errno); + return -1; + } +- while((len = fread(buf, 1, 4096, f)) > 0) ++ while ((len = fread(buf, 1, 4096, f)) > 0) + html_raw(buf, len); + fclose(f); + return 0; +@@ -286,14 +286,14 @@ char *convert_query_hexchar(char *txt) + *txt = '\0'; + return txt-1; + } +- d1 = hextoint(*(txt+1)); +- d2 = hextoint(*(txt+2)); +- if (d1<0 || d2<0) { +- memmove(txt, txt+3, n-2); ++ d1 = hextoint(*(txt + 1)); ++ d2 = hextoint(*(txt + 2)); ++ if (d1 < 0 || d2 < 0) { ++ memmove(txt, txt + 3, n - 2); + return txt-1; + } else { + *txt = d1 * 16 + d2; +- memmove(txt+1, txt+3, n-2); ++ memmove(txt + 1, txt + 3, n - 2); + return txt; + } + } +@@ -310,23 +310,23 @@ int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const + printf("Out of memory\n"); + exit(1); + } +- while((c=*t) != '\0') { +- if (c=='=') { ++ while ((c=*t) != '\0') { ++ if (c == '=') { + *t = '\0'; +- value = t+1; +- } else if (c=='+') { ++ value = t + 1; ++ } else if (c == '+') { + *t = ' '; +- } else if (c=='%') { ++ } else if (c == '%') { + t = convert_query_hexchar(t); +- } else if (c=='&') { ++ } else if (c == '&') { + *t = '\0'; + (*fn)(txt, value); +- txt = t+1; ++ txt = t + 1; + value = NULL; + } + t++; + } +- if (t!=txt) ++ if (t != txt) + (*fn)(txt, value); + free(o); + return 0; +diff --git a/parsing.c b/parsing.c +index 1b2a551..9b7efb3 100644 +--- a/parsing.c ++++ b/parsing.c +@@ -112,7 +112,7 @@ const char *reencode(char **txt, const char *src_enc, const char *dst_enc) + return *txt; + + /* no encoding needed if src_enc equals dst_enc */ +- if(!strcasecmp(src_enc, dst_enc)) ++ if (!strcasecmp(src_enc, dst_enc)) + return *txt; + + tmp = reencode_string(*txt, dst_enc, src_enc); +@@ -170,7 +170,7 @@ struct commitinfo *cgit_parse_commit(struct commit *commit) + } + + /* if no special encoding is found, assume UTF-8 */ +- if(!ret->msg_encoding) ++ if (!ret->msg_encoding) + ret->msg_encoding = xstrdup("UTF-8"); + + // skip unknown header fields +diff --git a/scan-tree.c b/scan-tree.c +index 6ce8036..10d90f4 100644 +--- a/scan-tree.c ++++ b/scan-tree.c +@@ -106,7 +106,7 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn) + config_fn = fn; + if (ctx.cfg.enable_git_config) + git_config_from_file(gitconfig_config, fmt("%s/config", path), NULL); +- ++ + if (ctx.cfg.remove_suffix) + if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git")) + *p = '\0'; +@@ -185,7 +185,7 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn) + add_repo(base, fmt("%s/.git", path), fn); + goto end; + } +- while((ent = readdir(dir)) != NULL) { ++ while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.') { + if (ent->d_name[1] == '\0') + continue; +@@ -222,7 +222,7 @@ void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn + char line[MAX_PATH * 2], *z; + FILE *projects; + int err; +- ++ + projects = fopen(projectsfile, "r"); + if (!projects) { + fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n", +diff --git a/shared.c b/shared.c +index 8e5ae48..e732064 100644 +--- a/shared.c ++++ b/shared.c +@@ -28,8 +28,8 @@ int chk_positive(int result, char *msg) + + int chk_non_negative(int result, char *msg) + { +- if (result < 0) +- die("%s: %s",msg, strerror(errno)); ++ if (result < 0) ++ die("%s: %s", msg, strerror(errno)); + return result; + } + +@@ -80,7 +80,7 @@ struct cgit_repo *cgit_get_repoinfo(const char *url) + int i; + struct cgit_repo *repo; + +- for (i=0; iurl, url)) + return repo; +@@ -108,7 +108,7 @@ char *trim_end(const char *str, char c) + if (str == NULL) + return NULL; + len = strlen(str); +- while(len > 0 && str[len - 1] == c) ++ while (len > 0 && str[len - 1] == c) + len--; + if (len == 0) + return NULL; +@@ -207,7 +207,7 @@ static int load_mmfile(mmfile_t *file, const unsigned char *sha1) + file->ptr = (char *)""; + file->size = 0; + } else { +- file->ptr = read_sha1_file(sha1, &type, ++ file->ptr = read_sha1_file(sha1, &type, + (unsigned long *)&file->size); + } + return 1; +@@ -307,7 +307,7 @@ void cgit_diff_tree(const unsigned char *old_sha1, + filepair_fn fn, const char *prefix, int ignorews) + { + struct diff_options opt; +- int prefixlen; ++ struct pathspec_item item; + + diff_setup(&opt); + opt.output_format = DIFF_FORMAT_CALLBACK; +@@ -319,10 +319,10 @@ void cgit_diff_tree(const unsigned char *old_sha1, + opt.format_callback = cgit_diff_tree_cb; + opt.format_callback_data = fn; + if (prefix) { +- opt.nr_paths = 1; +- opt.paths = &prefix; +- prefixlen = strlen(prefix); +- opt.pathlens = &prefixlen; ++ item.match = prefix; ++ item.len = strlen(prefix); ++ opt.pathspec.nr = 1; ++ opt.pathspec.items = &item; + } + diff_setup_done(&opt); + +@@ -351,17 +351,17 @@ int cgit_parse_snapshots_mask(const char *str) + int tl, sl, rv = 0; + + /* favor legacy setting */ +- if(atoi(str)) ++ if (atoi(str)) + return 1; +- for(;;) { +- str += strspn(str,delim); +- tl = strcspn(str,delim); ++ for (;;) { ++ str += strspn(str, delim); ++ tl = strcspn(str, delim); + if (!tl) + break; + for (f = cgit_snapshot_formats; f->suffix; f++) { + sl = strlen(f->suffix); +- if((tl == sl && !strncmp(f->suffix, str, tl)) || +- (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { ++ if ((tl == sl && !strncmp(f->suffix, str, tl)) || ++ (tl == sl - 1 && !strncmp(f->suffix + 1, str, tl - 1))) { + rv |= f->bit; + break; + } +diff --git a/tests/t0101-index.sh b/tests/t0101-index.sh +index 573a351..ab63aca 100755 +--- a/tests/t0101-index.sh ++++ b/tests/t0101-index.sh +@@ -5,14 +5,14 @@ + prepare_tests "Check content on index page" + + run_test 'generate index page' 'cgit_url "" >trash/tmp' +-run_test 'find foo repo' 'grep -e "foo" trash/tmp' +-run_test 'find foo description' 'grep -e "\[no description\]" trash/tmp' +-run_test 'find bar repo' 'grep -e "bar" trash/tmp' +-run_test 'find bar description' 'grep -e "the bar repo" trash/tmp' +-run_test 'find foo+bar repo' 'grep -e ">foo+bar<" trash/tmp' +-run_test 'verify foo+bar link' 'grep -e "/foo+bar/" trash/tmp' +-run_test 'verify "with%20space" link' 'grep -e "/with%20space/" trash/tmp' +-run_test 'no tree-link' '! grep -e "foo/tree" trash/tmp' +-run_test 'no log-link' '! grep -e "foo/log" trash/tmp' ++run_test 'find foo repo' 'grep "foo" trash/tmp' ++run_test 'find foo description' 'grep "\[no description\]" trash/tmp' ++run_test 'find bar repo' 'grep "bar" trash/tmp' ++run_test 'find bar description' 'grep "the bar repo" trash/tmp' ++run_test 'find foo+bar repo' 'grep ">foo+bar<" trash/tmp' ++run_test 'verify foo+bar link' 'grep "/foo+bar/" trash/tmp' ++run_test 'verify "with%20space" link' 'grep "/with%20space/" trash/tmp' ++run_test 'no tree-link' '! grep "foo/tree" trash/tmp' ++run_test 'no log-link' '! grep "foo/log" trash/tmp' + + tests_done +diff --git a/tests/t0102-summary.sh b/tests/t0102-summary.sh +index f299c5a..f778cb4 100755 +--- a/tests/t0102-summary.sh ++++ b/tests/t0102-summary.sh +@@ -5,22 +5,22 @@ + prepare_tests "Check content on summary page" + + run_test 'generate foo summary' 'cgit_url "foo" >trash/tmp' +-run_test 'find commit 1' 'grep -e "commit 1" trash/tmp' +-run_test 'find commit 5' 'grep -e "commit 5" trash/tmp' +-run_test 'find branch master' 'grep -e "master" trash/tmp' +-run_test 'no tags' '! grep -e "tags" trash/tmp' ++run_test 'find commit 1' 'grep "commit 1" trash/tmp' ++run_test 'find commit 5' 'grep "commit 5" trash/tmp' ++run_test 'find branch master' 'grep "master" trash/tmp' ++run_test 'no tags' '! grep "tags" trash/tmp' + run_test 'clone-url expanded correctly' ' +- grep -e "git://example.org/foo.git" trash/tmp ++ grep "git://example.org/foo.git" trash/tmp + ' + + run_test 'generate bar summary' 'cgit_url "bar" >trash/tmp' +-run_test 'no commit 45' '! grep -e "commit 45" trash/tmp' +-run_test 'find commit 46' 'grep -e "commit 46" trash/tmp' +-run_test 'find commit 50' 'grep -e "commit 50" trash/tmp' +-run_test 'find branch master' 'grep -e "master" trash/tmp' +-run_test 'no tags' '! grep -e "tags" trash/tmp' ++run_test 'no commit 45' '! grep "commit 45" trash/tmp' ++run_test 'find commit 46' 'grep "commit 46" trash/tmp' ++run_test 'find commit 50' 'grep "commit 50" trash/tmp' ++run_test 'find branch master' 'grep "master" trash/tmp' ++run_test 'no tags' '! grep "tags" trash/tmp' + run_test 'clone-url expanded correctly' ' +- grep -e "git://example.org/bar.git" trash/tmp ++ grep "git://example.org/bar.git" trash/tmp + ' + + tests_done +diff --git a/tests/t0103-log.sh b/tests/t0103-log.sh +index 7fa6754..67fcba0 100755 +--- a/tests/t0103-log.sh ++++ b/tests/t0103-log.sh +@@ -5,21 +5,21 @@ + prepare_tests "Check content on log page" + + run_test 'generate foo/log' 'cgit_url "foo/log" >trash/tmp' +-run_test 'find commit 1' 'grep -e "commit 1" trash/tmp' +-run_test 'find commit 5' 'grep -e "commit 5" trash/tmp' ++run_test 'find commit 1' 'grep "commit 1" trash/tmp' ++run_test 'find commit 5' 'grep "commit 5" trash/tmp' + + run_test 'generate bar/log' 'cgit_url "bar/log" >trash/tmp' +-run_test 'find commit 1' 'grep -e "commit 1" trash/tmp' +-run_test 'find commit 50' 'grep -e "commit 50" trash/tmp' ++run_test 'find commit 1' 'grep "commit 1" trash/tmp' ++run_test 'find commit 50' 'grep "commit 50" trash/tmp' + + run_test 'generate "with%20space/log?qt=grep&q=commit+1"' ' + cgit_url "with+space/log&qt=grep&q=commit+1" >trash/tmp + ' +-run_test 'find commit 1' 'grep -e "commit 1" trash/tmp' +-run_test 'find link with %20 in path' 'grep -e "/with%20space/log/?qt=grep" trash/tmp' +-run_test 'find link with + in arg' 'grep -e "/log/?qt=grep&q=commit+1" trash/tmp' +-run_test 'no links with space in path' '! grep -e "href=./with space/" trash/tmp' +-run_test 'no links with space in arg' '! grep -e "q=commit 1" trash/tmp' +-run_test 'commit 2 is not visible' '! grep -e "commit 2" trash/tmp' ++run_test 'find commit 1' 'grep "commit 1" trash/tmp' ++run_test 'find link with %20 in path' 'grep "/with%20space/log/?qt=grep" trash/tmp' ++run_test 'find link with + in arg' 'grep "/log/?qt=grep&q=commit+1" trash/tmp' ++run_test 'no links with space in path' '! grep "href=./with space/" trash/tmp' ++run_test 'no links with space in arg' '! grep "q=commit 1" trash/tmp' ++run_test 'commit 2 is not visible' '! grep "commit 2" trash/tmp' + + tests_done +diff --git a/tests/t0104-tree.sh b/tests/t0104-tree.sh +index 2ce1251..7aa3b8d 100755 +--- a/tests/t0104-tree.sh ++++ b/tests/t0104-tree.sh +@@ -5,29 +5,29 @@ + prepare_tests "Check content on tree page" + + run_test 'generate bar/tree' 'cgit_url "bar/tree" >trash/tmp' +-run_test 'find file-1' 'grep -e "file-1" trash/tmp' +-run_test 'find file-50' 'grep -e "file-50" trash/tmp' ++run_test 'find file-1' 'grep "file-1" trash/tmp' ++run_test 'find file-50' 'grep "file-50" trash/tmp' + + run_test 'generate bar/tree/file-50' 'cgit_url "bar/tree/file-50" >trash/tmp' + + run_test 'find line 1' ' +- grep -e "1" trash/tmp ++ grep "1" trash/tmp + ' + + run_test 'no line 2' ' +- ! grep -e "2" trash/tmp ++ ! grep "2" trash/tmp + ' + + run_test 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >trash/tmp' + + run_test 'verify a+b link' ' +- grep -e "/foo+bar/tree/a+b" trash/tmp ++ grep "/foo+bar/tree/a+b" trash/tmp + ' + + run_test 'generate foo+bar/tree?h=1+2' 'cgit_url "foo%2bbar/tree&h=1%2b2" >trash/tmp' + + run_test 'verify a+b?h=1+2 link' ' +- grep -e "/foo+bar/tree/a+b?h=1%2b2" trash/tmp ++ grep "/foo+bar/tree/a+b?h=1%2b2" trash/tmp + ' + + tests_done +diff --git a/tests/t0105-commit.sh b/tests/t0105-commit.sh +index ae794c8..31b554b 100755 +--- a/tests/t0105-commit.sh ++++ b/tests/t0105-commit.sh +@@ -5,33 +5,33 @@ + prepare_tests "Check content on commit page" + + run_test 'generate foo/commit' 'cgit_url "foo/commit" >trash/tmp' +-run_test 'find tree link' 'grep -e "" trash/tmp' ++run_test 'find tree link' 'grep "" trash/tmp' + run_test 'find parent link' 'grep -E "" trash/tmp' + + run_test 'find commit subject' ' +- grep -e "
commit 5<" trash/tmp ++ grep "
commit 5<" trash/tmp + ' + +-run_test 'find commit msg' 'grep -e "
" trash/tmp' +-run_test 'find diffstat' 'grep -e "" trash/tmp' ++run_test 'find commit msg' 'grep "
" trash/tmp' ++run_test 'find diffstat' 'grep "
" trash/tmp' + + run_test 'find diff summary' ' +- grep -e "1 files changed, 1 insertions, 0 deletions" trash/tmp ++ grep "1 files changed, 1 insertions, 0 deletions" trash/tmp + ' + + run_test 'get root commit' ' +- root=$(cd trash/repos/foo && git rev-list --reverse HEAD | head -1) && +- cgit_url "foo/commit&id=$root" >trash/tmp && +- grep "" trash/tmp ++ root=$(cd trash/repos/foo && git rev-list --reverse HEAD | head -1) && ++ cgit_url "foo/commit&id=$root" >trash/tmp && ++ grep "" trash/tmp + ' + + run_test 'root commit contains diffstat' ' +- grep "file-1" trash/tmp ++ grep "file-1" trash/tmp + ' + + run_test 'root commit contains diff' ' +- grep ">diff --git a/file-1 b/file-1<" trash/tmp && +- grep -e "
+1
" trash/tmp ++ grep ">diff --git a/file-1 b/file-1<" trash/tmp && ++ grep "
+1
" trash/tmp + ' + + tests_done +diff --git a/tests/t0106-diff.sh b/tests/t0106-diff.sh +index e140bcc..eee0c8c 100755 +--- a/tests/t0106-diff.sh ++++ b/tests/t0106-diff.sh +@@ -5,16 +5,16 @@ + prepare_tests "Check content on diff page" + + run_test 'generate foo/diff' 'cgit_url "foo/diff" >trash/tmp' +-run_test 'find diff header' 'grep -e "a/file-5 b/file-5" trash/tmp' +-run_test 'find blob link' 'grep -e "@@ -0,0 +1 @@" trash/tmp ++ grep "
@@ -0,0 +1 @@
" trash/tmp + ' + + run_test 'find added line' ' +- grep -e "
+5
" trash/tmp ++ grep "
+5
" trash/tmp + ' + + tests_done +diff --git a/tests/t0107-snapshot.sh b/tests/t0107-snapshot.sh +index 8ab4912..132d2e9 100755 +--- a/tests/t0107-snapshot.sh ++++ b/tests/t0107-snapshot.sh +@@ -10,17 +10,20 @@ run_test 'get foo/snapshot/master.tar.gz' ' + + run_test 'check html headers' ' + head -n 1 trash/tmp | +- grep -e "Content-Type: application/x-gzip" && ++ grep "Content-Type: application/x-gzip" && + + head -n 2 trash/tmp | +- grep -e "Content-Disposition: inline; filename=.master.tar.gz." ++ grep "Content-Disposition: inline; filename=.master.tar.gz." + ' + + run_test 'strip off the header lines' ' +- tail -n +6 trash/tmp > trash/master.tar.gz ++ tail -n +6 trash/tmp > trash/master.tar.gz ++' ++ ++run_test 'verify gzip format' ' ++ gunzip --test trash/master.tar.gz + ' + +-run_test 'verify gzip format' 'gunzip --test trash/master.tar.gz' + run_test 'untar' ' + rm -rf trash/master && + tar -xf trash/master.tar.gz -C trash +@@ -32,7 +35,42 @@ run_test 'count files' ' + ' + + run_test 'verify untarred file-5' ' +- grep -e "^5$" trash/master/file-5 && ++ grep "^5$" trash/master/file-5 && ++ test $(cat trash/master/file-5 | wc -l) = 1 ++' ++ ++run_test 'get foo/snapshot/master.zip' ' ++ cgit_url "foo/snapshot/master.zip" >trash/tmp ++' ++ ++run_test 'check HTML headers (zip)' ' ++ head -n 1 trash/tmp | ++ grep "Content-Type: application/x-zip" && ++ ++ head -n 2 trash/tmp | ++ grep "Content-Disposition: inline; filename=.master.zip." ++' ++ ++run_test 'strip off the header lines (zip)' ' ++ tail -n +6 trash/tmp >trash/master.zip ++' ++ ++run_test 'verify zip format' ' ++ unzip -t trash/master.zip ++' ++ ++run_test 'unzip' ' ++ rm -rf trash/master && ++ unzip trash/master.zip -d trash ++' ++ ++run_test 'count files (zip)' ' ++ c=$(ls -1 trash/master/ | wc -l) && ++ test $c = 5 ++' ++ ++run_test 'verify unzipped file-5' ' ++ grep "^5$" trash/master/file-5 && + test $(cat trash/master/file-5 | wc -l) = 1 + ' + +diff --git a/tests/t0108-patch.sh b/tests/t0108-patch.sh +index 6ee70b3..f92f69c 100755 +--- a/tests/t0108-patch.sh ++++ b/tests/t0108-patch.sh +@@ -9,19 +9,19 @@ run_test 'generate foo/patch' ' + ' + + run_test 'find `From:` line' ' +- grep -e "^From: " trash/tmp ++ grep "^From: " trash/tmp + ' + + run_test 'find `Date:` line' ' +- grep -e "^Date: " trash/tmp ++ grep "^Date: " trash/tmp + ' + + run_test 'find `Subject:` line' ' +- grep -e "^Subject: commit 5" trash/tmp ++ grep "^Subject: commit 5" trash/tmp + ' + + run_test 'find `cgit` signature' ' +- tail -1 trash/tmp | grep -e "^cgit" ++ tail -1 trash/tmp | grep "^cgit" + ' + + run_test 'find initial commit' ' +@@ -33,7 +33,7 @@ run_test 'generate patch for initial commit' ' + ' + + run_test 'find `cgit` signature' ' +- tail -1 trash/tmp | grep -e "^cgit" ++ tail -1 trash/tmp | grep "^cgit" + ' + + tests_done +diff --git a/ui-blob.c b/ui-blob.c +index ec435e1..c59fbcb 100644 +--- a/ui-blob.c ++++ b/ui-blob.c +@@ -11,17 +11,22 @@ + #include "html.h" + #include "ui-shared.h" + +-static char *match_path; +-static unsigned char *matched_sha1; +-static int found_path; ++struct walk_tree_context { ++ char *match_path; ++ unsigned char *matched_sha1; ++ int found_path; ++}; + +-static int walk_tree(const unsigned char *sha1, const char *base,int baselen, +- const char *pathname, unsigned mode, int stage, void *cbdata) { +- if(strncmp(base,match_path,baselen) +- || strcmp(match_path+baselen,pathname) ) ++static int walk_tree(const unsigned char *sha1, const char *base, int baselen, ++ const char *pathname, unsigned mode, int stage, void *cbdata) ++{ ++ struct walk_tree_context *walk_tree_ctx = cbdata; ++ ++ if (strncmp(base, walk_tree_ctx->match_path, baselen) ++ || strcmp(walk_tree_ctx->match_path + baselen, pathname)) + return READ_TREE_RECURSIVE; +- memmove(matched_sha1,sha1,20); +- found_path = 1; ++ memmove(walk_tree_ctx->matched_sha1, sha1, 20); ++ walk_tree_ctx->found_path = 1; + return 0; + } + +@@ -32,17 +37,27 @@ int cgit_print_file(char *path, const char *head) + char *buf; + unsigned long size; + struct commit *commit; +- const char *paths[] = {path, NULL}; ++ struct pathspec_item path_items = { ++ .match = path, ++ .len = strlen(path) ++ }; ++ struct pathspec paths = { ++ .nr = 1, ++ .items = &path_items ++ }; ++ struct walk_tree_context walk_tree_ctx = { ++ .match_path = path, ++ .matched_sha1 = sha1, ++ .found_path = 0 ++ }; ++ + if (get_sha1(head, sha1)) + return -1; + type = sha1_object_info(sha1, &size); +- if(type == OBJ_COMMIT && path) { ++ if (type == OBJ_COMMIT && path) { + commit = lookup_commit_reference(sha1); +- match_path = path; +- matched_sha1 = sha1; +- found_path = 0; +- read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); +- if (!found_path) ++ read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); ++ if (!walk_tree_ctx.found_path) + return -1; + type = sha1_object_info(sha1, &size); + } +@@ -63,15 +78,26 @@ void cgit_print_blob(const char *hex, char *path, const char *head) + char *buf; + unsigned long size; + struct commit *commit; +- const char *paths[] = {path, NULL}; ++ struct pathspec_item path_items = { ++ .match = path, ++ .len = strlen(path) ++ }; ++ struct pathspec paths = { ++ .nr = 1, ++ .items = &path_items ++ }; ++ struct walk_tree_context walk_tree_ctx = { ++ .match_path = path, ++ .matched_sha1 = sha1, ++ }; + + if (hex) { +- if (get_sha1_hex(hex, sha1)){ ++ if (get_sha1_hex(hex, sha1)) { + cgit_print_error(fmt("Bad hex value: %s", hex)); + return; + } + } else { +- if (get_sha1(head,sha1)) { ++ if (get_sha1(head, sha1)) { + cgit_print_error(fmt("Bad ref: %s", head)); + return; + } +@@ -79,11 +105,9 @@ void cgit_print_blob(const char *hex, char *path, const char *head) + + type = sha1_object_info(sha1, &size); + +- if((!hex) && type == OBJ_COMMIT && path) { ++ if ((!hex) && type == OBJ_COMMIT && path) { + commit = lookup_commit_reference(sha1); +- match_path = path; +- matched_sha1 = sha1; +- read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); ++ read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); + type = sha1_object_info(sha1,&size); + } + +diff --git a/ui-clone.c b/ui-clone.c +index 81e7a4e..fdea24f 100644 +--- a/ui-clone.c ++++ b/ui-clone.c +@@ -19,12 +19,10 @@ static int print_ref_info(const char *refname, const unsigned char *sha1, + if (!(obj = parse_object(sha1))) + return 0; + +- if (!strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/")) +- htmlf("%s\t%s\n", sha1_to_hex(sha1), refname); +- else if (!prefixcmp(refname, "refs/tags") && obj->type == OBJ_TAG) { ++ htmlf("%s\t%s\n", sha1_to_hex(sha1), refname); ++ if (obj->type == OBJ_TAG) { + if (!(obj = deref_tag(obj, refname, 0))) + return 0; +- htmlf("%s\t%s\n", sha1_to_hex(sha1), refname); + htmlf("%s\t%s^{}\n", sha1_to_hex(obj->sha1), refname); + } + return 0; +diff --git a/ui-commit.c b/ui-commit.c +index 536a8e8..74f37c8 100644 +--- a/ui-commit.c ++++ b/ui-commit.c +@@ -39,7 +39,7 @@ void cgit_print_commit(char *hex, const char *prefix) + format_note(NULL, sha1, ¬es, PAGE_ENCODING, 0); + + load_ref_decorations(DECORATE_FULL_REFS); +- ++ + cgit_print_diff_ctrls(); + html("
\n"); + html("\n"); +- for (p = commit->parents; p ; p = p->next) { ++ for (p = commit->parents; p; p = p->next) { + parent = lookup_commit_reference(p->item->object.sha1); + if (!parent) { + html(""); + print_sort_header("Name", "name"); + print_sort_header("Description", "desc"); +- print_sort_header("Owner", "owner"); ++ if (ctx.cfg.enable_index_owner) ++ print_sort_header("Owner", "owner"); + print_sort_header("Idle", "idle"); + if (ctx.cfg.enable_index_links) + html(""); +@@ -128,10 +129,10 @@ void print_pager(int items, int pagelen, char *search, char *sort) + int i, ofs; + char *class = NULL; + html("
"); +- for(i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) { ++ for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) { + class = (ctx.qry.ofs == ofs) ? "current" : NULL; +- cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), class, +- search, sort, ofs); ++ cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1), ++ class, search, sort, ofs); + } + html("
"); + } +@@ -239,13 +240,15 @@ int sort_repolist(char *field) + + void cgit_print_repolist() + { +- int i, columns = 4, hits = 0, header = 0; ++ int i, columns = 3, hits = 0, header = 0; + char *last_section = NULL; + char *section; + int sorted = 0; + + if (ctx.cfg.enable_index_links) +- columns++; ++ ++columns; ++ if (ctx.cfg.enable_index_owner) ++ ++columns; + + ctx.page.title = ctx.cfg.root_title; + cgit_print_http_headers(&ctx); +@@ -255,13 +258,13 @@ void cgit_print_repolist() + if (ctx.cfg.index_header) + html_include(ctx.cfg.index_header); + +- if(ctx.qry.sort) ++ if (ctx.qry.sort) + sorted = sort_repolist(ctx.qry.sort); + else if (ctx.cfg.section_sort) + sort_repolist("section"); + + html("
author"); +@@ -75,7 +75,7 @@ void cgit_print_commit(char *hex, const char *prefix) + cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix); + } + html("
"); +diff --git a/ui-diff.c b/ui-diff.c +index 3d46da2..49e5b46 100644 +--- a/ui-diff.c ++++ b/ui-diff.c +@@ -184,7 +184,7 @@ void cgit_print_diffstat(const unsigned char *old_sha1, + max_changes = 0; + cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix, + ctx.qry.ignorews); +- for(i = 0; i"); + html("
"); +diff --git a/ui-plain.c b/ui-plain.c +index 85877d7..8ef4ec6 100644 +--- a/ui-plain.c ++++ b/ui-plain.c +@@ -11,8 +11,10 @@ + #include "html.h" + #include "ui-shared.h" + +-int match_baselen; +-int match; ++struct walk_tree_context { ++ int match_baselen; ++ int match; ++}; + + static char *get_mimetype_from_file(const char *filename, const char *ext) + { +@@ -54,7 +56,7 @@ static char *get_mimetype_from_file(const char *filename, const char *ext) + return result; + } + +-static void print_object(const unsigned char *sha1, const char *path) ++static int print_object(const unsigned char *sha1, const char *path) + { + enum object_type type; + char *buf, *ext; +@@ -65,13 +67,13 @@ static void print_object(const unsigned char *sha1, const char *path) + type = sha1_object_info(sha1, &size); + if (type == OBJ_BAD) { + html_status(404, "Not found", 0); +- return; ++ return 0; + } + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) { + html_status(404, "Not found", 0); +- return; ++ return 0; + } + ctx.page.mimetype = NULL; + ext = strrchr(path, '.'); +@@ -97,9 +99,9 @@ static void print_object(const unsigned char *sha1, const char *path) + ctx.page.etag = sha1_to_hex(sha1); + cgit_print_http_headers(&ctx); + html_raw(buf, size); +- match = 1; + if (freemime) + free(ctx.page.mimetype); ++ return 1; + } + + static char *buildpath(const char *base, int baselen, const char *path) +@@ -138,7 +140,6 @@ static void print_dir(const unsigned char *sha1, const char *base, + fullpath); + html("\n"); + } +- match = 2; + } + + static void print_dir_entry(const unsigned char *sha1, const char *base, +@@ -156,7 +157,6 @@ static void print_dir_entry(const unsigned char *sha1, const char *base, + cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, + fullpath); + html("\n"); +- match = 2; + } + + static void print_dir_tail(void) +@@ -168,18 +168,23 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen, + const char *pathname, unsigned mode, int stage, + void *cbdata) + { +- if (baselen == match_baselen) { +- if (S_ISREG(mode)) +- print_object(sha1, pathname); +- else if (S_ISDIR(mode)) { ++ struct walk_tree_context *walk_tree_ctx = cbdata; ++ ++ if (baselen == walk_tree_ctx->match_baselen) { ++ if (S_ISREG(mode)) { ++ if (print_object(sha1, pathname)) ++ walk_tree_ctx->match = 1; ++ } else if (S_ISDIR(mode)) { + print_dir(sha1, base, baselen, pathname); ++ walk_tree_ctx->match = 2; + return READ_TREE_RECURSIVE; + } +- } +- else if (baselen > match_baselen) ++ } else if (baselen > walk_tree_ctx->match_baselen) { + print_dir_entry(sha1, base, baselen, pathname, mode); +- else if (S_ISDIR(mode)) ++ walk_tree_ctx->match = 2; ++ } else if (S_ISDIR(mode)) { + return READ_TREE_RECURSIVE; ++ } + + return 0; + } +@@ -197,7 +202,17 @@ void cgit_print_plain(struct cgit_context *ctx) + const char *rev = ctx->qry.sha1; + unsigned char sha1[20]; + struct commit *commit; +- const char *paths[] = {ctx->qry.path, NULL}; ++ struct pathspec_item path_items = { ++ .match = ctx->qry.path, ++ .len = ctx->qry.path ? strlen(ctx->qry.path) : 0 ++ }; ++ struct pathspec paths = { ++ .nr = 1, ++ .items = &path_items ++ }; ++ struct walk_tree_context walk_tree_ctx = { ++ .match = 0 ++ }; + + if (!rev) + rev = ctx->qry.head; +@@ -211,16 +226,17 @@ void cgit_print_plain(struct cgit_context *ctx) + html_status(404, "Not found", 0); + return; + } +- if (!paths[0]) { +- paths[0] = ""; +- match_baselen = -1; ++ if (!path_items.match) { ++ path_items.match = ""; ++ walk_tree_ctx.match_baselen = -1; + print_dir(commit->tree->object.sha1, "", 0, ""); ++ walk_tree_ctx.match = 2; + } + else +- match_baselen = basedir_len(paths[0]); +- read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); +- if (!match) ++ walk_tree_ctx.match_baselen = basedir_len(path_items.match); ++ read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); ++ if (!walk_tree_ctx.match) + html_status(404, "Not found", 0); +- else if (match == 2) ++ else if (walk_tree_ctx.match == 2) + print_dir_tail(); + } +diff --git a/ui-refs.c b/ui-refs.c +index caddfbc..ce06b08 100644 +--- a/ui-refs.c ++++ b/ui-refs.c +@@ -200,7 +200,7 @@ void cgit_print_branches(int maxcount) + qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); + } + +- for(i=0; i list.count) + maxcount = list.count; + print_tag_header(); +- for(i=0; i%s", title); + } + +-void print_header(int columns) ++void print_header() + { + html("
Links
"); +- for (i=0; i ctx.qry.ofs + ctx.cfg.max_repo_count) + continue; + if (!header++) +- print_header(columns); ++ print_header(); + section = ctx.repo->section; + if (section && !strcmp(section, "")) + section = NULL; +@@ -294,8 +297,10 @@ void cgit_print_repolist() + html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); + html_link_close(); + html(""); + if (ctx.cfg.enable_index_links) { +diff --git a/ui-shared.c b/ui-shared.c +index 75b97a1..af5310b 100644 +--- a/ui-shared.c ++++ b/ui-shared.c +@@ -23,7 +23,7 @@ static char *http_date(time_t t) + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + struct tm *tm = gmtime(&t); + return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], +- tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, ++ tm->tm_mday, month[tm->tm_mon], 1900 + tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); + } + +@@ -93,7 +93,7 @@ char *cgit_fileurl(const char *reponame, const char *pagename, + char *cgit_pageurl(const char *reponame, const char *pagename, + const char *query) + { +- return cgit_fileurl(reponame,pagename,0,query); ++ return cgit_fileurl(reponame, pagename, 0, query); + } + + const char *cgit_repobasename(const char *reponame) +@@ -102,21 +102,21 @@ const char *cgit_repobasename(const char *reponame) + static char rvbuf[1024]; + int p; + const char *rv; +- strncpy(rvbuf,reponame,sizeof(rvbuf)); +- if(rvbuf[sizeof(rvbuf)-1]) ++ strncpy(rvbuf, reponame, sizeof(rvbuf)); ++ if (rvbuf[sizeof(rvbuf)-1]) + die("cgit_repobasename: truncated repository name '%s'", reponame); + p = strlen(rvbuf)-1; + /* strip trailing slashes */ +- while(p && rvbuf[p]=='/') rvbuf[p--]=0; ++ while (p && rvbuf[p] == '/') rvbuf[p--] = 0; + /* strip trailing .git */ +- if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { ++ if (p >= 3 && !strncmp(&rvbuf[p-3], ".git", 4)) { + p -= 3; rvbuf[p--] = 0; + } + /* strip more trailing slashes if any */ +- while( p && rvbuf[p]=='/') rvbuf[p--]=0; ++ while ( p && rvbuf[p] == '/') rvbuf[p--] = 0; + /* find last slash in the remaining string */ + rv = strrchr(rvbuf,'/'); +- if(rv) ++ if (rv) + return ++rv; + return rvbuf; + } +@@ -499,7 +499,7 @@ void cgit_object_link(struct object *obj) + shortrev = xstrdup(fullrev); + shortrev[10] = '\0'; + if (obj->type == OBJ_COMMIT) { +- cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, ++ cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, + ctx.qry.head, fullrev, NULL, 0); + return; + } else if (obj->type == OBJ_TREE) +@@ -564,6 +564,7 @@ void cgit_submodule_link(const char *class, char *path, const char *rev) + html("'>"); + html_txt(path); + html(""); ++ html_txt(fmt(" @ %.7s", rev)); + if (item && tail) + path[len - 1] = tail; + } +@@ -575,7 +576,7 @@ void cgit_print_date(time_t secs, const char *format, int local_time) + + if (!secs) + return; +- if(local_time) ++ if (local_time) + time = localtime(&secs); + else + time = gmtime(&secs); +@@ -735,7 +736,7 @@ int print_archive_ref(const char *refname, const unsigned char *sha1, + + if (prefixcmp(refname, "refs/archives")) + return 0; +- strncpy(buf, refname+14, sizeof(buf)); ++ strncpy(buf, refname + 14, sizeof(buf)); + obj = parse_object(sha1); + if (!obj) + return 1; +@@ -967,7 +968,7 @@ void cgit_print_snapshot_links(const char *repo, const char *head, + { + const struct cgit_snapshot_format* f; + char *prefix; +- char *filename; ++ char *filename; + unsigned char sha1[20]; + + if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 && +diff --git a/ui-snapshot.c b/ui-snapshot.c +index 47432bd..54e659c 100644 +--- a/ui-snapshot.c ++++ b/ui-snapshot.c +@@ -11,7 +11,32 @@ + #include "html.h" + #include "ui-shared.h" + +-static int write_compressed_tar_archive(struct archiver_args *args, char *filter_argv[]) ++static int write_archive_type(const char *format, const char *hex, const char *prefix) ++{ ++ struct argv_array argv = ARGV_ARRAY_INIT; ++ argv_array_push(&argv, "snapshot"); ++ argv_array_push(&argv, format); ++ if (prefix) { ++ argv_array_push(&argv, "--prefix"); ++ argv_array_push(&argv, fmt("%s/", prefix)); ++ } ++ argv_array_push(&argv, hex); ++ return write_archive(argv.argc, argv.argv, NULL, 1, NULL, 0); ++} ++ ++static int write_tar_archive(const char *hex, const char *prefix) ++{ ++ return write_archive_type("--format=tar", hex, prefix); ++} ++ ++static int write_zip_archive(const char *hex, const char *prefix) ++{ ++ return write_archive_type("--format=zip", hex, prefix); ++} ++ ++static int write_compressed_tar_archive(const char *hex, ++ const char *prefix, ++ char *filter_argv[]) + { + int rv; + struct cgit_filter f; +@@ -19,27 +44,27 @@ static int write_compressed_tar_archive(struct archiver_args *args, char *filter + f.cmd = filter_argv[0]; + f.argv = filter_argv; + cgit_open_filter(&f); +- rv = write_tar_archive(args); ++ rv = write_tar_archive(hex, prefix); + cgit_close_filter(&f); + return rv; + } + +-static int write_tar_gzip_archive(struct archiver_args *args) ++static int write_tar_gzip_archive(const char *hex, const char *prefix) + { + char *argv[] = { "gzip", "-n", NULL }; +- return write_compressed_tar_archive(args, argv); ++ return write_compressed_tar_archive(hex, prefix, argv); + } + +-static int write_tar_bzip2_archive(struct archiver_args *args) ++static int write_tar_bzip2_archive(const char *hex, const char *prefix) + { + char *argv[] = { "bzip2", NULL }; +- return write_compressed_tar_archive(args, argv); ++ return write_compressed_tar_archive(hex, prefix, argv); + } + +-static int write_tar_xz_archive(struct archiver_args *args) ++static int write_tar_xz_archive(const char *hex, const char *prefix) + { + char *argv[] = { "xz", NULL }; +- return write_compressed_tar_archive(args, argv); ++ return write_compressed_tar_archive(hex, prefix, argv); + } + + const struct cgit_snapshot_format cgit_snapshot_formats[] = { +@@ -48,7 +73,7 @@ const struct cgit_snapshot_format cgit_snapshot_formats[] = { + { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 }, + { ".tar", "application/x-tar", write_tar_archive, 0x08 }, + { ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 }, +- {} ++ { NULL } + }; + + static const struct cgit_snapshot_format *get_format(const char *filename) +@@ -57,7 +82,7 @@ static const struct cgit_snapshot_format *get_format(const char *filename) + int fl, sl; + + fl = strlen(filename); +- for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { ++ for (fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { + sl = strlen(fmt->suffix); + if (sl >= fl) + continue; +@@ -71,34 +96,20 @@ static int make_snapshot(const struct cgit_snapshot_format *format, + const char *hex, const char *prefix, + const char *filename) + { +- struct archiver_args args; +- struct commit *commit; + unsigned char sha1[20]; + +- if(get_sha1(hex, sha1)) { ++ if (get_sha1(hex, sha1)) { + cgit_print_error(fmt("Bad object id: %s", hex)); + return 1; + } +- commit = lookup_commit_reference(sha1); +- if(!commit) { ++ if (!lookup_commit_reference(sha1)) { + cgit_print_error(fmt("Not a commit reference: %s", hex)); + return 1; + } +- memset(&args, 0, sizeof(args)); +- if (prefix) { +- args.base = fmt("%s/", prefix); +- args.baselen = strlen(prefix) + 1; +- } else { +- args.base = ""; +- args.baselen = 0; +- } +- args.tree = commit->tree; +- args.time = commit->date; +- args.compression_level = Z_DEFAULT_COMPRESSION; + ctx.page.mimetype = xstrdup(format->mimetype); + ctx.page.filename = xstrdup(filename); + cgit_print_http_headers(&ctx); +- format->write_func(&args); ++ format->write_func(hex, prefix); + return 0; + } + +diff --git a/ui-ssdiff.c b/ui-ssdiff.c +index 7108779..3d3dad6 100644 +--- a/ui-ssdiff.c ++++ b/ui-ssdiff.c +@@ -138,9 +138,8 @@ static char *replace_tabs(char *line) + strcat(result, prev_buf); + break; + } else { +- strcat(result, " "); +- strncat(result, spaces, 8 - (strlen(result) % 8)); + strncat(result, prev_buf, cur_buf - prev_buf); ++ strncat(result, spaces, 8 - (strlen(result) % 8)); + } + prev_buf = cur_buf + 1; + } +diff --git a/ui-stats.c b/ui-stats.c +index 59f4c1e..9cf1dbd 100644 +--- a/ui-stats.c ++++ b/ui-stats.c +@@ -23,21 +23,21 @@ static void trunc_week(struct tm *tm) + { + time_t t = timegm(tm); + t -= ((tm->tm_wday + 6) % 7) * DAY_SECS; +- gmtime_r(&t, tm); ++ gmtime_r(&t, tm); + } + + static void dec_week(struct tm *tm) + { + time_t t = timegm(tm); + t -= WEEK_SECS; +- gmtime_r(&t, tm); ++ gmtime_r(&t, tm); + } + + static void inc_week(struct tm *tm) + { + time_t t = timegm(tm); + t += WEEK_SECS; +- gmtime_r(&t, tm); ++ gmtime_r(&t, tm); + } + + static char *pretty_week(struct tm *tm) +@@ -83,7 +83,7 @@ static char *pretty_month(struct tm *tm) + static void trunc_quarter(struct tm *tm) + { + trunc_month(tm); +- while(tm->tm_mon % 3 != 0) ++ while (tm->tm_mon % 3 != 0) + dec_month(tm); + } + +@@ -153,7 +153,7 @@ int cgit_find_stats_period(const char *expr, struct cgit_period **period) + if (periods[i].code == code || !strcmp(periods[i].name, expr)) { + if (period) + *period = &periods[i]; +- return i+1; ++ return i + 1; + } + return 0; + } +@@ -239,7 +239,7 @@ struct string_list collect_stats(struct cgit_context *ctx, + init_revisions(&rev, NULL); + rev.abbrev = DEFAULT_ABBREV; + rev.commit_format = CMIT_FMT_DEFAULT; +- rev.no_merges = 1; ++ rev.max_parents = 1; + rev.verbose_header = 1; + rev.show_root_diff = 0; + setup_revisions(argc, argv, &rev, NULL); +diff --git a/ui-tag.c b/ui-tag.c +index 39e4cb8..cab96b1 100644 +--- a/ui-tag.c ++++ b/ui-tag.c +@@ -99,6 +99,6 @@ void cgit_print_tag(char *revname) + if (ctx.repo->snapshots) + print_download_links(revname); + html("
"); +- html_txt(ctx.repo->owner); +- html(""); ++ if (ctx.cfg.enable_index_owner) { ++ html_txt(ctx.repo->owner); ++ html(""); ++ } + print_modtime(ctx.repo); + html("
\n"); +- } ++ } + return; + } +diff --git a/ui-tree.c b/ui-tree.c +index b1adcc7..561f9e7 100644 +--- a/ui-tree.c ++++ b/ui-tree.c +@@ -11,9 +11,11 @@ + #include "html.h" + #include "ui-shared.h" + +-char *curr_rev; +-char *match_path; +-int header = 0; ++struct walk_tree_context { ++ char *curr_rev; ++ char *match_path; ++ int state; ++}; + + static void print_text_buffer(const char *name, char *buf, unsigned long size) + { +@@ -27,10 +29,10 @@ static void print_text_buffer(const char *name, char *buf, unsigned long size) + html("
");
+ 		idx = 0;
+ 		lineno = 0;
+-	
++
+ 		if (size) {
+ 			htmlf(numberfmt, ++lineno);
+-			while(idx < size - 1) { // skip absolute last newline
++			while (idx < size - 1) { // skip absolute last newline
+ 				if (buf[idx] == '\n')
+ 					htmlf(numberfmt, ++lineno);
+ 				idx++;
+@@ -84,7 +86,7 @@ static void print_binary_buffer(char *buf, unsigned long size)
+ 	html("\n");
+ }
+ 
+-static void print_object(const unsigned char *sha1, char *path, const char *basename)
++static void print_object(const unsigned char *sha1, char *path, const char *basename, const char *rev)
+ {
+ 	enum object_type type;
+ 	char *buf;
+@@ -106,7 +108,7 @@ static void print_object(const unsigned char *sha1, char *path, const char *base
+ 
+ 	htmlf("blob: %s (", sha1_to_hex(sha1));
+ 	cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
+-		        curr_rev, path);
++		        rev, path);
+ 	html(")\n");
+ 
+ 	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
+@@ -126,6 +128,7 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
+ 		   const char *pathname, unsigned int mode, int stage,
+ 		   void *cbdata)
+ {
++	struct walk_tree_context *walk_tree_ctx = cbdata;
+ 	char *name;
+ 	char *fullpath;
+ 	char *class;
+@@ -153,7 +156,7 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
+ 		cgit_submodule_link("ls-mod", fullpath, sha1_to_hex(sha1));
+ 	} else if (S_ISDIR(mode)) {
+ 		cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
+-			       curr_rev, fullpath);
++			       walk_tree_ctx->curr_rev, fullpath);
+ 	} else {
+ 		class = strrchr(name, '.');
+ 		if (class != NULL) {
+@@ -161,19 +164,20 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
+ 		} else
+ 			class = "ls-blob";
+ 		cgit_tree_link(name, NULL, class, ctx.qry.head,
+-			       curr_rev, fullpath);
++			       walk_tree_ctx->curr_rev, fullpath);
+ 	}
+ 	htmlf("%li", size);
+ 
+ 	html("");
+-	cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
+-		      fullpath, 0, NULL, NULL, ctx.qry.showmsg);
++	cgit_log_link("log", NULL, "button", ctx.qry.head,
++		      walk_tree_ctx->curr_rev, fullpath, 0, NULL, NULL,
++		      ctx.qry.showmsg);
+ 	if (ctx.repo->max_stats)
+ 		cgit_stats_link("stats", NULL, "button", ctx.qry.head,
+ 				fullpath);
+ 	if (!S_ISGITLINK(mode))
+-		cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev,
+-				fullpath);
++		cgit_plain_link("plain", NULL, "button", ctx.qry.head,
++				walk_tree_ctx->curr_rev, fullpath);
+ 	html("\n");
+ 	free(name);
+ 	return 0;
+@@ -188,20 +192,19 @@ static void ls_head()
+ 	html("Size");
+ 	html("");
+ 	html("\n");
+-	header = 1;
+ }
+ 
+ static void ls_tail()
+ {
+-	if (!header)
+-		return;
+ 	html("\n");
+-	header = 0;
+ }
+ 
+-static void ls_tree(const unsigned char *sha1, char *path)
++static void ls_tree(const unsigned char *sha1, char *path, struct walk_tree_context *walk_tree_ctx)
+ {
+ 	struct tree *tree;
++	struct pathspec paths = {
++		.nr = 0
++	};
+ 
+ 	tree = parse_tree_indirect(sha1);
+ 	if (!tree) {
+@@ -211,7 +214,7 @@ static void ls_tree(const unsigned char *sha1, char *path)
+ 	}
+ 
+ 	ls_head();
+-	read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
++	read_tree_recursive(tree, "", 0, 1, &paths, ls_item, walk_tree_ctx);
+ 	ls_tail();
+ }
+ 
+@@ -220,25 +223,25 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
+ 		     const char *pathname, unsigned mode, int stage,
+ 		     void *cbdata)
+ {
+-	static int state;
++	struct walk_tree_context *walk_tree_ctx = cbdata;
+ 	static char buffer[PATH_MAX];
+ 
+-	if (state == 0) {
++	if (walk_tree_ctx->state == 0) {
+ 		memcpy(buffer, base, baselen);
+-		strcpy(buffer+baselen, pathname);
+-		if (strcmp(match_path, buffer))
++		strcpy(buffer + baselen, pathname);
++		if (strcmp(walk_tree_ctx->match_path, buffer))
+ 			return READ_TREE_RECURSIVE;
+ 
+ 		if (S_ISDIR(mode)) {
+-			state = 1;
++			walk_tree_ctx->state = 1;
+ 			ls_head();
+ 			return READ_TREE_RECURSIVE;
+ 		} else {
+-			print_object(sha1, buffer, pathname);
++			print_object(sha1, buffer, pathname, walk_tree_ctx->curr_rev);
+ 			return 0;
+ 		}
+ 	}
+-	ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
++	ls_item(sha1, base, baselen, pathname, mode, stage, walk_tree_ctx);
+ 	return 0;
+ }
+ 
+@@ -252,12 +255,23 @@ void cgit_print_tree(const char *rev, char *path)
+ {
+ 	unsigned char sha1[20];
+ 	struct commit *commit;
+-	const char *paths[] = {path, NULL};
++	struct pathspec_item path_items = {
++		.match = path,
++		.len = path ? strlen(path) : 0
++	};
++	struct pathspec paths = {
++		.nr = path ? 1 : 0,
++		.items = &path_items
++	};
++	struct walk_tree_context walk_tree_ctx = {
++		.match_path = path,
++		.state = 0
++	};
+ 
+ 	if (!rev)
+ 		rev = ctx.qry.head;
+ 
+-	curr_rev = xstrdup(rev);
++	walk_tree_ctx.curr_rev = xstrdup(rev);
+ 	if (get_sha1(rev, sha1)) {
+ 		cgit_print_error(fmt("Invalid revision name: %s", rev));
+ 		return;
+@@ -269,11 +283,11 @@ void cgit_print_tree(const char *rev, char *path)
+ 	}
+ 
+ 	if (path == NULL) {
+-		ls_tree(commit->tree->object.sha1, NULL);
++		ls_tree(commit->tree->object.sha1, NULL, &walk_tree_ctx);
+ 		return;
+ 	}
+ 
+-	match_path = path;
+-	read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
+-	ls_tail();
++	read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx);
++	if (walk_tree_ctx.state == 1)
++		ls_tail();
+ }