summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile3
-rw-r--r--cgit.c9
-rw-r--r--cgit.css40
-rw-r--r--cgit.h4
-rw-r--r--cgitrc.5.txt23
-rw-r--r--cmd.c3
m---------git0
-rw-r--r--html.c4
-rw-r--r--scan-tree.c20
-rw-r--r--shared.c1
-rw-r--r--ui-log.c266
-rw-r--r--ui-log.h3
-rw-r--r--ui-summary.c2
-rw-r--r--vector.c38
-rw-r--r--vector.h17
15 files changed, 361 insertions, 72 deletions
diff --git a/Makefile b/Makefile
index fe4b10e..a988751 100644
--- a/Makefile
+++ b/Makefile
@@ -1,63 +1,63 @@
1CGIT_VERSION = v0.8.3.4 1CGIT_VERSION = v0.8.3.4
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7prefix = /usr 7prefix = /usr
8libdir = $(prefix)/lib 8libdir = $(prefix)/lib
9filterdir = $(libdir)/cgit/filters 9filterdir = $(libdir)/cgit/filters
10docdir = $(prefix)/share/doc/cgit 10docdir = $(prefix)/share/doc/cgit
11htmldir = $(docdir) 11htmldir = $(docdir)
12pdfdir = $(docdir) 12pdfdir = $(docdir)
13mandir = $(prefix)/share/man 13mandir = $(prefix)/share/man
14SHA1_HEADER = <openssl/sha.h> 14SHA1_HEADER = <openssl/sha.h>
15GIT_VER = 1.7.3 15GIT_VER = 1.7.4
16GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 16GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
17INSTALL = install 17INSTALL = install
18MAN5_TXT = $(wildcard *.5.txt) 18MAN5_TXT = $(wildcard *.5.txt)
19MAN_TXT = $(MAN5_TXT) 19MAN_TXT = $(MAN5_TXT)
20DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT)) 20DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
21DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) 21DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
22DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT)) 22DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT))
23 23
24# Define NO_STRCASESTR if you don't have strcasestr. 24# Define NO_STRCASESTR if you don't have strcasestr.
25# 25#
26# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1 26# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
27# implementation (slower). 27# implementation (slower).
28# 28#
29# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 29# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
30# 30#
31# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) 31# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
32# do not support the 'size specifiers' introduced by C99, namely ll, hh, 32# do not support the 'size specifiers' introduced by C99, namely ll, hh,
33# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). 33# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
34# some C compilers supported these specifiers prior to C99 as an extension. 34# some C compilers supported these specifiers prior to C99 as an extension.
35# 35#
36 36
37#-include config.mak 37#-include config.mak
38 38
39# 39#
40# Platform specific tweaks 40# Platform specific tweaks
41# 41#
42 42
43uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 43uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
44uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 44uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
45uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 45uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
46 46
47ifeq ($(uname_O),Cygwin) 47ifeq ($(uname_O),Cygwin)
48 NO_STRCASESTR = YesPlease 48 NO_STRCASESTR = YesPlease
49 NEEDS_LIBICONV = YesPlease 49 NEEDS_LIBICONV = YesPlease
50endif 50endif
51 51
52# 52#
53# Let the user override the above settings. 53# Let the user override the above settings.
54# 54#
55-include cgit.conf 55-include cgit.conf
56 56
57# 57#
58# Define a way to invoke make in subdirs quietly, shamelessly ripped 58# Define a way to invoke make in subdirs quietly, shamelessly ripped
59# from git.git 59# from git.git
60# 60#
61QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 61QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
62QUIET_SUBDIR1 = 62QUIET_SUBDIR1 =
63 63
@@ -70,96 +70,97 @@ endif
70ifndef V 70ifndef V
71 QUIET_CC = @echo ' ' CC $@; 71 QUIET_CC = @echo ' ' CC $@;
72 QUIET_MM = @echo ' ' MM $@; 72 QUIET_MM = @echo ' ' MM $@;
73 QUIET_SUBDIR0 = +@subdir= 73 QUIET_SUBDIR0 = +@subdir=
74 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 74 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
75 $(MAKE) $(PRINT_DIR) -C $$subdir 75 $(MAKE) $(PRINT_DIR) -C $$subdir
76endif 76endif
77 77
78# 78#
79# Define a pattern rule for automatic dependency building 79# Define a pattern rule for automatic dependency building
80# 80#
81%.d: %.c 81%.d: %.c
82 $(QUIET_MM)$(CC) $(CFLAGS) -MM -MP $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 82 $(QUIET_MM)$(CC) $(CFLAGS) -MM -MP $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
83 83
84# 84#
85# Define a pattern rule for silent object building 85# Define a pattern rule for silent object building
86# 86#
87%.o: %.c 87%.o: %.c
88 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 88 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
89 89
90 90
91EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread 91EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread
92OBJECTS = 92OBJECTS =
93OBJECTS += cache.o 93OBJECTS += cache.o
94OBJECTS += cgit.o 94OBJECTS += cgit.o
95OBJECTS += cmd.o 95OBJECTS += cmd.o
96OBJECTS += configfile.o 96OBJECTS += configfile.o
97OBJECTS += html.o 97OBJECTS += html.o
98OBJECTS += parsing.o 98OBJECTS += parsing.o
99OBJECTS += scan-tree.o 99OBJECTS += scan-tree.o
100OBJECTS += shared.o 100OBJECTS += shared.o
101OBJECTS += ui-atom.o 101OBJECTS += ui-atom.o
102OBJECTS += ui-blob.o 102OBJECTS += ui-blob.o
103OBJECTS += ui-clone.o 103OBJECTS += ui-clone.o
104OBJECTS += ui-commit.o 104OBJECTS += ui-commit.o
105OBJECTS += ui-diff.o 105OBJECTS += ui-diff.o
106OBJECTS += ui-log.o 106OBJECTS += ui-log.o
107OBJECTS += ui-patch.o 107OBJECTS += ui-patch.o
108OBJECTS += ui-plain.o 108OBJECTS += ui-plain.o
109OBJECTS += ui-refs.o 109OBJECTS += ui-refs.o
110OBJECTS += ui-repolist.o 110OBJECTS += ui-repolist.o
111OBJECTS += ui-shared.o 111OBJECTS += ui-shared.o
112OBJECTS += ui-snapshot.o 112OBJECTS += ui-snapshot.o
113OBJECTS += ui-ssdiff.o 113OBJECTS += ui-ssdiff.o
114OBJECTS += ui-stats.o 114OBJECTS += ui-stats.o
115OBJECTS += ui-summary.o 115OBJECTS += ui-summary.o
116OBJECTS += ui-tag.o 116OBJECTS += ui-tag.o
117OBJECTS += ui-tree.o 117OBJECTS += ui-tree.o
118OBJECTS += vector.o
118 119
119ifdef NEEDS_LIBICONV 120ifdef NEEDS_LIBICONV
120 EXTLIBS += -liconv 121 EXTLIBS += -liconv
121endif 122endif
122 123
123 124
124.PHONY: all libgit test install uninstall clean force-version get-git \ 125.PHONY: all libgit test install uninstall clean force-version get-git \
125 doc clean-doc install-doc install-man install-html install-pdf \ 126 doc clean-doc install-doc install-man install-html install-pdf \
126 uninstall-doc uninstall-man uninstall-html uninstall-pdf 127 uninstall-doc uninstall-man uninstall-html uninstall-pdf
127 128
128all: cgit 129all: cgit
129 130
130VERSION: force-version 131VERSION: force-version
131 @./gen-version.sh "$(CGIT_VERSION)" 132 @./gen-version.sh "$(CGIT_VERSION)"
132-include VERSION 133-include VERSION
133 134
134 135
135CFLAGS += -g -Wall -Igit 136CFLAGS += -g -Wall -Igit
136CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 137CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
137CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 138CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
138CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 139CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
139CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 140CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
140CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 141CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
141 142
142GIT_OPTIONS = prefix=/usr 143GIT_OPTIONS = prefix=/usr
143 144
144ifdef NO_ICONV 145ifdef NO_ICONV
145 CFLAGS += -DNO_ICONV 146 CFLAGS += -DNO_ICONV
146endif 147endif
147ifdef NO_STRCASESTR 148ifdef NO_STRCASESTR
148 CFLAGS += -DNO_STRCASESTR 149 CFLAGS += -DNO_STRCASESTR
149endif 150endif
150ifdef NO_C99_FORMAT 151ifdef NO_C99_FORMAT
151 CFLAGS += -DNO_C99_FORMAT 152 CFLAGS += -DNO_C99_FORMAT
152endif 153endif
153ifdef NO_OPENSSL 154ifdef NO_OPENSSL
154 CFLAGS += -DNO_OPENSSL 155 CFLAGS += -DNO_OPENSSL
155 GIT_OPTIONS += NO_OPENSSL=1 156 GIT_OPTIONS += NO_OPENSSL=1
156else 157else
157 EXTLIBS += -lcrypto 158 EXTLIBS += -lcrypto
158endif 159endif
159 160
160cgit: $(OBJECTS) libgit 161cgit: $(OBJECTS) libgit
161 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 162 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
162 163
163cgit.o: VERSION 164cgit.o: VERSION
164 165
165ifneq "$(MAKECMDGOALS)" "clean" 166ifneq "$(MAKECMDGOALS)" "clean"
diff --git a/cgit.c b/cgit.c
index e8c1f94..916feb4 100644
--- a/cgit.c
+++ b/cgit.c
@@ -12,238 +12,244 @@
12#include "cmd.h" 12#include "cmd.h"
13#include "configfile.h" 13#include "configfile.h"
14#include "html.h" 14#include "html.h"
15#include "ui-shared.h" 15#include "ui-shared.h"
16#include "ui-stats.h" 16#include "ui-stats.h"
17#include "scan-tree.h" 17#include "scan-tree.h"
18 18
19const char *cgit_version = CGIT_VERSION; 19const char *cgit_version = CGIT_VERSION;
20 20
21void add_mimetype(const char *name, const char *value) 21void add_mimetype(const char *name, const char *value)
22{ 22{
23 struct string_list_item *item; 23 struct string_list_item *item;
24 24
25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); 25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
26 item->util = xstrdup(value); 26 item->util = xstrdup(value);
27} 27}
28 28
29struct cgit_filter *new_filter(const char *cmd, int extra_args) 29struct cgit_filter *new_filter(const char *cmd, int extra_args)
30{ 30{
31 struct cgit_filter *f; 31 struct cgit_filter *f;
32 32
33 if (!cmd || !cmd[0]) 33 if (!cmd || !cmd[0])
34 return NULL; 34 return NULL;
35 35
36 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
39 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
40 f->argv[1] = NULL; 40 f->argv[1] = NULL;
41 return f; 41 return f;
42} 42}
43 43
44static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
45 45
46void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
47{ 47{
48 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
49 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
50 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
51 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
52 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
53 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
54 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
55 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
56 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
57 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
60 else if (!strcmp(name, "enable-commit-graph"))
61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
60 else if (!strcmp(name, "enable-log-filecount")) 62 else if (!strcmp(name, "enable-log-filecount"))
61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
62 else if (!strcmp(name, "enable-log-linecount")) 64 else if (!strcmp(name, "enable-log-linecount"))
63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
64 else if (!strcmp(name, "enable-remote-branches")) 66 else if (!strcmp(name, "enable-remote-branches"))
65 repo->enable_remote_branches = atoi(value); 67 repo->enable_remote_branches = atoi(value);
66 else if (!strcmp(name, "enable-subject-links")) 68 else if (!strcmp(name, "enable-subject-links"))
67 repo->enable_subject_links = atoi(value); 69 repo->enable_subject_links = atoi(value);
68 else if (!strcmp(name, "max-stats")) 70 else if (!strcmp(name, "max-stats"))
69 repo->max_stats = cgit_find_stats_period(value, NULL); 71 repo->max_stats = cgit_find_stats_period(value, NULL);
70 else if (!strcmp(name, "module-link")) 72 else if (!strcmp(name, "module-link"))
71 repo->module_link= xstrdup(value); 73 repo->module_link= xstrdup(value);
72 else if (!strcmp(name, "section")) 74 else if (!strcmp(name, "section"))
73 repo->section = xstrdup(value); 75 repo->section = xstrdup(value);
74 else if (!strcmp(name, "readme") && value != NULL) 76 else if (!strcmp(name, "readme") && value != NULL)
75 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
76 else if (!strcmp(name, "logo") && value != NULL) 78 else if (!strcmp(name, "logo") && value != NULL)
77 repo->logo = xstrdup(value); 79 repo->logo = xstrdup(value);
78 else if (!strcmp(name, "logo-link") && value != NULL) 80 else if (!strcmp(name, "logo-link") && value != NULL)
79 repo->logo_link = xstrdup(value); 81 repo->logo_link = xstrdup(value);
80 else if (ctx.cfg.enable_filter_overrides) { 82 else if (ctx.cfg.enable_filter_overrides) {
81 if (!strcmp(name, "about-filter")) 83 if (!strcmp(name, "about-filter"))
82 repo->about_filter = new_filter(value, 0); 84 repo->about_filter = new_filter(value, 0);
83 else if (!strcmp(name, "commit-filter")) 85 else if (!strcmp(name, "commit-filter"))
84 repo->commit_filter = new_filter(value, 0); 86 repo->commit_filter = new_filter(value, 0);
85 else if (!strcmp(name, "source-filter")) 87 else if (!strcmp(name, "source-filter"))
86 repo->source_filter = new_filter(value, 1); 88 repo->source_filter = new_filter(value, 1);
87 } 89 }
88} 90}
89 91
90void config_cb(const char *name, const char *value) 92void config_cb(const char *name, const char *value)
91{ 93{
92 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 94 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
93 ctx.cfg.section = xstrdup(value); 95 ctx.cfg.section = xstrdup(value);
94 else if (!strcmp(name, "repo.url")) 96 else if (!strcmp(name, "repo.url"))
95 ctx.repo = cgit_add_repo(value); 97 ctx.repo = cgit_add_repo(value);
96 else if (ctx.repo && !strcmp(name, "repo.path")) 98 else if (ctx.repo && !strcmp(name, "repo.path"))
97 ctx.repo->path = trim_end(value, '/'); 99 ctx.repo->path = trim_end(value, '/');
98 else if (ctx.repo && !prefixcmp(name, "repo.")) 100 else if (ctx.repo && !prefixcmp(name, "repo."))
99 repo_config(ctx.repo, name + 5, value); 101 repo_config(ctx.repo, name + 5, value);
100 else if (!strcmp(name, "readme")) 102 else if (!strcmp(name, "readme"))
101 ctx.cfg.readme = xstrdup(value); 103 ctx.cfg.readme = xstrdup(value);
102 else if (!strcmp(name, "root-title")) 104 else if (!strcmp(name, "root-title"))
103 ctx.cfg.root_title = xstrdup(value); 105 ctx.cfg.root_title = xstrdup(value);
104 else if (!strcmp(name, "root-desc")) 106 else if (!strcmp(name, "root-desc"))
105 ctx.cfg.root_desc = xstrdup(value); 107 ctx.cfg.root_desc = xstrdup(value);
106 else if (!strcmp(name, "root-readme")) 108 else if (!strcmp(name, "root-readme"))
107 ctx.cfg.root_readme = xstrdup(value); 109 ctx.cfg.root_readme = xstrdup(value);
108 else if (!strcmp(name, "css")) 110 else if (!strcmp(name, "css"))
109 ctx.cfg.css = xstrdup(value); 111 ctx.cfg.css = xstrdup(value);
110 else if (!strcmp(name, "favicon")) 112 else if (!strcmp(name, "favicon"))
111 ctx.cfg.favicon = xstrdup(value); 113 ctx.cfg.favicon = xstrdup(value);
112 else if (!strcmp(name, "footer")) 114 else if (!strcmp(name, "footer"))
113 ctx.cfg.footer = xstrdup(value); 115 ctx.cfg.footer = xstrdup(value);
114 else if (!strcmp(name, "head-include")) 116 else if (!strcmp(name, "head-include"))
115 ctx.cfg.head_include = xstrdup(value); 117 ctx.cfg.head_include = xstrdup(value);
116 else if (!strcmp(name, "header")) 118 else if (!strcmp(name, "header"))
117 ctx.cfg.header = xstrdup(value); 119 ctx.cfg.header = xstrdup(value);
118 else if (!strcmp(name, "logo")) 120 else if (!strcmp(name, "logo"))
119 ctx.cfg.logo = xstrdup(value); 121 ctx.cfg.logo = xstrdup(value);
120 else if (!strcmp(name, "index-header")) 122 else if (!strcmp(name, "index-header"))
121 ctx.cfg.index_header = xstrdup(value); 123 ctx.cfg.index_header = xstrdup(value);
122 else if (!strcmp(name, "index-info")) 124 else if (!strcmp(name, "index-info"))
123 ctx.cfg.index_info = xstrdup(value); 125 ctx.cfg.index_info = xstrdup(value);
124 else if (!strcmp(name, "logo-link")) 126 else if (!strcmp(name, "logo-link"))
125 ctx.cfg.logo_link = xstrdup(value); 127 ctx.cfg.logo_link = xstrdup(value);
126 else if (!strcmp(name, "module-link")) 128 else if (!strcmp(name, "module-link"))
127 ctx.cfg.module_link = xstrdup(value); 129 ctx.cfg.module_link = xstrdup(value);
128 else if (!strcmp(name, "strict-export")) 130 else if (!strcmp(name, "strict-export"))
129 ctx.cfg.strict_export = xstrdup(value); 131 ctx.cfg.strict_export = xstrdup(value);
130 else if (!strcmp(name, "virtual-root")) { 132 else if (!strcmp(name, "virtual-root")) {
131 ctx.cfg.virtual_root = trim_end(value, '/'); 133 ctx.cfg.virtual_root = trim_end(value, '/');
132 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 134 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
133 ctx.cfg.virtual_root = ""; 135 ctx.cfg.virtual_root = "";
134 } else if (!strcmp(name, "nocache")) 136 } else if (!strcmp(name, "nocache"))
135 ctx.cfg.nocache = atoi(value); 137 ctx.cfg.nocache = atoi(value);
136 else if (!strcmp(name, "noplainemail")) 138 else if (!strcmp(name, "noplainemail"))
137 ctx.cfg.noplainemail = atoi(value); 139 ctx.cfg.noplainemail = atoi(value);
138 else if (!strcmp(name, "noheader")) 140 else if (!strcmp(name, "noheader"))
139 ctx.cfg.noheader = atoi(value); 141 ctx.cfg.noheader = atoi(value);
140 else if (!strcmp(name, "snapshots")) 142 else if (!strcmp(name, "snapshots"))
141 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 143 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
142 else if (!strcmp(name, "enable-filter-overrides")) 144 else if (!strcmp(name, "enable-filter-overrides"))
143 ctx.cfg.enable_filter_overrides = atoi(value); 145 ctx.cfg.enable_filter_overrides = atoi(value);
144 else if (!strcmp(name, "enable-gitweb-owner")) 146 else if (!strcmp(name, "enable-gitweb-owner"))
145 ctx.cfg.enable_gitweb_owner = atoi(value); 147 ctx.cfg.enable_gitweb_owner = atoi(value);
146 else if (!strcmp(name, "enable-index-links")) 148 else if (!strcmp(name, "enable-index-links"))
147 ctx.cfg.enable_index_links = atoi(value); 149 ctx.cfg.enable_index_links = atoi(value);
150 else if (!strcmp(name, "enable-commit-graph"))
151 ctx.cfg.enable_commit_graph = atoi(value);
148 else if (!strcmp(name, "enable-log-filecount")) 152 else if (!strcmp(name, "enable-log-filecount"))
149 ctx.cfg.enable_log_filecount = atoi(value); 153 ctx.cfg.enable_log_filecount = atoi(value);
150 else if (!strcmp(name, "enable-log-linecount")) 154 else if (!strcmp(name, "enable-log-linecount"))
151 ctx.cfg.enable_log_linecount = atoi(value); 155 ctx.cfg.enable_log_linecount = atoi(value);
152 else if (!strcmp(name, "enable-remote-branches")) 156 else if (!strcmp(name, "enable-remote-branches"))
153 ctx.cfg.enable_remote_branches = atoi(value); 157 ctx.cfg.enable_remote_branches = atoi(value);
154 else if (!strcmp(name, "enable-subject-links")) 158 else if (!strcmp(name, "enable-subject-links"))
155 ctx.cfg.enable_subject_links = atoi(value); 159 ctx.cfg.enable_subject_links = atoi(value);
156 else if (!strcmp(name, "enable-tree-linenumbers")) 160 else if (!strcmp(name, "enable-tree-linenumbers"))
157 ctx.cfg.enable_tree_linenumbers = atoi(value); 161 ctx.cfg.enable_tree_linenumbers = atoi(value);
158 else if (!strcmp(name, "max-stats")) 162 else if (!strcmp(name, "max-stats"))
159 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 163 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
160 else if (!strcmp(name, "cache-size")) 164 else if (!strcmp(name, "cache-size"))
161 ctx.cfg.cache_size = atoi(value); 165 ctx.cfg.cache_size = atoi(value);
162 else if (!strcmp(name, "cache-root")) 166 else if (!strcmp(name, "cache-root"))
163 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 167 ctx.cfg.cache_root = xstrdup(expand_macros(value));
164 else if (!strcmp(name, "cache-root-ttl")) 168 else if (!strcmp(name, "cache-root-ttl"))
165 ctx.cfg.cache_root_ttl = atoi(value); 169 ctx.cfg.cache_root_ttl = atoi(value);
166 else if (!strcmp(name, "cache-repo-ttl")) 170 else if (!strcmp(name, "cache-repo-ttl"))
167 ctx.cfg.cache_repo_ttl = atoi(value); 171 ctx.cfg.cache_repo_ttl = atoi(value);
168 else if (!strcmp(name, "cache-scanrc-ttl")) 172 else if (!strcmp(name, "cache-scanrc-ttl"))
169 ctx.cfg.cache_scanrc_ttl = atoi(value); 173 ctx.cfg.cache_scanrc_ttl = atoi(value);
170 else if (!strcmp(name, "cache-static-ttl")) 174 else if (!strcmp(name, "cache-static-ttl"))
171 ctx.cfg.cache_static_ttl = atoi(value); 175 ctx.cfg.cache_static_ttl = atoi(value);
172 else if (!strcmp(name, "cache-dynamic-ttl")) 176 else if (!strcmp(name, "cache-dynamic-ttl"))
173 ctx.cfg.cache_dynamic_ttl = atoi(value); 177 ctx.cfg.cache_dynamic_ttl = atoi(value);
174 else if (!strcmp(name, "about-filter")) 178 else if (!strcmp(name, "about-filter"))
175 ctx.cfg.about_filter = new_filter(value, 0); 179 ctx.cfg.about_filter = new_filter(value, 0);
176 else if (!strcmp(name, "commit-filter")) 180 else if (!strcmp(name, "commit-filter"))
177 ctx.cfg.commit_filter = new_filter(value, 0); 181 ctx.cfg.commit_filter = new_filter(value, 0);
178 else if (!strcmp(name, "embedded")) 182 else if (!strcmp(name, "embedded"))
179 ctx.cfg.embedded = atoi(value); 183 ctx.cfg.embedded = atoi(value);
180 else if (!strcmp(name, "max-atom-items")) 184 else if (!strcmp(name, "max-atom-items"))
181 ctx.cfg.max_atom_items = atoi(value); 185 ctx.cfg.max_atom_items = atoi(value);
182 else if (!strcmp(name, "max-message-length")) 186 else if (!strcmp(name, "max-message-length"))
183 ctx.cfg.max_msg_len = atoi(value); 187 ctx.cfg.max_msg_len = atoi(value);
184 else if (!strcmp(name, "max-repodesc-length")) 188 else if (!strcmp(name, "max-repodesc-length"))
185 ctx.cfg.max_repodesc_len = atoi(value); 189 ctx.cfg.max_repodesc_len = atoi(value);
186 else if (!strcmp(name, "max-blob-size")) 190 else if (!strcmp(name, "max-blob-size"))
187 ctx.cfg.max_blob_size = atoi(value); 191 ctx.cfg.max_blob_size = atoi(value);
188 else if (!strcmp(name, "max-repo-count")) 192 else if (!strcmp(name, "max-repo-count"))
189 ctx.cfg.max_repo_count = atoi(value); 193 ctx.cfg.max_repo_count = atoi(value);
190 else if (!strcmp(name, "max-commit-count")) 194 else if (!strcmp(name, "max-commit-count"))
191 ctx.cfg.max_commit_count = atoi(value); 195 ctx.cfg.max_commit_count = atoi(value);
192 else if (!strcmp(name, "project-list")) 196 else if (!strcmp(name, "project-list"))
193 ctx.cfg.project_list = xstrdup(expand_macros(value)); 197 ctx.cfg.project_list = xstrdup(expand_macros(value));
194 else if (!strcmp(name, "scan-path")) 198 else if (!strcmp(name, "scan-path"))
195 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 199 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
196 process_cached_repolist(expand_macros(value)); 200 process_cached_repolist(expand_macros(value));
197 else if (ctx.cfg.project_list) 201 else if (ctx.cfg.project_list)
198 scan_projects(expand_macros(value), 202 scan_projects(expand_macros(value),
199 ctx.cfg.project_list, repo_config); 203 ctx.cfg.project_list, repo_config);
200 else 204 else
201 scan_tree(expand_macros(value), repo_config); 205 scan_tree(expand_macros(value), repo_config);
206 else if (!strcmp(name, "scan-hidden-path"))
207 ctx.cfg.scan_hidden_path = atoi(value);
202 else if (!strcmp(name, "section-from-path")) 208 else if (!strcmp(name, "section-from-path"))
203 ctx.cfg.section_from_path = atoi(value); 209 ctx.cfg.section_from_path = atoi(value);
204 else if (!strcmp(name, "source-filter")) 210 else if (!strcmp(name, "source-filter"))
205 ctx.cfg.source_filter = new_filter(value, 1); 211 ctx.cfg.source_filter = new_filter(value, 1);
206 else if (!strcmp(name, "summary-log")) 212 else if (!strcmp(name, "summary-log"))
207 ctx.cfg.summary_log = atoi(value); 213 ctx.cfg.summary_log = atoi(value);
208 else if (!strcmp(name, "summary-branches")) 214 else if (!strcmp(name, "summary-branches"))
209 ctx.cfg.summary_branches = atoi(value); 215 ctx.cfg.summary_branches = atoi(value);
210 else if (!strcmp(name, "summary-tags")) 216 else if (!strcmp(name, "summary-tags"))
211 ctx.cfg.summary_tags = atoi(value); 217 ctx.cfg.summary_tags = atoi(value);
212 else if (!strcmp(name, "side-by-side-diffs")) 218 else if (!strcmp(name, "side-by-side-diffs"))
213 ctx.cfg.ssdiff = atoi(value); 219 ctx.cfg.ssdiff = atoi(value);
214 else if (!strcmp(name, "agefile")) 220 else if (!strcmp(name, "agefile"))
215 ctx.cfg.agefile = xstrdup(value); 221 ctx.cfg.agefile = xstrdup(value);
216 else if (!strcmp(name, "renamelimit")) 222 else if (!strcmp(name, "renamelimit"))
217 ctx.cfg.renamelimit = atoi(value); 223 ctx.cfg.renamelimit = atoi(value);
218 else if (!strcmp(name, "remove-suffix")) 224 else if (!strcmp(name, "remove-suffix"))
219 ctx.cfg.remove_suffix = atoi(value); 225 ctx.cfg.remove_suffix = atoi(value);
220 else if (!strcmp(name, "robots")) 226 else if (!strcmp(name, "robots"))
221 ctx.cfg.robots = xstrdup(value); 227 ctx.cfg.robots = xstrdup(value);
222 else if (!strcmp(name, "clone-prefix")) 228 else if (!strcmp(name, "clone-prefix"))
223 ctx.cfg.clone_prefix = xstrdup(value); 229 ctx.cfg.clone_prefix = xstrdup(value);
224 else if (!strcmp(name, "local-time")) 230 else if (!strcmp(name, "local-time"))
225 ctx.cfg.local_time = atoi(value); 231 ctx.cfg.local_time = atoi(value);
226 else if (!prefixcmp(name, "mimetype.")) 232 else if (!prefixcmp(name, "mimetype."))
227 add_mimetype(name + 9, value); 233 add_mimetype(name + 9, value);
228 else if (!strcmp(name, "include")) 234 else if (!strcmp(name, "include"))
229 parse_configfile(expand_macros(value), config_cb); 235 parse_configfile(expand_macros(value), config_cb);
230} 236}
231 237
232static void querystring_cb(const char *name, const char *value) 238static void querystring_cb(const char *name, const char *value)
233{ 239{
234 if (!value) 240 if (!value)
235 value = ""; 241 value = "";
236 242
237 if (!strcmp(name,"r")) { 243 if (!strcmp(name,"r")) {
238 ctx.qry.repo = xstrdup(value); 244 ctx.qry.repo = xstrdup(value);
239 ctx.repo = cgit_get_repoinfo(value); 245 ctx.repo = cgit_get_repoinfo(value);
240 } else if (!strcmp(name, "p")) { 246 } else if (!strcmp(name, "p")) {
241 ctx.qry.page = xstrdup(value); 247 ctx.qry.page = xstrdup(value);
242 } else if (!strcmp(name, "url")) { 248 } else if (!strcmp(name, "url")) {
243 if (*value == '/') 249 if (*value == '/')
244 value++; 250 value++;
245 ctx.qry.url = xstrdup(value); 251 ctx.qry.url = xstrdup(value);
246 cgit_parse_url(value); 252 cgit_parse_url(value);
247 } else if (!strcmp(name, "qt")) { 253 } else if (!strcmp(name, "qt")) {
248 ctx.qry.grep = xstrdup(value); 254 ctx.qry.grep = xstrdup(value);
249 } else if (!strcmp(name, "q")) { 255 } else if (!strcmp(name, "q")) {
@@ -274,96 +280,97 @@ static void querystring_cb(const char *name, const char *value)
274 } else if (!strcmp(name, "ss")) { 280 } else if (!strcmp(name, "ss")) {
275 ctx.qry.ssdiff = atoi(value); 281 ctx.qry.ssdiff = atoi(value);
276 } else if (!strcmp(name, "all")) { 282 } else if (!strcmp(name, "all")) {
277 ctx.qry.show_all = atoi(value); 283 ctx.qry.show_all = atoi(value);
278 } else if (!strcmp(name, "context")) { 284 } else if (!strcmp(name, "context")) {
279 ctx.qry.context = atoi(value); 285 ctx.qry.context = atoi(value);
280 } else if (!strcmp(name, "ignorews")) { 286 } else if (!strcmp(name, "ignorews")) {
281 ctx.qry.ignorews = atoi(value); 287 ctx.qry.ignorews = atoi(value);
282 } 288 }
283} 289}
284 290
285char *xstrdupn(const char *str) 291char *xstrdupn(const char *str)
286{ 292{
287 return (str ? xstrdup(str) : NULL); 293 return (str ? xstrdup(str) : NULL);
288} 294}
289 295
290static void prepare_context(struct cgit_context *ctx) 296static void prepare_context(struct cgit_context *ctx)
291{ 297{
292 memset(ctx, 0, sizeof(*ctx)); 298 memset(ctx, 0, sizeof(*ctx));
293 ctx->cfg.agefile = "info/web/last-modified"; 299 ctx->cfg.agefile = "info/web/last-modified";
294 ctx->cfg.nocache = 0; 300 ctx->cfg.nocache = 0;
295 ctx->cfg.cache_size = 0; 301 ctx->cfg.cache_size = 0;
296 ctx->cfg.cache_dynamic_ttl = 5; 302 ctx->cfg.cache_dynamic_ttl = 5;
297 ctx->cfg.cache_max_create_time = 5; 303 ctx->cfg.cache_max_create_time = 5;
298 ctx->cfg.cache_repo_ttl = 5; 304 ctx->cfg.cache_repo_ttl = 5;
299 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 305 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
300 ctx->cfg.cache_root_ttl = 5; 306 ctx->cfg.cache_root_ttl = 5;
301 ctx->cfg.cache_scanrc_ttl = 15; 307 ctx->cfg.cache_scanrc_ttl = 15;
302 ctx->cfg.cache_static_ttl = -1; 308 ctx->cfg.cache_static_ttl = -1;
303 ctx->cfg.css = "/cgit.css"; 309 ctx->cfg.css = "/cgit.css";
304 ctx->cfg.logo = "/cgit.png"; 310 ctx->cfg.logo = "/cgit.png";
305 ctx->cfg.local_time = 0; 311 ctx->cfg.local_time = 0;
306 ctx->cfg.enable_gitweb_owner = 1; 312 ctx->cfg.enable_gitweb_owner = 1;
307 ctx->cfg.enable_tree_linenumbers = 1; 313 ctx->cfg.enable_tree_linenumbers = 1;
308 ctx->cfg.max_repo_count = 50; 314 ctx->cfg.max_repo_count = 50;
309 ctx->cfg.max_commit_count = 50; 315 ctx->cfg.max_commit_count = 50;
310 ctx->cfg.max_lock_attempts = 5; 316 ctx->cfg.max_lock_attempts = 5;
311 ctx->cfg.max_msg_len = 80; 317 ctx->cfg.max_msg_len = 80;
312 ctx->cfg.max_repodesc_len = 80; 318 ctx->cfg.max_repodesc_len = 80;
313 ctx->cfg.max_blob_size = 0; 319 ctx->cfg.max_blob_size = 0;
314 ctx->cfg.max_stats = 0; 320 ctx->cfg.max_stats = 0;
315 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 321 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
316 ctx->cfg.project_list = NULL; 322 ctx->cfg.project_list = NULL;
317 ctx->cfg.renamelimit = -1; 323 ctx->cfg.renamelimit = -1;
318 ctx->cfg.remove_suffix = 0; 324 ctx->cfg.remove_suffix = 0;
319 ctx->cfg.robots = "index, nofollow"; 325 ctx->cfg.robots = "index, nofollow";
320 ctx->cfg.root_title = "Git repository browser"; 326 ctx->cfg.root_title = "Git repository browser";
321 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 327 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
328 ctx->cfg.scan_hidden_path = 0;
322 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 329 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
323 ctx->cfg.section = ""; 330 ctx->cfg.section = "";
324 ctx->cfg.summary_branches = 10; 331 ctx->cfg.summary_branches = 10;
325 ctx->cfg.summary_log = 10; 332 ctx->cfg.summary_log = 10;
326 ctx->cfg.summary_tags = 10; 333 ctx->cfg.summary_tags = 10;
327 ctx->cfg.max_atom_items = 10; 334 ctx->cfg.max_atom_items = 10;
328 ctx->cfg.ssdiff = 0; 335 ctx->cfg.ssdiff = 0;
329 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 336 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
330 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 337 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
331 ctx->env.https = xstrdupn(getenv("HTTPS")); 338 ctx->env.https = xstrdupn(getenv("HTTPS"));
332 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 339 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
333 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 340 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
334 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 341 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
335 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 342 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
336 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 343 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
337 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 344 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
338 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 345 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
339 ctx->page.mimetype = "text/html"; 346 ctx->page.mimetype = "text/html";
340 ctx->page.charset = PAGE_ENCODING; 347 ctx->page.charset = PAGE_ENCODING;
341 ctx->page.filename = NULL; 348 ctx->page.filename = NULL;
342 ctx->page.size = 0; 349 ctx->page.size = 0;
343 ctx->page.modified = time(NULL); 350 ctx->page.modified = time(NULL);
344 ctx->page.expires = ctx->page.modified; 351 ctx->page.expires = ctx->page.modified;
345 ctx->page.etag = NULL; 352 ctx->page.etag = NULL;
346 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 353 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
347 if (ctx->env.script_name) 354 if (ctx->env.script_name)
348 ctx->cfg.script_name = ctx->env.script_name; 355 ctx->cfg.script_name = ctx->env.script_name;
349 if (ctx->env.query_string) 356 if (ctx->env.query_string)
350 ctx->qry.raw = ctx->env.query_string; 357 ctx->qry.raw = ctx->env.query_string;
351 if (!ctx->env.cgit_config) 358 if (!ctx->env.cgit_config)
352 ctx->env.cgit_config = CGIT_CONFIG; 359 ctx->env.cgit_config = CGIT_CONFIG;
353} 360}
354 361
355struct refmatch { 362struct refmatch {
356 char *req_ref; 363 char *req_ref;
357 char *first_ref; 364 char *first_ref;
358 int match; 365 int match;
359}; 366};
360 367
361int find_current_ref(const char *refname, const unsigned char *sha1, 368int find_current_ref(const char *refname, const unsigned char *sha1,
362 int flags, void *cb_data) 369 int flags, void *cb_data)
363{ 370{
364 struct refmatch *info; 371 struct refmatch *info;
365 372
366 info = (struct refmatch *)cb_data; 373 info = (struct refmatch *)cb_data;
367 if (!strcmp(refname, info->req_ref)) 374 if (!strcmp(refname, info->req_ref))
368 info->match = 1; 375 info->match = 1;
369 if (!info->first_ref) 376 if (!info->first_ref)
@@ -499,96 +506,98 @@ char *build_snapshot_setting(int bitmap)
499 char *result = xstrdup(""); 506 char *result = xstrdup("");
500 char *tmp; 507 char *tmp;
501 int len; 508 int len;
502 509
503 for (f = cgit_snapshot_formats; f->suffix; f++) { 510 for (f = cgit_snapshot_formats; f->suffix; f++) {
504 if (f->bit & bitmap) { 511 if (f->bit & bitmap) {
505 tmp = result; 512 tmp = result;
506 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 513 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
507 free(tmp); 514 free(tmp);
508 } 515 }
509 } 516 }
510 len = strlen(result); 517 len = strlen(result);
511 if (len) 518 if (len)
512 result[len - 1] = '\0'; 519 result[len - 1] = '\0';
513 return result; 520 return result;
514} 521}
515 522
516char *get_first_line(char *txt) 523char *get_first_line(char *txt)
517{ 524{
518 char *t = xstrdup(txt); 525 char *t = xstrdup(txt);
519 char *p = strchr(t, '\n'); 526 char *p = strchr(t, '\n');
520 if (p) 527 if (p)
521 *p = '\0'; 528 *p = '\0';
522 return t; 529 return t;
523} 530}
524 531
525void print_repo(FILE *f, struct cgit_repo *repo) 532void print_repo(FILE *f, struct cgit_repo *repo)
526{ 533{
527 fprintf(f, "repo.url=%s\n", repo->url); 534 fprintf(f, "repo.url=%s\n", repo->url);
528 fprintf(f, "repo.name=%s\n", repo->name); 535 fprintf(f, "repo.name=%s\n", repo->name);
529 fprintf(f, "repo.path=%s\n", repo->path); 536 fprintf(f, "repo.path=%s\n", repo->path);
530 if (repo->owner) 537 if (repo->owner)
531 fprintf(f, "repo.owner=%s\n", repo->owner); 538 fprintf(f, "repo.owner=%s\n", repo->owner);
532 if (repo->desc) { 539 if (repo->desc) {
533 char *tmp = get_first_line(repo->desc); 540 char *tmp = get_first_line(repo->desc);
534 fprintf(f, "repo.desc=%s\n", tmp); 541 fprintf(f, "repo.desc=%s\n", tmp);
535 free(tmp); 542 free(tmp);
536 } 543 }
537 if (repo->readme) 544 if (repo->readme)
538 fprintf(f, "repo.readme=%s\n", repo->readme); 545 fprintf(f, "repo.readme=%s\n", repo->readme);
539 if (repo->defbranch) 546 if (repo->defbranch)
540 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 547 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
541 if (repo->module_link) 548 if (repo->module_link)
542 fprintf(f, "repo.module-link=%s\n", repo->module_link); 549 fprintf(f, "repo.module-link=%s\n", repo->module_link);
543 if (repo->section) 550 if (repo->section)
544 fprintf(f, "repo.section=%s\n", repo->section); 551 fprintf(f, "repo.section=%s\n", repo->section);
545 if (repo->clone_url) 552 if (repo->clone_url)
546 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 553 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
554 fprintf(f, "repo.enable-commit-graph=%d\n",
555 repo->enable_commit_graph);
547 fprintf(f, "repo.enable-log-filecount=%d\n", 556 fprintf(f, "repo.enable-log-filecount=%d\n",
548 repo->enable_log_filecount); 557 repo->enable_log_filecount);
549 fprintf(f, "repo.enable-log-linecount=%d\n", 558 fprintf(f, "repo.enable-log-linecount=%d\n",
550 repo->enable_log_linecount); 559 repo->enable_log_linecount);
551 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 560 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
552 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 561 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
553 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 562 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
554 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 563 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
555 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 564 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
556 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 565 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
557 if (repo->snapshots != ctx.cfg.snapshots) { 566 if (repo->snapshots != ctx.cfg.snapshots) {
558 char *tmp = build_snapshot_setting(repo->snapshots); 567 char *tmp = build_snapshot_setting(repo->snapshots);
559 fprintf(f, "repo.snapshots=%s\n", tmp); 568 fprintf(f, "repo.snapshots=%s\n", tmp);
560 free(tmp); 569 free(tmp);
561 } 570 }
562 if (repo->max_stats != ctx.cfg.max_stats) 571 if (repo->max_stats != ctx.cfg.max_stats)
563 fprintf(f, "repo.max-stats=%s\n", 572 fprintf(f, "repo.max-stats=%s\n",
564 cgit_find_stats_periodname(repo->max_stats)); 573 cgit_find_stats_periodname(repo->max_stats));
565 fprintf(f, "\n"); 574 fprintf(f, "\n");
566} 575}
567 576
568void print_repolist(FILE *f, struct cgit_repolist *list, int start) 577void print_repolist(FILE *f, struct cgit_repolist *list, int start)
569{ 578{
570 int i; 579 int i;
571 580
572 for(i = start; i < list->count; i++) 581 for(i = start; i < list->count; i++)
573 print_repo(f, &list->repos[i]); 582 print_repo(f, &list->repos[i]);
574} 583}
575 584
576/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 585/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
577 * and return 0 on success. 586 * and return 0 on success.
578 */ 587 */
579static int generate_cached_repolist(const char *path, const char *cached_rc) 588static int generate_cached_repolist(const char *path, const char *cached_rc)
580{ 589{
581 char *locked_rc; 590 char *locked_rc;
582 int idx; 591 int idx;
583 FILE *f; 592 FILE *f;
584 593
585 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 594 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
586 f = fopen(locked_rc, "wx"); 595 f = fopen(locked_rc, "wx");
587 if (!f) { 596 if (!f) {
588 /* Inform about the error unless the lockfile already existed, 597 /* Inform about the error unless the lockfile already existed,
589 * since that only means we've got concurrent requests. 598 * since that only means we've got concurrent requests.
590 */ 599 */
591 if (errno != EEXIST) 600 if (errno != EEXIST)
592 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 601 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
593 locked_rc, strerror(errno), errno); 602 locked_rc, strerror(errno), errno);
594 return errno; 603 return errno;
diff --git a/cgit.css b/cgit.css
index 3ed1989..1d90057 100644
--- a/cgit.css
+++ b/cgit.css
@@ -108,116 +108,134 @@ div.path {
108 color: #000; 108 color: #000;
109 background-color: #eee; 109 background-color: #eee;
110} 110}
111 111
112div.content { 112div.content {
113 margin: 0px; 113 margin: 0px;
114 padding: 2em; 114 padding: 2em;
115 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
116} 116}
117 117
118 118
119table.list { 119table.list {
120 width: 100%; 120 width: 100%;
121 border: none; 121 border: none;
122 border-collapse: collapse; 122 border-collapse: collapse;
123} 123}
124 124
125table.list tr { 125table.list tr {
126 background: white; 126 background: white;
127} 127}
128 128
129table.list tr.logheader { 129table.list tr.logheader {
130 background: #eee; 130 background: #eee;
131} 131}
132 132
133table.list tr:hover { 133table.list tr:hover {
134 background: #eee; 134 background: #eee;
135} 135}
136 136
137table.list tr.nohover:hover { 137table.list tr.nohover:hover {
138 background: white; 138 background: white;
139} 139}
140 140
141table.list th { 141table.list th {
142 font-weight: bold; 142 font-weight: bold;
143 /* color: #888; 143 /* color: #888;
144 border-top: dashed 1px #888; 144 border-top: dashed 1px #888;
145 border-bottom: dashed 1px #888; 145 border-bottom: dashed 1px #888;
146 */ 146 */
147 padding: 0.1em 0.5em 0.05em 0.5em; 147 padding: 0.1em 0.5em 0.05em 0.5em;
148 vertical-align: baseline; 148 vertical-align: baseline;
149} 149}
150 150
151table.list td { 151table.list td {
152 border: none; 152 border: none;
153 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
154} 154}
155 155
156table.list td.logsubject { 156table.list td.commitgraph {
157 font-family: monospace; 157 font-family: monospace;
158 font-weight: bold; 158 white-space: pre;
159} 159}
160 160
161table.list td.logmsg { 161table.list td.commitgraph .column1 {
162 font-family: monospace; 162 color: #a00;
163 white-space: pre; 163}
164 padding: 1em 0.5em 2em 0.5em; 164
165table.list td.commitgraph .column2 {
166 color: #0a0;
167}
168
169table.list td.commitgraph .column3 {
170 color: #aa0;
165} 171}
166 172
167table.list td.lognotes-label { 173table.list td.commitgraph .column4 {
168 text-align:right; 174 color: #00a;
169 vertical-align:top;
170} 175}
171 176
172table.list td.lognotes { 177table.list td.commitgraph .column5 {
178 color: #a0a;
179}
180
181table.list td.commitgraph .column6 {
182 color: #0aa;
183}
184
185table.list td.logsubject {
186 font-family: monospace;
187 font-weight: bold;
188}
189
190table.list td.logmsg {
173 font-family: monospace; 191 font-family: monospace;
174 white-space: pre; 192 white-space: pre;
175 padding: 0em 0.5em 2em 0.5em; 193 padding: 0 0.5em;
176} 194}
177 195
178table.list td a { 196table.list td a {
179 color: black; 197 color: black;
180} 198}
181 199
182table.list td a.ls-dir { 200table.list td a.ls-dir {
183 font-weight: bold; 201 font-weight: bold;
184 color: #00f; 202 color: #00f;
185} 203}
186 204
187table.list td a:hover { 205table.list td a:hover {
188 color: #00f; 206 color: #00f;
189} 207}
190 208
191img { 209img {
192 border: none; 210 border: none;
193} 211}
194 212
195input#switch-btn { 213input#switch-btn {
196 margin: 2px 0px 0px 0px; 214 margin: 2px 0px 0px 0px;
197} 215}
198 216
199td#sidebar input.txt { 217td#sidebar input.txt {
200 width: 100%; 218 width: 100%;
201 margin: 2px 0px 0px 0px; 219 margin: 2px 0px 0px 0px;
202} 220}
203 221
204table#grid { 222table#grid {
205 margin: 0px; 223 margin: 0px;
206} 224}
207 225
208td#content { 226td#content {
209 vertical-align: top; 227 vertical-align: top;
210 padding: 1em 2em 1em 1em; 228 padding: 1em 2em 1em 1em;
211 border: none; 229 border: none;
212} 230}
213 231
214div#summary { 232div#summary {
215 vertical-align: top; 233 vertical-align: top;
216 margin-bottom: 1em; 234 margin-bottom: 1em;
217} 235}
218 236
219table#downloads { 237table#downloads {
220 float: right; 238 float: right;
221 border-collapse: collapse; 239 border-collapse: collapse;
222 border: solid 1px #777; 240 border: solid 1px #777;
223 margin-left: 0.5em; 241 margin-left: 0.5em;
diff --git a/cgit.h b/cgit.h
index 8a9d5fa..b5f00fc 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,123 +1,125 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22#include <notes.h> 22#include <notes.h>
23#include <graph.h>
23 24
24 25
25/* 26/*
26 * Dateformats used on misc. pages 27 * Dateformats used on misc. pages
27 */ 28 */
28#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 29#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
29#define FMT_SHORTDATE "%Y-%m-%d" 30#define FMT_SHORTDATE "%Y-%m-%d"
30#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 31#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
31 32
32 33
33/* 34/*
34 * Limits used for relative dates 35 * Limits used for relative dates
35 */ 36 */
36#define TM_MIN 60 37#define TM_MIN 60
37#define TM_HOUR (TM_MIN * 60) 38#define TM_HOUR (TM_MIN * 60)
38#define TM_DAY (TM_HOUR * 24) 39#define TM_DAY (TM_HOUR * 24)
39#define TM_WEEK (TM_DAY * 7) 40#define TM_WEEK (TM_DAY * 7)
40#define TM_YEAR (TM_DAY * 365) 41#define TM_YEAR (TM_DAY * 365)
41#define TM_MONTH (TM_YEAR / 12.0) 42#define TM_MONTH (TM_YEAR / 12.0)
42 43
43 44
44/* 45/*
45 * Default encoding 46 * Default encoding
46 */ 47 */
47#define PAGE_ENCODING "UTF-8" 48#define PAGE_ENCODING "UTF-8"
48 49
49typedef void (*configfn)(const char *name, const char *value); 50typedef void (*configfn)(const char *name, const char *value);
50typedef void (*filepair_fn)(struct diff_filepair *pair); 51typedef void (*filepair_fn)(struct diff_filepair *pair);
51typedef void (*linediff_fn)(char *line, int len); 52typedef void (*linediff_fn)(char *line, int len);
52 53
53struct cgit_filter { 54struct cgit_filter {
54 char *cmd; 55 char *cmd;
55 char **argv; 56 char **argv;
56 int old_stdout; 57 int old_stdout;
57 int pipe_fh[2]; 58 int pipe_fh[2];
58 int pid; 59 int pid;
59 int exitstatus; 60 int exitstatus;
60}; 61};
61 62
62struct cgit_repo { 63struct cgit_repo {
63 char *url; 64 char *url;
64 char *name; 65 char *name;
65 char *path; 66 char *path;
66 char *desc; 67 char *desc;
67 char *owner; 68 char *owner;
68 char *defbranch; 69 char *defbranch;
69 char *module_link; 70 char *module_link;
70 char *readme; 71 char *readme;
71 char *section; 72 char *section;
72 char *clone_url; 73 char *clone_url;
73 char *logo; 74 char *logo;
74 char *logo_link; 75 char *logo_link;
75 int snapshots; 76 int snapshots;
77 int enable_commit_graph;
76 int enable_log_filecount; 78 int enable_log_filecount;
77 int enable_log_linecount; 79 int enable_log_linecount;
78 int enable_remote_branches; 80 int enable_remote_branches;
79 int enable_subject_links; 81 int enable_subject_links;
80 int max_stats; 82 int max_stats;
81 time_t mtime; 83 time_t mtime;
82 struct cgit_filter *about_filter; 84 struct cgit_filter *about_filter;
83 struct cgit_filter *commit_filter; 85 struct cgit_filter *commit_filter;
84 struct cgit_filter *source_filter; 86 struct cgit_filter *source_filter;
85}; 87};
86 88
87typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 89typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
88 const char *value); 90 const char *value);
89 91
90struct cgit_repolist { 92struct cgit_repolist {
91 int length; 93 int length;
92 int count; 94 int count;
93 struct cgit_repo *repos; 95 struct cgit_repo *repos;
94}; 96};
95 97
96struct commitinfo { 98struct commitinfo {
97 struct commit *commit; 99 struct commit *commit;
98 char *author; 100 char *author;
99 char *author_email; 101 char *author_email;
100 unsigned long author_date; 102 unsigned long author_date;
101 char *committer; 103 char *committer;
102 char *committer_email; 104 char *committer_email;
103 unsigned long committer_date; 105 unsigned long committer_date;
104 char *subject; 106 char *subject;
105 char *msg; 107 char *msg;
106 char *msg_encoding; 108 char *msg_encoding;
107}; 109};
108 110
109struct taginfo { 111struct taginfo {
110 char *tagger; 112 char *tagger;
111 char *tagger_email; 113 char *tagger_email;
112 unsigned long tagger_date; 114 unsigned long tagger_date;
113 char *msg; 115 char *msg;
114}; 116};
115 117
116struct refinfo { 118struct refinfo {
117 const char *refname; 119 const char *refname;
118 struct object *object; 120 struct object *object;
119 union { 121 union {
120 struct taginfo *tag; 122 struct taginfo *tag;
121 struct commitinfo *commit; 123 struct commitinfo *commit;
122 }; 124 };
123}; 125};
@@ -145,115 +147,117 @@ struct cgit_query {
145 char *url; 147 char *url;
146 char *period; 148 char *period;
147 int ofs; 149 int ofs;
148 int nohead; 150 int nohead;
149 char *sort; 151 char *sort;
150 int showmsg; 152 int showmsg;
151 int ssdiff; 153 int ssdiff;
152 int show_all; 154 int show_all;
153 int context; 155 int context;
154 int ignorews; 156 int ignorews;
155 char *vpath; 157 char *vpath;
156}; 158};
157 159
158struct cgit_config { 160struct cgit_config {
159 char *agefile; 161 char *agefile;
160 char *cache_root; 162 char *cache_root;
161 char *clone_prefix; 163 char *clone_prefix;
162 char *css; 164 char *css;
163 char *favicon; 165 char *favicon;
164 char *footer; 166 char *footer;
165 char *head_include; 167 char *head_include;
166 char *header; 168 char *header;
167 char *index_header; 169 char *index_header;
168 char *index_info; 170 char *index_info;
169 char *logo; 171 char *logo;
170 char *logo_link; 172 char *logo_link;
171 char *module_link; 173 char *module_link;
172 char *project_list; 174 char *project_list;
173 char *readme; 175 char *readme;
174 char *robots; 176 char *robots;
175 char *root_title; 177 char *root_title;
176 char *root_desc; 178 char *root_desc;
177 char *root_readme; 179 char *root_readme;
178 char *script_name; 180 char *script_name;
179 char *section; 181 char *section;
180 char *virtual_root; 182 char *virtual_root;
181 char *strict_export; 183 char *strict_export;
182 int cache_size; 184 int cache_size;
183 int cache_dynamic_ttl; 185 int cache_dynamic_ttl;
184 int cache_max_create_time; 186 int cache_max_create_time;
185 int cache_repo_ttl; 187 int cache_repo_ttl;
186 int cache_root_ttl; 188 int cache_root_ttl;
187 int cache_scanrc_ttl; 189 int cache_scanrc_ttl;
188 int cache_static_ttl; 190 int cache_static_ttl;
189 int embedded; 191 int embedded;
190 int enable_filter_overrides; 192 int enable_filter_overrides;
191 int enable_gitweb_owner; 193 int enable_gitweb_owner;
192 int enable_index_links; 194 int enable_index_links;
195 int enable_commit_graph;
193 int enable_log_filecount; 196 int enable_log_filecount;
194 int enable_log_linecount; 197 int enable_log_linecount;
195 int enable_remote_branches; 198 int enable_remote_branches;
196 int enable_subject_links; 199 int enable_subject_links;
197 int enable_tree_linenumbers; 200 int enable_tree_linenumbers;
198 int local_time; 201 int local_time;
199 int max_atom_items; 202 int max_atom_items;
200 int max_repo_count; 203 int max_repo_count;
201 int max_commit_count; 204 int max_commit_count;
202 int max_lock_attempts; 205 int max_lock_attempts;
203 int max_msg_len; 206 int max_msg_len;
204 int max_repodesc_len; 207 int max_repodesc_len;
205 int max_blob_size; 208 int max_blob_size;
206 int max_stats; 209 int max_stats;
207 int nocache; 210 int nocache;
208 int noplainemail; 211 int noplainemail;
209 int noheader; 212 int noheader;
210 int renamelimit; 213 int renamelimit;
211 int remove_suffix; 214 int remove_suffix;
215 int scan_hidden_path;
212 int section_from_path; 216 int section_from_path;
213 int snapshots; 217 int snapshots;
214 int summary_branches; 218 int summary_branches;
215 int summary_log; 219 int summary_log;
216 int summary_tags; 220 int summary_tags;
217 int ssdiff; 221 int ssdiff;
218 struct string_list mimetypes; 222 struct string_list mimetypes;
219 struct cgit_filter *about_filter; 223 struct cgit_filter *about_filter;
220 struct cgit_filter *commit_filter; 224 struct cgit_filter *commit_filter;
221 struct cgit_filter *source_filter; 225 struct cgit_filter *source_filter;
222}; 226};
223 227
224struct cgit_page { 228struct cgit_page {
225 time_t modified; 229 time_t modified;
226 time_t expires; 230 time_t expires;
227 size_t size; 231 size_t size;
228 char *mimetype; 232 char *mimetype;
229 char *charset; 233 char *charset;
230 char *filename; 234 char *filename;
231 char *etag; 235 char *etag;
232 char *title; 236 char *title;
233 int status; 237 int status;
234 char *statusmsg; 238 char *statusmsg;
235}; 239};
236 240
237struct cgit_environment { 241struct cgit_environment {
238 char *cgit_config; 242 char *cgit_config;
239 char *http_host; 243 char *http_host;
240 char *https; 244 char *https;
241 char *no_http; 245 char *no_http;
242 char *path_info; 246 char *path_info;
243 char *query_string; 247 char *query_string;
244 char *request_method; 248 char *request_method;
245 char *script_name; 249 char *script_name;
246 char *server_name; 250 char *server_name;
247 char *server_port; 251 char *server_port;
248}; 252};
249 253
250struct cgit_context { 254struct cgit_context {
251 struct cgit_environment env; 255 struct cgit_environment env;
252 struct cgit_query qry; 256 struct cgit_query qry;
253 struct cgit_config cfg; 257 struct cgit_config cfg;
254 struct cgit_repo *repo; 258 struct cgit_repo *repo;
255 struct cgit_page page; 259 struct cgit_page page;
256}; 260};
257 261
258struct cgit_snapshot_format { 262struct cgit_snapshot_format {
259 const char *suffix; 263 const char *suffix;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 01157a9..c3698a6 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -45,97 +45,102 @@ cache-root::
45 "/var/cache/cgit". 45 "/var/cache/cgit".
46 46
47cache-dynamic-ttl:: 47cache-dynamic-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed without a fixed SHA1. Default 49 version of repository pages accessed without a fixed SHA1. Default
50 value: "5". 50 value: "5".
51 51
52cache-repo-ttl:: 52cache-repo-ttl::
53 Number which specifies the time-to-live, in minutes, for the cached 53 Number which specifies the time-to-live, in minutes, for the cached
54 version of the repository summary page. Default value: "5". 54 version of the repository summary page. Default value: "5".
55 55
56cache-root-ttl:: 56cache-root-ttl::
57 Number which specifies the time-to-live, in minutes, for the cached 57 Number which specifies the time-to-live, in minutes, for the cached
58 version of the repository index page. Default value: "5". 58 version of the repository index page. Default value: "5".
59 59
60cache-scanrc-ttl:: 60cache-scanrc-ttl::
61 Number which specifies the time-to-live, in minutes, for the result 61 Number which specifies the time-to-live, in minutes, for the result
62 of scanning a path for git repositories. Default value: "15". 62 of scanning a path for git repositories. Default value: "15".
63 63
64cache-size:: 64cache-size::
65 The maximum number of entries in the cgit cache. Default value: "0" 65 The maximum number of entries in the cgit cache. Default value: "0"
66 (i.e. caching is disabled). 66 (i.e. caching is disabled).
67 67
68cache-static-ttl:: 68cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-commit-graph::
95 Flag which, when set to "1", will make cgit print an ASCII-art commit
96 history graph to the left of the commit messages in the repository
97 log page. Default value: "0".
98
94enable-filter-overrides:: 99enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 100 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 101 overridden in repository-specific cgitrc files. Default value: none.
97 102
98enable-gitweb-owner:: 103enable-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository 104 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner. 105 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path. 106 Default value: "1". See also: scan-path.
102 107
103enable-index-links:: 108enable-index-links::
104 Flag which, when set to "1", will make cgit generate extra links for 109 Flag which, when set to "1", will make cgit generate extra links for
105 each repo in the repository index (specifically, to the "summary", 110 each repo in the repository index (specifically, to the "summary",
106 "commit" and "tree" pages). Default value: "0". 111 "commit" and "tree" pages). Default value: "0".
107 112
108enable-log-filecount:: 113enable-log-filecount::
109 Flag which, when set to "1", will make cgit print the number of 114 Flag which, when set to "1", will make cgit print the number of
110 modified files for each commit on the repository log page. Default 115 modified files for each commit on the repository log page. Default
111 value: "0". 116 value: "0".
112 117
113enable-log-linecount:: 118enable-log-linecount::
114 Flag which, when set to "1", will make cgit print the number of added 119 Flag which, when set to "1", will make cgit print the number of added
115 and removed lines for each commit on the repository log page. Default 120 and removed lines for each commit on the repository log page. Default
116 value: "0". 121 value: "0".
117 122
118enable-remote-branches:: 123enable-remote-branches::
119 Flag which, when set to "1", will make cgit display remote branches 124 Flag which, when set to "1", will make cgit display remote branches
120 in the summary and refs views. Default value: "0". See also: 125 in the summary and refs views. Default value: "0". See also:
121 "repo.enable-remote-branches". 126 "repo.enable-remote-branches".
122 127
123enable-subject-links:: 128enable-subject-links::
124 Flag which, when set to "1", will make cgit use the subject of the 129 Flag which, when set to "1", will make cgit use the subject of the
125 parent commit as link text when generating links to parent commits 130 parent commit as link text when generating links to parent commits
126 in commit view. Default value: "0". See also: 131 in commit view. Default value: "0". See also:
127 "repo.enable-subject-links". 132 "repo.enable-subject-links".
128 133
129enable-tree-linenumbers:: 134enable-tree-linenumbers::
130 Flag which, when set to "1", will make cgit generate linenumber links 135 Flag which, when set to "1", will make cgit generate linenumber links
131 for plaintext blobs printed in the tree view. Default value: "1". 136 for plaintext blobs printed in the tree view. Default value: "1".
132 137
133favicon:: 138favicon::
134 Url used as link to a shortcut icon for cgit. If specified, it is 139 Url used as link to a shortcut icon for cgit. If specified, it is
135 suggested to use the value "/favicon.ico" since certain browsers will 140 suggested to use the value "/favicon.ico" since certain browsers will
136 ignore other values. Default value: none. 141 ignore other values. Default value: none.
137 142
138footer:: 143footer::
139 The content of the file specified with this option will be included 144 The content of the file specified with this option will be included
140 verbatim at the bottom of all pages (i.e. it replaces the standard 145 verbatim at the bottom of all pages (i.e. it replaces the standard
141 "generated by..." message. Default value: none. 146 "generated by..." message. Default value: none.
@@ -224,277 +229,293 @@ nocache::
224noplainemail:: 229noplainemail::
225 If set to "1" showing full author email adresses will be disabled. 230 If set to "1" showing full author email adresses will be disabled.
226 Default value: "0". 231 Default value: "0".
227 232
228noheader:: 233noheader::
229 Flag which, when set to "1", will make cgit omit the standard header 234 Flag which, when set to "1", will make cgit omit the standard header
230 on all pages. Default value: none. See also: "embedded". 235 on all pages. Default value: none. See also: "embedded".
231 236
232project-list:: 237project-list::
233 A list of subdirectories inside of scan-path, relative to it, that 238 A list of subdirectories inside of scan-path, relative to it, that
234 should loaded as git repositories. This must be defined prior to 239 should loaded as git repositories. This must be defined prior to
235 scan-path. Default value: none. See also: scan-path. 240 scan-path. Default value: none. See also: scan-path.
236 241
237readme:: 242readme::
238 Text which will be used as default value for "repo.readme". Default 243 Text which will be used as default value for "repo.readme". Default
239 value: none. 244 value: none.
240 245
241remove-suffix:: 246remove-suffix::
242 If set to "1" and scan-path is enabled, if any repositories are found 247 If set to "1" and scan-path is enabled, if any repositories are found
243 with a suffix of ".git", this suffix will be removed for the url and 248 with a suffix of ".git", this suffix will be removed for the url and
244 name. Default value: "0". See also: scan-path. 249 name. Default value: "0". See also: scan-path.
245 250
246renamelimit:: 251renamelimit::
247 Maximum number of files to consider when detecting renames. The value 252 Maximum number of files to consider when detecting renames. The value
248 "-1" uses the compiletime value in git (for further info, look at 253 "-1" uses the compiletime value in git (for further info, look at
249 `man git-diff`). Default value: "-1". 254 `man git-diff`). Default value: "-1".
250 255
251repo.group:: 256repo.group::
252 Legacy alias for "section". This option is deprecated and will not be 257 Legacy alias for "section". This option is deprecated and will not be
253 supported in cgit-1.0. 258 supported in cgit-1.0.
254 259
255robots:: 260robots::
256 Text used as content for the "robots" meta-tag. Default value: 261 Text used as content for the "robots" meta-tag. Default value:
257 "index, nofollow". 262 "index, nofollow".
258 263
259root-desc:: 264root-desc::
260 Text printed below the heading on the repository index page. Default 265 Text printed below the heading on the repository index page. Default
261 value: "a fast webinterface for the git dscm". 266 value: "a fast webinterface for the git dscm".
262 267
263root-readme:: 268root-readme::
264 The content of the file specified with this option will be included 269 The content of the file specified with this option will be included
265 verbatim below the "about" link on the repository index page. Default 270 verbatim below the "about" link on the repository index page. Default
266 value: none. 271 value: none.
267 272
268root-title:: 273root-title::
269 Text printed as heading on the repository index page. Default value: 274 Text printed as heading on the repository index page. Default value:
270 "Git Repository Browser". 275 "Git Repository Browser".
271 276
277scan-hidden-path::
278 If set to "1" and scan-path is enabled, scan-path will recurse into
279 directories whose name starts with a period ('.'). Otherwise,
280 scan-path will stay away from such directories (considered as
281 "hidden"). Note that this does not apply to the ".git" directory in
282 non-bare repos. This must be defined prior to scan-path.
283 Default value: 0. See also: scan-path.
284
272scan-path:: 285scan-path::
273 A path which will be scanned for repositories. If caching is enabled, 286 A path which will be scanned for repositories. If caching is enabled,
274 the result will be cached as a cgitrc include-file in the cache 287 the result will be cached as a cgitrc include-file in the cache
275 directory. If project-list has been defined prior to scan-path, 288 directory. If project-list has been defined prior to scan-path,
276 scan-path loads only the directories listed in the file pointed to by 289 scan-path loads only the directories listed in the file pointed to by
277 project-list. Default value: none. See also: cache-scanrc-ttl, 290 project-list. Default value: none. See also: cache-scanrc-ttl,
278 project-list. 291 project-list.
279 292
280section:: 293section::
281 The name of the current repository section - all repositories defined 294 The name of the current repository section - all repositories defined
282 after this option will inherit the current section name. Default value: 295 after this option will inherit the current section name. Default value:
283 none. 296 none.
284 297
285section-from-path:: 298section-from-path::
286 A number which, if specified before scan-path, specifies how many 299 A number which, if specified before scan-path, specifies how many
287 path elements from each repo path to use as a default section name. 300 path elements from each repo path to use as a default section name.
288 If negative, cgit will discard the specified number of path elements 301 If negative, cgit will discard the specified number of path elements
289 above the repo directory. Default value: 0. 302 above the repo directory. Default value: 0.
290 303
291side-by-side-diffs:: 304side-by-side-diffs::
292 If set to "1" shows side-by-side diffs instead of unidiffs per 305 If set to "1" shows side-by-side diffs instead of unidiffs per
293 default. Default value: "0". 306 default. Default value: "0".
294 307
295snapshots:: 308snapshots::
296 Text which specifies the default set of snapshot formats generated by 309 Text which specifies the default set of snapshot formats generated by
297 cgit. The value is a space-separated list of zero or more of the 310 cgit. The value is a space-separated list of zero or more of the
298 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 311 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
299 312
300source-filter:: 313source-filter::
301 Specifies a command which will be invoked to format plaintext blobs 314 Specifies a command which will be invoked to format plaintext blobs
302 in the tree view. The command will get the blob content on its STDIN 315 in the tree view. The command will get the blob content on its STDIN
303 and the name of the blob as its only command line argument. The STDOUT 316 and the name of the blob as its only command line argument. The STDOUT
304 from the command will be included verbatim as the blob contents, i.e. 317 from the command will be included verbatim as the blob contents, i.e.
305 this can be used to implement e.g. syntax highlighting. Default value: 318 this can be used to implement e.g. syntax highlighting. Default value:
306 none. 319 none.
307 320
308summary-branches:: 321summary-branches::
309 Specifies the number of branches to display in the repository "summary" 322 Specifies the number of branches to display in the repository "summary"
310 view. Default value: "10". 323 view. Default value: "10".
311 324
312summary-log:: 325summary-log::
313 Specifies the number of log entries to display in the repository 326 Specifies the number of log entries to display in the repository
314 "summary" view. Default value: "10". 327 "summary" view. Default value: "10".
315 328
316summary-tags:: 329summary-tags::
317 Specifies the number of tags to display in the repository "summary" 330 Specifies the number of tags to display in the repository "summary"
318 view. Default value: "10". 331 view. Default value: "10".
319 332
320strict-export:: 333strict-export::
321 Filename which, if specified, needs to be present within the repository 334 Filename which, if specified, needs to be present within the repository
322 for cgit to allow access to that repository. This can be used to emulate 335 for cgit to allow access to that repository. This can be used to emulate
323 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's 336 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's
324 repositories to match those exported by git-daemon. This option MUST come 337 repositories to match those exported by git-daemon. This option MUST come
325 before 'scan-path'. 338 before 'scan-path'.
326 339
327virtual-root:: 340virtual-root::
328 Url which, if specified, will be used as root for all cgit links. It 341 Url which, if specified, will be used as root for all cgit links. It
329 will also cause cgit to generate 'virtual urls', i.e. urls like 342 will also cause cgit to generate 'virtual urls', i.e. urls like
330 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 343 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
331 value: none. 344 value: none.
332 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 345 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
333 same kind of virtual urls, so this option will probably be deprecated. 346 same kind of virtual urls, so this option will probably be deprecated.
334 347
335REPOSITORY SETTINGS 348REPOSITORY SETTINGS
336------------------- 349-------------------
337repo.about-filter:: 350repo.about-filter::
338 Override the default about-filter. Default value: none. See also: 351 Override the default about-filter. Default value: none. See also:
339 "enable-filter-overrides". 352 "enable-filter-overrides".
340 353
341repo.clone-url:: 354repo.clone-url::
342 A list of space-separated urls which can be used to clone this repo. 355 A list of space-separated urls which can be used to clone this repo.
343 Default value: none. 356 Default value: none.
344 357
345repo.commit-filter:: 358repo.commit-filter::
346 Override the default commit-filter. Default value: none. See also: 359 Override the default commit-filter. Default value: none. See also:
347 "enable-filter-overrides". 360 "enable-filter-overrides".
348 361
349repo.defbranch:: 362repo.defbranch::
350 The name of the default branch for this repository. If no such branch 363 The name of the default branch for this repository. If no such branch
351 exists in the repository, the first branch name (when sorted) is used 364 exists in the repository, the first branch name (when sorted) is used
352 as default instead. Default value: "master". 365 as default instead. Default value: "master".
353 366
354repo.desc:: 367repo.desc::
355 The value to show as repository description. Default value: none. 368 The value to show as repository description. Default value: none.
356 369
370repo.enable-commit-graph::
371 A flag which can be used to disable the global setting
372 `enable-commit-graph'. Default value: none.
373
357repo.enable-log-filecount:: 374repo.enable-log-filecount::
358 A flag which can be used to disable the global setting 375 A flag which can be used to disable the global setting
359 `enable-log-filecount'. Default value: none. 376 `enable-log-filecount'. Default value: none.
360 377
361repo.enable-log-linecount:: 378repo.enable-log-linecount::
362 A flag which can be used to disable the global setting 379 A flag which can be used to disable the global setting
363 `enable-log-linecount'. Default value: none. 380 `enable-log-linecount'. Default value: none.
364 381
365repo.enable-remote-branches:: 382repo.enable-remote-branches::
366 Flag which, when set to "1", will make cgit display remote branches 383 Flag which, when set to "1", will make cgit display remote branches
367 in the summary and refs views. Default value: <enable-remote-branches>. 384 in the summary and refs views. Default value: <enable-remote-branches>.
368 385
369repo.enable-subject-links:: 386repo.enable-subject-links::
370 A flag which can be used to override the global setting 387 A flag which can be used to override the global setting
371 `enable-subject-links'. Default value: none. 388 `enable-subject-links'. Default value: none.
372 389
373repo.logo:: 390repo.logo::
374 Url which specifies the source of an image which will be used as a logo 391 Url which specifies the source of an image which will be used as a logo
375 on this repo's pages. Default value: global logo. 392 on this repo's pages. Default value: global logo.
376 393
377repo.logo-link:: 394repo.logo-link::
378 Url loaded when clicking on the cgit logo image. If unspecified the 395 Url loaded when clicking on the cgit logo image. If unspecified the
379 calculated url of the repository index page will be used. Default 396 calculated url of the repository index page will be used. Default
380 value: global logo-link. 397 value: global logo-link.
381 398
382repo.max-stats:: 399repo.max-stats::
383 Override the default maximum statistics period. Valid values are equal 400 Override the default maximum statistics period. Valid values are equal
384 to the values specified for the global "max-stats" setting. Default 401 to the values specified for the global "max-stats" setting. Default
385 value: none. 402 value: none.
386 403
387repo.name:: 404repo.name::
388 The value to show as repository name. Default value: <repo.url>. 405 The value to show as repository name. Default value: <repo.url>.
389 406
390repo.owner:: 407repo.owner::
391 A value used to identify the owner of the repository. Default value: 408 A value used to identify the owner of the repository. Default value:
392 none. 409 none.
393 410
394repo.path:: 411repo.path::
395 An absolute path to the repository directory. For non-bare repositories 412 An absolute path to the repository directory. For non-bare repositories
396 this is the .git-directory. Default value: none. 413 this is the .git-directory. Default value: none.
397 414
398repo.readme:: 415repo.readme::
399 A path (relative to <repo.path>) which specifies a file to include 416 A path (relative to <repo.path>) which specifies a file to include
400 verbatim as the "About" page for this repo. You may also specify a 417 verbatim as the "About" page for this repo. You may also specify a
401 git refspec by head or by hash by prepending the refspec followed by 418 git refspec by head or by hash by prepending the refspec followed by
402 a colon. For example, "master:docs/readme.mkd" Default value: <readme>. 419 a colon. For example, "master:docs/readme.mkd" Default value: <readme>.
403 420
404repo.snapshots:: 421repo.snapshots::
405 A mask of allowed snapshot-formats for this repo, restricted by the 422 A mask of allowed snapshot-formats for this repo, restricted by the
406 "snapshots" global setting. Default value: <snapshots>. 423 "snapshots" global setting. Default value: <snapshots>.
407 424
408repo.section:: 425repo.section::
409 Override the current section name for this repository. Default value: 426 Override the current section name for this repository. Default value:
410 none. 427 none.
411 428
412repo.source-filter:: 429repo.source-filter::
413 Override the default source-filter. Default value: none. See also: 430 Override the default source-filter. Default value: none. See also:
414 "enable-filter-overrides". 431 "enable-filter-overrides".
415 432
416repo.url:: 433repo.url::
417 The relative url used to access the repository. This must be the first 434 The relative url used to access the repository. This must be the first
418 setting specified for each repo. Default value: none. 435 setting specified for each repo. Default value: none.
419 436
420 437
421REPOSITORY-SPECIFIC CGITRC FILE 438REPOSITORY-SPECIFIC CGITRC FILE
422------------------------------- 439-------------------------------
423When the option "scan-path" is used to auto-discover git repositories, cgit 440When the option "scan-path" is used to auto-discover git repositories, cgit
424will try to parse the file "cgitrc" within any found repository. Such a 441will try to parse the file "cgitrc" within any found repository. Such a
425repo-specific config file may contain any of the repo-specific options 442repo-specific config file may contain any of the repo-specific options
426described above, except "repo.url" and "repo.path". Additionally, the "filter" 443described above, except "repo.url" and "repo.path". Additionally, the "filter"
427options are only acknowledged in repo-specific config files when 444options are only acknowledged in repo-specific config files when
428"enable-filter-overrides" is set to "1". 445"enable-filter-overrides" is set to "1".
429 446
430Note: the "repo." prefix is dropped from the option names in repo-specific 447Note: the "repo." prefix is dropped from the option names in repo-specific
431config files, e.g. "repo.desc" becomes "desc". 448config files, e.g. "repo.desc" becomes "desc".
432 449
433 450
434EXAMPLE CGITRC FILE 451EXAMPLE CGITRC FILE
435------------------- 452-------------------
436 453
437.... 454....
438# Enable caching of up to 1000 output entriess 455# Enable caching of up to 1000 output entriess
439cache-size=1000 456cache-size=1000
440 457
441 458
442# Specify some default clone prefixes 459# Specify some default clone prefixes
443clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git 460clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git
444 461
445# Specify the css url 462# Specify the css url
446css=/css/cgit.css 463css=/css/cgit.css
447 464
448 465
449# Show extra links for each repository on the index page 466# Show extra links for each repository on the index page
450enable-index-links=1 467enable-index-links=1
451 468
452 469
470# Enable ASCII art commit history graph on the log pages
471enable-commit-graph=1
472
473
453# Show number of affected files per commit on the log pages 474# Show number of affected files per commit on the log pages
454enable-log-filecount=1 475enable-log-filecount=1
455 476
456 477
457# Show number of added/removed lines per commit on the log pages 478# Show number of added/removed lines per commit on the log pages
458enable-log-linecount=1 479enable-log-linecount=1
459 480
460 481
461# Add a cgit favicon 482# Add a cgit favicon
462favicon=/favicon.ico 483favicon=/favicon.ico
463 484
464 485
465# Use a custom logo 486# Use a custom logo
466logo=/img/mylogo.png 487logo=/img/mylogo.png
467 488
468 489
469# Enable statistics per week, month and quarter 490# Enable statistics per week, month and quarter
470max-stats=quarter 491max-stats=quarter
471 492
472 493
473# Set the title and heading of the repository index page 494# Set the title and heading of the repository index page
474root-title=example.com git repositories 495root-title=example.com git repositories
475 496
476 497
477# Set a subheading for the repository index page 498# Set a subheading for the repository index page
478root-desc=tracking the foobar development 499root-desc=tracking the foobar development
479 500
480 501
481# Include some more info about example.com on the index page 502# Include some more info about example.com on the index page
482root-readme=/var/www/htdocs/about.html 503root-readme=/var/www/htdocs/about.html
483 504
484 505
485# Allow download of tar.gz, tar.bz2 and zip-files 506# Allow download of tar.gz, tar.bz2 and zip-files
486snapshots=tar.gz tar.bz2 zip 507snapshots=tar.gz tar.bz2 zip
487 508
488 509
489## 510##
490## List of common mimetypes 511## List of common mimetypes
491## 512##
492 513
493mimetype.gif=image/gif 514mimetype.gif=image/gif
494mimetype.html=text/html 515mimetype.html=text/html
495mimetype.jpg=image/jpeg 516mimetype.jpg=image/jpeg
496mimetype.jpeg=image/jpeg 517mimetype.jpeg=image/jpeg
497mimetype.pdf=application/pdf 518mimetype.pdf=application/pdf
498mimetype.png=image/png 519mimetype.png=image/png
499mimetype.svg=image/svg+xml 520mimetype.svg=image/svg+xml
500 521
diff --git a/cmd.c b/cmd.c
index 6dc9f5e..536515b 100644
--- a/cmd.c
+++ b/cmd.c
@@ -22,97 +22,98 @@
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
40{ 40{
41 if (ctx->repo) 41 if (ctx->repo)
42 cgit_print_repo_readme(ctx->qry.path); 42 cgit_print_repo_readme(ctx->qry.path);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
67static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
68{ 68{
69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1,
71 ctx->repo->enable_commit_graph);
71} 72}
72 73
73static void ls_cache_fn(struct cgit_context *ctx) 74static void ls_cache_fn(struct cgit_context *ctx)
74{ 75{
75 ctx->page.mimetype = "text/plain"; 76 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 77 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 78 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 79 cache_ls(ctx->cfg.cache_root);
79} 80}
80 81
81static void objects_fn(struct cgit_context *ctx) 82static void objects_fn(struct cgit_context *ctx)
82{ 83{
83 cgit_clone_objects(ctx); 84 cgit_clone_objects(ctx);
84} 85}
85 86
86static void repolist_fn(struct cgit_context *ctx) 87static void repolist_fn(struct cgit_context *ctx)
87{ 88{
88 cgit_print_repolist(); 89 cgit_print_repolist();
89} 90}
90 91
91static void patch_fn(struct cgit_context *ctx) 92static void patch_fn(struct cgit_context *ctx)
92{ 93{
93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path); 94 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 95}
95 96
96static void plain_fn(struct cgit_context *ctx) 97static void plain_fn(struct cgit_context *ctx)
97{ 98{
98 cgit_print_plain(ctx); 99 cgit_print_plain(ctx);
99} 100}
100 101
101static void refs_fn(struct cgit_context *ctx) 102static void refs_fn(struct cgit_context *ctx)
102{ 103{
103 cgit_print_refs(); 104 cgit_print_refs();
104} 105}
105 106
106static void snapshot_fn(struct cgit_context *ctx) 107static void snapshot_fn(struct cgit_context *ctx)
107{ 108{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 109 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 110 ctx->repo->snapshots, ctx->qry.nohead);
110} 111}
111 112
112static void stats_fn(struct cgit_context *ctx) 113static void stats_fn(struct cgit_context *ctx)
113{ 114{
114 cgit_show_stats(ctx); 115 cgit_show_stats(ctx);
115} 116}
116 117
117static void summary_fn(struct cgit_context *ctx) 118static void summary_fn(struct cgit_context *ctx)
118{ 119{
diff --git a/git b/git
Subproject 87b50542a08ac6caa083ddc376e674424e37940 Subproject 7ed863a85a6ce2c4ac4476848310b8f917ab41f
diff --git a/html.c b/html.c
index 1305910..a1fe87d 100644
--- a/html.c
+++ b/html.c
@@ -1,69 +1,69 @@
1/* html.c: helper functions for html output 1/* html.c: helper functions for html output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <unistd.h> 9#include <unistd.h>
10#include <stdio.h> 10#include <stdio.h>
11#include <stdlib.h> 11#include <stdlib.h>
12#include <stdarg.h> 12#include <stdarg.h>
13#include <string.h> 13#include <string.h>
14#include <errno.h> 14#include <errno.h>
15 15
16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */ 16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
17static const char* url_escape_table[256] = { 17static const char* url_escape_table[256] = {
18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", 18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13", 19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13",
20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d", 20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d",
21 "%1e", "%1f", "%20", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0, 21 "%1e", "%1f", "+", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0,
22 "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d", 22 "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d",
23 "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0, 24 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0,
25 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b", 25 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b",
26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85", 26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85",
27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", 27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", 28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99",
29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3", 29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3",
30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", 30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad",
31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7", 31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1", 32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1",
33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb", 33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb",
34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", 34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5",
35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df", 35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9", 36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9",
37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3", 37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3",
38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", 38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd",
39 "%fe", "%ff" 39 "%fe", "%ff"
40}; 40};
41 41
42int htmlfd = STDOUT_FILENO; 42int htmlfd = STDOUT_FILENO;
43 43
44char *fmt(const char *format, ...) 44char *fmt(const char *format, ...)
45{ 45{
46 static char buf[8][1024]; 46 static char buf[8][1024];
47 static int bufidx; 47 static int bufidx;
48 int len; 48 int len;
49 va_list args; 49 va_list args;
50 50
51 bufidx++; 51 bufidx++;
52 bufidx &= 7; 52 bufidx &= 7;
53 53
54 va_start(args, format); 54 va_start(args, format);
55 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); 55 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
56 va_end(args); 56 va_end(args);
57 if (len>sizeof(buf[bufidx])) { 57 if (len>sizeof(buf[bufidx])) {
58 fprintf(stderr, "[html.c] string truncated: %s\n", format); 58 fprintf(stderr, "[html.c] string truncated: %s\n", format);
59 exit(1); 59 exit(1);
60 } 60 }
61 return buf[bufidx]; 61 return buf[bufidx];
62} 62}
63 63
64void html_raw(const char *data, size_t size) 64void html_raw(const char *data, size_t size)
65{ 65{
66 write(htmlfd, data, size); 66 write(htmlfd, data, size);
67} 67}
68 68
69void html(const char *txt) 69void html(const char *txt)
@@ -136,97 +136,97 @@ void html_ntxt(int len, const char *txt)
136void html_attr(const char *txt) 136void html_attr(const char *txt)
137{ 137{
138 const char *t = txt; 138 const char *t = txt;
139 while(t && *t){ 139 while(t && *t){
140 int c = *t; 140 int c = *t;
141 if (c=='<' || c=='>' || c=='\'' || c=='\"') { 141 if (c=='<' || c=='>' || c=='\'' || c=='\"') {
142 html_raw(txt, t - txt); 142 html_raw(txt, t - txt);
143 if (c=='>') 143 if (c=='>')
144 html("&gt;"); 144 html("&gt;");
145 else if (c=='<') 145 else if (c=='<')
146 html("&lt;"); 146 html("&lt;");
147 else if (c=='\'') 147 else if (c=='\'')
148 html("&#x27;"); 148 html("&#x27;");
149 else if (c=='"') 149 else if (c=='"')
150 html("&quot;"); 150 html("&quot;");
151 txt = t+1; 151 txt = t+1;
152 } 152 }
153 t++; 153 t++;
154 } 154 }
155 if (t!=txt) 155 if (t!=txt)
156 html(txt); 156 html(txt);
157} 157}
158 158
159void html_url_path(const char *txt) 159void html_url_path(const char *txt)
160{ 160{
161 const char *t = txt; 161 const char *t = txt;
162 while(t && *t){ 162 while(t && *t){
163 int c = *t; 163 int c = *t;
164 const char *e = url_escape_table[c]; 164 const char *e = url_escape_table[c];
165 if (e && c!='+' && c!='&' && c!='+') { 165 if (e && c!='+' && c!='&' && c!='+') {
166 html_raw(txt, t - txt); 166 html_raw(txt, t - txt);
167 html_raw(e, 3); 167 html_raw(e, 3);
168 txt = t+1; 168 txt = t+1;
169 } 169 }
170 t++; 170 t++;
171 } 171 }
172 if (t!=txt) 172 if (t!=txt)
173 html(txt); 173 html(txt);
174} 174}
175 175
176void html_url_arg(const char *txt) 176void html_url_arg(const char *txt)
177{ 177{
178 const char *t = txt; 178 const char *t = txt;
179 while(t && *t){ 179 while(t && *t){
180 int c = *t; 180 int c = *t;
181 const char *e = url_escape_table[c]; 181 const char *e = url_escape_table[c];
182 if (e) { 182 if (e) {
183 html_raw(txt, t - txt); 183 html_raw(txt, t - txt);
184 html_raw(e, 3); 184 html_raw(e, strlen(e));
185 txt = t+1; 185 txt = t+1;
186 } 186 }
187 t++; 187 t++;
188 } 188 }
189 if (t!=txt) 189 if (t!=txt)
190 html(txt); 190 html(txt);
191} 191}
192 192
193void html_hidden(const char *name, const char *value) 193void html_hidden(const char *name, const char *value)
194{ 194{
195 html("<input type='hidden' name='"); 195 html("<input type='hidden' name='");
196 html_attr(name); 196 html_attr(name);
197 html("' value='"); 197 html("' value='");
198 html_attr(value); 198 html_attr(value);
199 html("'/>"); 199 html("'/>");
200} 200}
201 201
202void html_option(const char *value, const char *text, const char *selected_value) 202void html_option(const char *value, const char *text, const char *selected_value)
203{ 203{
204 html("<option value='"); 204 html("<option value='");
205 html_attr(value); 205 html_attr(value);
206 html("'"); 206 html("'");
207 if (selected_value && !strcmp(selected_value, value)) 207 if (selected_value && !strcmp(selected_value, value))
208 html(" selected='selected'"); 208 html(" selected='selected'");
209 html(">"); 209 html(">");
210 html_txt(text); 210 html_txt(text);
211 html("</option>\n"); 211 html("</option>\n");
212} 212}
213 213
214void html_link_open(const char *url, const char *title, const char *class) 214void html_link_open(const char *url, const char *title, const char *class)
215{ 215{
216 html("<a href='"); 216 html("<a href='");
217 html_attr(url); 217 html_attr(url);
218 if (title) { 218 if (title) {
219 html("' title='"); 219 html("' title='");
220 html_attr(title); 220 html_attr(title);
221 } 221 }
222 if (class) { 222 if (class) {
223 html("' class='"); 223 html("' class='");
224 html_attr(class); 224 html_attr(class);
225 } 225 }
226 html("'>"); 226 html("'>");
227} 227}
228 228
229void html_link_close(void) 229void html_link_close(void)
230{ 230{
231 html("</a>"); 231 html("</a>");
232} 232}
diff --git a/scan-tree.c b/scan-tree.c
index a0e09ce..627af1b 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -114,126 +114,128 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
114 if (pwd->pw_gecos) 114 if (pwd->pw_gecos)
115 if ((p = strchr(pwd->pw_gecos, ','))) 115 if ((p = strchr(pwd->pw_gecos, ',')))
116 *p = '\0'; 116 *p = '\0';
117 owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name); 117 owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
118 } 118 }
119 repo->owner = owner; 119 repo->owner = owner;
120 120
121 p = fmt("%s/description", path); 121 p = fmt("%s/description", path);
122 if (!stat(p, &st)) 122 if (!stat(p, &st))
123 readfile(p, &repo->desc, &size); 123 readfile(p, &repo->desc, &size);
124 124
125 if (!repo->readme) { 125 if (!repo->readme) {
126 p = fmt("%s/README.html", path); 126 p = fmt("%s/README.html", path);
127 if (!stat(p, &st)) 127 if (!stat(p, &st))
128 repo->readme = "README.html"; 128 repo->readme = "README.html";
129 } 129 }
130 if (ctx.cfg.section_from_path) { 130 if (ctx.cfg.section_from_path) {
131 n = ctx.cfg.section_from_path; 131 n = ctx.cfg.section_from_path;
132 if (n > 0) { 132 if (n > 0) {
133 slash = rel; 133 slash = rel;
134 while (slash && n && (slash = strchr(slash, '/'))) 134 while (slash && n && (slash = strchr(slash, '/')))
135 n--; 135 n--;
136 } else { 136 } else {
137 slash = rel + strlen(rel); 137 slash = rel + strlen(rel);
138 while (slash && n && (slash = xstrrchr(rel, slash, '/'))) 138 while (slash && n && (slash = xstrrchr(rel, slash, '/')))
139 n++; 139 n++;
140 } 140 }
141 if (slash && !n) { 141 if (slash && !n) {
142 *slash = '\0'; 142 *slash = '\0';
143 repo->section = xstrdup(rel); 143 repo->section = xstrdup(rel);
144 *slash = '/'; 144 *slash = '/';
145 if (!prefixcmp(repo->name, repo->section)) { 145 if (!prefixcmp(repo->name, repo->section)) {
146 repo->name += strlen(repo->section); 146 repo->name += strlen(repo->section);
147 if (*repo->name == '/') 147 if (*repo->name == '/')
148 repo->name++; 148 repo->name++;
149 } 149 }
150 } 150 }
151 } 151 }
152 152
153 p = fmt("%s/cgitrc", path); 153 p = fmt("%s/cgitrc", path);
154 if (!stat(p, &st)) { 154 if (!stat(p, &st)) {
155 config_fn = fn; 155 config_fn = fn;
156 parse_configfile(xstrdup(p), &repo_config); 156 parse_configfile(xstrdup(p), &repo_config);
157 } 157 }
158} 158}
159 159
160static void scan_path(const char *base, const char *path, repo_config_fn fn) 160static void scan_path(const char *base, const char *path, repo_config_fn fn)
161{ 161{
162 DIR *dir; 162 DIR *dir = opendir(path);
163 struct dirent *ent; 163 struct dirent *ent;
164 char *buf; 164 char *buf;
165 struct stat st; 165 struct stat st;
166 166
167 if (!dir) {
168 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
169 path, strerror(errno), errno);
170 return;
171 }
167 if (is_git_dir(path)) { 172 if (is_git_dir(path)) {
168 add_repo(base, path, fn); 173 add_repo(base, path, fn);
169 return; 174 goto end;
170 } 175 }
171 if (is_git_dir(fmt("%s/.git", path))) { 176 if (is_git_dir(fmt("%s/.git", path))) {
172 add_repo(base, fmt("%s/.git", path), fn); 177 add_repo(base, fmt("%s/.git", path), fn);
173 return; 178 goto end;
174 }
175 dir = opendir(path);
176 if (!dir) {
177 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
178 path, strerror(errno), errno);
179 return;
180 } 179 }
181 while((ent = readdir(dir)) != NULL) { 180 while((ent = readdir(dir)) != NULL) {
182 if (ent->d_name[0] == '.') { 181 if (ent->d_name[0] == '.') {
183 if (ent->d_name[1] == '\0') 182 if (ent->d_name[1] == '\0')
184 continue; 183 continue;
185 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 184 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
186 continue; 185 continue;
186 if (!ctx.cfg.scan_hidden_path)
187 continue;
187 } 188 }
188 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 189 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
189 if (!buf) { 190 if (!buf) {
190 fprintf(stderr, "Alloc error on %s: %s (%d)\n", 191 fprintf(stderr, "Alloc error on %s: %s (%d)\n",
191 path, strerror(errno), errno); 192 path, strerror(errno), errno);
192 exit(1); 193 exit(1);
193 } 194 }
194 sprintf(buf, "%s/%s", path, ent->d_name); 195 sprintf(buf, "%s/%s", path, ent->d_name);
195 if (stat(buf, &st)) { 196 if (stat(buf, &st)) {
196 fprintf(stderr, "Error checking path %s: %s (%d)\n", 197 fprintf(stderr, "Error checking path %s: %s (%d)\n",
197 buf, strerror(errno), errno); 198 buf, strerror(errno), errno);
198 free(buf); 199 free(buf);
199 continue; 200 continue;
200 } 201 }
201 if (S_ISDIR(st.st_mode)) 202 if (S_ISDIR(st.st_mode))
202 scan_path(base, buf, fn); 203 scan_path(base, buf, fn);
203 free(buf); 204 free(buf);
204 } 205 }
206end:
205 closedir(dir); 207 closedir(dir);
206} 208}
207 209
208#define lastc(s) s[strlen(s) - 1] 210#define lastc(s) s[strlen(s) - 1]
209 211
210void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn) 212void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
211{ 213{
212 char line[MAX_PATH * 2], *z; 214 char line[MAX_PATH * 2], *z;
213 FILE *projects; 215 FILE *projects;
214 int err; 216 int err;
215 217
216 projects = fopen(projectsfile, "r"); 218 projects = fopen(projectsfile, "r");
217 if (!projects) { 219 if (!projects) {
218 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n", 220 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
219 projectsfile, strerror(errno), errno); 221 projectsfile, strerror(errno), errno);
220 } 222 }
221 while (fgets(line, sizeof(line), projects) != NULL) { 223 while (fgets(line, sizeof(line), projects) != NULL) {
222 for (z = &lastc(line); 224 for (z = &lastc(line);
223 strlen(line) && strchr("\n\r", *z); 225 strlen(line) && strchr("\n\r", *z);
224 z = &lastc(line)) 226 z = &lastc(line))
225 *z = '\0'; 227 *z = '\0';
226 if (strlen(line)) 228 if (strlen(line))
227 scan_path(path, fmt("%s/%s", path, line), fn); 229 scan_path(path, fmt("%s/%s", path, line), fn);
228 } 230 }
229 if ((err = ferror(projects))) { 231 if ((err = ferror(projects))) {
230 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n", 232 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
231 projectsfile, strerror(err), err); 233 projectsfile, strerror(err), err);
232 } 234 }
233 fclose(projects); 235 fclose(projects);
234} 236}
235 237
236void scan_tree(const char *path, repo_config_fn fn) 238void scan_tree(const char *path, repo_config_fn fn)
237{ 239{
238 scan_path(path, path, fn); 240 scan_path(path, path, fn);
239} 241}
diff --git a/shared.c b/shared.c
index 765cd27..7ec2e19 100644
--- a/shared.c
+++ b/shared.c
@@ -11,96 +11,97 @@
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13 13
14int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
15{ 15{
16 if (result != 0) 16 if (result != 0)
17 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
18 return result; 18 return result;
19} 19}
20 20
21int chk_positive(int result, char *msg) 21int chk_positive(int result, char *msg)
22{ 22{
23 if (result <= 0) 23 if (result <= 0)
24 die("%s: %s", msg, strerror(errno)); 24 die("%s: %s", msg, strerror(errno));
25 return result; 25 return result;
26} 26}
27 27
28int chk_non_negative(int result, char *msg) 28int chk_non_negative(int result, char *msg)
29{ 29{
30 if (result < 0) 30 if (result < 0)
31 die("%s: %s",msg, strerror(errno)); 31 die("%s: %s",msg, strerror(errno));
32 return result; 32 return result;
33} 33}
34 34
35struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
36{ 36{
37 struct cgit_repo *ret; 37 struct cgit_repo *ret;
38 38
39 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
40 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
41 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
42 else 42 else
43 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
44 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
45 cgit_repolist.length * 45 cgit_repolist.length *
46 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
47 } 47 }
48 48
49 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
50 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches; 62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links; 63 ret->enable_subject_links = ctx.cfg.enable_subject_links;
63 ret->max_stats = ctx.cfg.max_stats; 64 ret->max_stats = ctx.cfg.max_stats;
64 ret->module_link = ctx.cfg.module_link; 65 ret->module_link = ctx.cfg.module_link;
65 ret->readme = ctx.cfg.readme; 66 ret->readme = ctx.cfg.readme;
66 ret->mtime = -1; 67 ret->mtime = -1;
67 ret->about_filter = ctx.cfg.about_filter; 68 ret->about_filter = ctx.cfg.about_filter;
68 ret->commit_filter = ctx.cfg.commit_filter; 69 ret->commit_filter = ctx.cfg.commit_filter;
69 ret->source_filter = ctx.cfg.source_filter; 70 ret->source_filter = ctx.cfg.source_filter;
70 return ret; 71 return ret;
71} 72}
72 73
73struct cgit_repo *cgit_get_repoinfo(const char *url) 74struct cgit_repo *cgit_get_repoinfo(const char *url)
74{ 75{
75 int i; 76 int i;
76 struct cgit_repo *repo; 77 struct cgit_repo *repo;
77 78
78 for (i=0; i<cgit_repolist.count; i++) { 79 for (i=0; i<cgit_repolist.count; i++) {
79 repo = &cgit_repolist.repos[i]; 80 repo = &cgit_repolist.repos[i];
80 if (!strcmp(repo->url, url)) 81 if (!strcmp(repo->url, url))
81 return repo; 82 return repo;
82 } 83 }
83 return NULL; 84 return NULL;
84} 85}
85 86
86void *cgit_free_commitinfo(struct commitinfo *info) 87void *cgit_free_commitinfo(struct commitinfo *info)
87{ 88{
88 free(info->author); 89 free(info->author);
89 free(info->author_email); 90 free(info->author_email);
90 free(info->committer); 91 free(info->committer);
91 free(info->committer_email); 92 free(info->committer_email);
92 free(info->subject); 93 free(info->subject);
93 free(info->msg); 94 free(info->msg);
94 free(info->msg_encoding); 95 free(info->msg_encoding);
95 free(info); 96 free(info);
96 return NULL; 97 return NULL;
97} 98}
98 99
99char *trim_end(const char *str, char c) 100char *trim_end(const char *str, char c)
100{ 101{
101 int len; 102 int len;
102 char *s, *t; 103 char *s, *t;
103 104
104 if (str == NULL) 105 if (str == NULL)
105 return NULL; 106 return NULL;
106 t = (char *)str; 107 t = (char *)str;
diff --git a/ui-log.c b/ui-log.c
index b9771fa..8add66a 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,252 +1,428 @@
1/* ui-log.c: functions for log output 1/* ui-log.c: functions for log output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "vector.h"
12 13
13int files, add_lines, rem_lines; 14int files, add_lines, rem_lines;
14 15
16/*
17 * The list of available column colors in the commit graph.
18 */
19static const char *column_colors_html[] = {
20 "<span class='column1'>",
21 "<span class='column2'>",
22 "<span class='column3'>",
23 "<span class='column4'>",
24 "<span class='column5'>",
25 "<span class='column6'>",
26 "</span>",
27};
28
29#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
30
15void count_lines(char *line, int size) 31void count_lines(char *line, int size)
16{ 32{
17 if (size <= 0) 33 if (size <= 0)
18 return; 34 return;
19 35
20 if (line[0] == '+') 36 if (line[0] == '+')
21 add_lines++; 37 add_lines++;
22 38
23 else if (line[0] == '-') 39 else if (line[0] == '-')
24 rem_lines++; 40 rem_lines++;
25} 41}
26 42
27void inspect_files(struct diff_filepair *pair) 43void inspect_files(struct diff_filepair *pair)
28{ 44{
29 unsigned long old_size = 0; 45 unsigned long old_size = 0;
30 unsigned long new_size = 0; 46 unsigned long new_size = 0;
31 int binary = 0; 47 int binary = 0;
32 48
33 files++; 49 files++;
34 if (ctx.repo->enable_log_linecount) 50 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 51 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, 0, ctx.qry.ignorews, 52 &new_size, &binary, 0, ctx.qry.ignorews,
37 count_lines); 53 count_lines);
38} 54}
39 55
40void show_commit_decorations(struct commit *commit) 56void show_commit_decorations(struct commit *commit)
41{ 57{
42 struct name_decoration *deco; 58 struct name_decoration *deco;
43 static char buf[1024]; 59 static char buf[1024];
44 60
45 buf[sizeof(buf) - 1] = 0; 61 buf[sizeof(buf) - 1] = 0;
46 deco = lookup_decoration(&name_decoration, &commit->object); 62 deco = lookup_decoration(&name_decoration, &commit->object);
47 while (deco) { 63 while (deco) {
48 if (!prefixcmp(deco->name, "refs/heads/")) { 64 if (!prefixcmp(deco->name, "refs/heads/")) {
49 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 65 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
50 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, 66 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
51 ctx.qry.vpath, 0, NULL, NULL, 67 ctx.qry.vpath, 0, NULL, NULL,
52 ctx.qry.showmsg); 68 ctx.qry.showmsg);
53 } 69 }
54 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 70 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
55 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 71 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
56 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 72 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
57 } 73 }
58 else if (!prefixcmp(deco->name, "refs/tags/")) { 74 else if (!prefixcmp(deco->name, "refs/tags/")) {
59 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 75 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
60 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 76 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
61 } 77 }
62 else if (!prefixcmp(deco->name, "refs/remotes/")) { 78 else if (!prefixcmp(deco->name, "refs/remotes/")) {
63 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 79 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
64 cgit_log_link(buf, NULL, "remote-deco", NULL, 80 cgit_log_link(buf, NULL, "remote-deco", NULL,
65 sha1_to_hex(commit->object.sha1), 81 sha1_to_hex(commit->object.sha1),
66 ctx.qry.vpath, 0, NULL, NULL, 82 ctx.qry.vpath, 0, NULL, NULL,
67 ctx.qry.showmsg); 83 ctx.qry.showmsg);
68 } 84 }
69 else { 85 else {
70 strncpy(buf, deco->name, sizeof(buf) - 1); 86 strncpy(buf, deco->name, sizeof(buf) - 1);
71 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 87 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
72 sha1_to_hex(commit->object.sha1), 88 sha1_to_hex(commit->object.sha1),
73 ctx.qry.vpath, 0); 89 ctx.qry.vpath, 0);
74 } 90 }
75 deco = deco->next; 91 deco = deco->next;
76 } 92 }
77} 93}
78 94
79void print_commit(struct commit *commit) 95void print_commit(struct commit *commit, struct rev_info *revs)
80{ 96{
81 struct commitinfo *info; 97 struct commitinfo *info;
82 char *tmp; 98 char *tmp;
83 int cols = 2; 99 int cols = revs->graph ? 3 : 2;
100 struct strbuf graphbuf = STRBUF_INIT;
101 struct strbuf msgbuf = STRBUF_INIT;
102
103 if (ctx.repo->enable_log_filecount) {
104 cols++;
105 if (ctx.repo->enable_log_linecount)
106 cols++;
107 }
108
109 if (revs->graph) {
110 /* Advance graph until current commit */
111 while (!graph_next_line(revs->graph, &graphbuf)) {
112 /* Print graph segment in otherwise empty table row */
113 html("<tr class='nohover'><td class='commitgraph'>");
114 html(graphbuf.buf);
115 htmlf("</td><td colspan='%d' /></tr>\n", cols);
116 strbuf_setlen(&graphbuf, 0);
117 }
118 /* Current commit's graph segment is now ready in graphbuf */
119 }
84 120
85 info = cgit_parse_commit(commit); 121 info = cgit_parse_commit(commit);
86 htmlf("<tr%s><td>", 122 htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
87 ctx.qry.showmsg ? " class='logheader'" : ""); 123
88 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 124 if (revs->graph) {
89 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 125 /* Print graph segment for current commit */
90 html_link_open(tmp, NULL, NULL); 126 html("<td class='commitgraph'>");
91 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 127 html(graphbuf.buf);
92 html_link_close(); 128 html("</td>");
93 htmlf("</td><td%s>", 129 strbuf_setlen(&graphbuf, 0);
94 ctx.qry.showmsg ? " class='logsubject'" : ""); 130 }
131 else {
132 html("<td>");
133 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
134 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
135 html_link_open(tmp, NULL, NULL);
136 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
137 html_link_close();
138 html("</td>");
139 }
140
141 htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
142 if (ctx.qry.showmsg) {
143 /* line-wrap long commit subjects instead of truncating them */
144 size_t subject_len = strlen(info->subject);
145
146 if (subject_len > ctx.cfg.max_msg_len &&
147 ctx.cfg.max_msg_len >= 15) {
148 /* symbol for signaling line-wrap (in PAGE_ENCODING) */
149 const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
150 int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
151
152 /* Rewind i to preceding space character */
153 while (i > 0 && !isspace(info->subject[i]))
154 --i;
155 if (!i) /* Oops, zero spaces. Reset i */
156 i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
157
158 /* add remainder starting at i to msgbuf */
159 strbuf_add(&msgbuf, info->subject + i, subject_len - i);
160 strbuf_trim(&msgbuf);
161 strbuf_add(&msgbuf, "\n\n", 2);
162
163 /* Place wrap_symbol at position i in info->subject */
164 strcpy(info->subject + i, wrap_symbol);
165 }
166 }
95 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 167 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
96 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 168 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
97 show_commit_decorations(commit); 169 show_commit_decorations(commit);
98 html("</td><td>"); 170 html("</td><td>");
99 html_txt(info->author); 171 html_txt(info->author);
172
173 if (revs->graph) {
174 html("</td><td>");
175 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
176 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
177 html_link_open(tmp, NULL, NULL);
178 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
179 html_link_close();
180 }
181
100 if (ctx.repo->enable_log_filecount) { 182 if (ctx.repo->enable_log_filecount) {
101 files = 0; 183 files = 0;
102 add_lines = 0; 184 add_lines = 0;
103 rem_lines = 0; 185 rem_lines = 0;
104 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); 186 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
105 html("</td><td>"); 187 html("</td><td>");
106 htmlf("%d", files); 188 htmlf("%d", files);
107 if (ctx.repo->enable_log_linecount) { 189 if (ctx.repo->enable_log_linecount) {
108 html("</td><td>"); 190 html("</td><td>");
109 htmlf("-%d/+%d", rem_lines, add_lines); 191 htmlf("-%d/+%d", rem_lines, add_lines);
110 } 192 }
111 } 193 }
112 html("</td></tr>\n"); 194 html("</td></tr>\n");
113 if (ctx.qry.showmsg) {
114 struct strbuf notes = STRBUF_INIT;
115 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0);
116 195
117 if (ctx.repo->enable_log_filecount) { 196 if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
118 cols++; 197 html("<tr class='nohover'>");
119 if (ctx.repo->enable_log_linecount) 198
120 cols++; 199 if (ctx.qry.showmsg) {
200 /* Concatenate commit message + notes in msgbuf */
201 if (info->msg && *(info->msg)) {
202 strbuf_addstr(&msgbuf, info->msg);
203 strbuf_addch(&msgbuf, '\n');
204 }
205 format_note(NULL, commit->object.sha1, &msgbuf,
206 PAGE_ENCODING,
207 NOTES_SHOW_HEADER | NOTES_INDENT);
208 strbuf_addch(&msgbuf, '\n');
209 strbuf_ltrim(&msgbuf);
121 } 210 }
122 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 211
123 cols); 212 if (revs->graph) {
124 html_txt(info->msg); 213 int lines = 0;
125 html("</td></tr>\n"); 214
126 if (notes.len != 0) { 215 /* Calculate graph padding */
127 html("<tr class='nohover'>"); 216 if (ctx.qry.showmsg) {
128 html("<td class='lognotes-label'>Notes:</td>"); 217 /* Count #lines in commit message + notes */
129 htmlf("<td colspan='%d' class='lognotes'>", 218 const char *p = msgbuf.buf;
130 cols); 219 lines = 1;
131 html_txt(notes.buf); 220 while ((p = strchr(p, '\n'))) {
132 html("</td></tr>\n"); 221 p++;
222 lines++;
223 }
224 }
225
226 /* Print graph padding */
227 html("<td class='commitgraph'>");
228 while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
229 if (graphbuf.len)
230 html("\n");
231 strbuf_setlen(&graphbuf, 0);
232 graph_next_line(revs->graph, &graphbuf);
233 html(graphbuf.buf);
234 lines--;
235 }
236 html("</td>\n");
133 } 237 }
134 strbuf_release(&notes); 238 else
239 html("<td/>"); /* Empty 'Age' column */
240
241 /* Print msgbuf into remainder of table row */
242 htmlf("<td colspan='%d'%s>\n", cols,
243 ctx.qry.showmsg ? " class='logmsg'" : "");
244 html_txt(msgbuf.buf);
245 html("</td></tr>\n");
135 } 246 }
247
248 strbuf_release(&msgbuf);
249 strbuf_release(&graphbuf);
136 cgit_free_commitinfo(info); 250 cgit_free_commitinfo(info);
137} 251}
138 252
139static const char *disambiguate_ref(const char *ref) 253static const char *disambiguate_ref(const char *ref)
140{ 254{
141 unsigned char sha1[20]; 255 unsigned char sha1[20];
142 const char *longref; 256 const char *longref;
143 257
144 longref = fmt("refs/heads/%s", ref); 258 longref = fmt("refs/heads/%s", ref);
145 if (get_sha1(longref, sha1) == 0) 259 if (get_sha1(longref, sha1) == 0)
146 return longref; 260 return longref;
147 261
148 return ref; 262 return ref;
149} 263}
150 264
265static char *next_token(char **src)
266{
267 char *result;
268
269 if (!src || !*src)
270 return NULL;
271 while (isspace(**src))
272 (*src)++;
273 if (!**src)
274 return NULL;
275 result = *src;
276 while (**src) {
277 if (isspace(**src)) {
278 **src = '\0';
279 (*src)++;
280 break;
281 }
282 (*src)++;
283 }
284 return result;
285}
286
151void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 287void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
152 char *path, int pager) 288 char *path, int pager, int commit_graph)
153{ 289{
154 struct rev_info rev; 290 struct rev_info rev;
155 struct commit *commit; 291 struct commit *commit;
156 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 292 struct vector vec = VECTOR_INIT(char *);
157 int argc = 2;
158 int i, columns = 3; 293 int i, columns = 3;
294 char *arg;
295
296 /* First argv is NULL */
297 vector_push(&vec, NULL, 0);
159 298
160 if (!tip) 299 if (!tip)
161 tip = ctx.qry.head; 300 tip = ctx.qry.head;
162 301 tip = disambiguate_ref(tip);
163 argv[1] = disambiguate_ref(tip); 302 vector_push(&vec, &tip, 0);
164 303
165 if (grep && pattern && *pattern) { 304 if (grep && pattern && *pattern) {
305 pattern = xstrdup(pattern);
166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") || 306 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
167 !strcmp(grep, "committer")) 307 !strcmp(grep, "committer")) {
168 argv[argc++] = fmt("--%s=%s", grep, pattern); 308 arg = fmt("--%s=%s", grep, pattern);
169 if (!strcmp(grep, "range")) 309 vector_push(&vec, &arg, 0);
170 argv[1] = pattern; 310 }
311 if (!strcmp(grep, "range")) {
312 /* Split the pattern at whitespace and add each token
313 * as a revision expression. Do not accept other
314 * rev-list options. Also, replace the previously
315 * pushed tip (it's no longer relevant).
316 */
317 vec.count--;
318 while ((arg = next_token(&pattern))) {
319 if (*arg == '-') {
320 fprintf(stderr, "Bad range expr: %s\n",
321 arg);
322 break;
323 }
324 vector_push(&vec, &arg, 0);
325 }
326 }
327 }
328 if (commit_graph) {
329 static const char *graph_arg = "--graph";
330 static const char *color_arg = "--color";
331 vector_push(&vec, &graph_arg, 0);
332 vector_push(&vec, &color_arg, 0);
333 graph_set_column_colors(column_colors_html,
334 COLUMN_COLORS_HTML_MAX);
171 } 335 }
172 336
173 if (path) { 337 if (path) {
174 argv[argc++] = "--"; 338 arg = "--";
175 argv[argc++] = path; 339 vector_push(&vec, &arg, 0);
340 vector_push(&vec, &path, 0);
176 } 341 }
342
343 /* Make sure the vector is NULL-terminated */
344 vector_push(&vec, NULL, 0);
345 vec.count--;
346
177 init_revisions(&rev, NULL); 347 init_revisions(&rev, NULL);
178 rev.abbrev = DEFAULT_ABBREV; 348 rev.abbrev = DEFAULT_ABBREV;
179 rev.commit_format = CMIT_FMT_DEFAULT; 349 rev.commit_format = CMIT_FMT_DEFAULT;
180 rev.verbose_header = 1; 350 rev.verbose_header = 1;
181 rev.show_root_diff = 0; 351 rev.show_root_diff = 0;
182 setup_revisions(argc, argv, &rev, NULL); 352 setup_revisions(vec.count, vec.data, &rev, NULL);
183 load_ref_decorations(DECORATE_FULL_REFS); 353 load_ref_decorations(DECORATE_FULL_REFS);
184 rev.show_decorations = 1; 354 rev.show_decorations = 1;
185 rev.grep_filter.regflags |= REG_ICASE; 355 rev.grep_filter.regflags |= REG_ICASE;
186 compile_grep_patterns(&rev.grep_filter); 356 compile_grep_patterns(&rev.grep_filter);
187 prepare_revision_walk(&rev); 357 prepare_revision_walk(&rev);
188 358
189 if (pager) 359 if (pager)
190 html("<table class='list nowrap'>"); 360 html("<table class='list nowrap'>");
191 361
192 html("<tr class='nohover'><th class='left'>Age</th>" 362 html("<tr class='nohover'>");
193 "<th class='left'>Commit message"); 363 if (commit_graph)
364 html("<th></th>");
365 else
366 html("<th class='left'>Age</th>");
367 html("<th class='left'>Commit message");
194 if (pager) { 368 if (pager) {
195 html(" ("); 369 html(" (");
196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 370 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
197 NULL, ctx.qry.head, ctx.qry.sha1, 371 NULL, ctx.qry.head, ctx.qry.sha1,
198 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 372 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
199 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 373 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
200 html(")"); 374 html(")");
201 } 375 }
202 html("</th><th class='left'>Author</th>"); 376 html("</th><th class='left'>Author</th>");
377 if (commit_graph)
378 html("<th class='left'>Age</th>");
203 if (ctx.repo->enable_log_filecount) { 379 if (ctx.repo->enable_log_filecount) {
204 html("<th class='left'>Files</th>"); 380 html("<th class='left'>Files</th>");
205 columns++; 381 columns++;
206 if (ctx.repo->enable_log_linecount) { 382 if (ctx.repo->enable_log_linecount) {
207 html("<th class='left'>Lines</th>"); 383 html("<th class='left'>Lines</th>");
208 columns++; 384 columns++;
209 } 385 }
210 } 386 }
211 html("</tr>\n"); 387 html("</tr>\n");
212 388
213 if (ofs<0) 389 if (ofs<0)
214 ofs = 0; 390 ofs = 0;
215 391
216 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 392 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
217 free(commit->buffer); 393 free(commit->buffer);
218 commit->buffer = NULL; 394 commit->buffer = NULL;
219 free_commit_list(commit->parents); 395 free_commit_list(commit->parents);
220 commit->parents = NULL; 396 commit->parents = NULL;
221 } 397 }
222 398
223 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 399 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
224 print_commit(commit); 400 print_commit(commit, &rev);
225 free(commit->buffer); 401 free(commit->buffer);
226 commit->buffer = NULL; 402 commit->buffer = NULL;
227 free_commit_list(commit->parents); 403 free_commit_list(commit->parents);
228 commit->parents = NULL; 404 commit->parents = NULL;
229 } 405 }
230 if (pager) { 406 if (pager) {
231 html("</table><div class='pager'>"); 407 html("</table><div class='pager'>");
232 if (ofs > 0) { 408 if (ofs > 0) {
233 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 409 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
234 ctx.qry.sha1, ctx.qry.vpath, 410 ctx.qry.sha1, ctx.qry.vpath,
235 ofs - cnt, ctx.qry.grep, 411 ofs - cnt, ctx.qry.grep,
236 ctx.qry.search, ctx.qry.showmsg); 412 ctx.qry.search, ctx.qry.showmsg);
237 html("&nbsp;"); 413 html("&nbsp;");
238 } 414 }
239 if ((commit = get_revision(&rev)) != NULL) { 415 if ((commit = get_revision(&rev)) != NULL) {
240 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 416 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
241 ctx.qry.sha1, ctx.qry.vpath, 417 ctx.qry.sha1, ctx.qry.vpath,
242 ofs + cnt, ctx.qry.grep, 418 ofs + cnt, ctx.qry.grep,
243 ctx.qry.search, ctx.qry.showmsg); 419 ctx.qry.search, ctx.qry.showmsg);
244 } 420 }
245 html("</div>"); 421 html("</div>");
246 } else if ((commit = get_revision(&rev)) != NULL) { 422 } else if ((commit = get_revision(&rev)) != NULL) {
247 html("<tr class='nohover'><td colspan='3'>"); 423 html("<tr class='nohover'><td colspan='3'>");
248 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, 424 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
249 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); 425 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
250 html("</td></tr>\n"); 426 html("</td></tr>\n");
251 } 427 }
252} 428}
diff --git a/ui-log.h b/ui-log.h
index 6034055..d0cb779 100644
--- a/ui-log.h
+++ b/ui-log.h
@@ -1,8 +1,9 @@
1#ifndef UI_LOG_H 1#ifndef UI_LOG_H
2#define UI_LOG_H 2#define UI_LOG_H
3 3
4extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, 4extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep,
5 char *pattern, char *path, int pager); 5 char *pattern, char *path, int pager,
6 int commit_graph);
6extern void show_commit_decorations(struct commit *commit); 7extern void show_commit_decorations(struct commit *commit);
7 8
8#endif /* UI_LOG_H */ 9#endif /* UI_LOG_H */
diff --git a/ui-summary.c b/ui-summary.c
index b203bcc..5be2545 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -14,97 +14,97 @@
14#include "ui-blob.h" 14#include "ui-blob.h"
15 15
16int urls = 0; 16int urls = 0;
17 17
18static void print_url(char *base, char *suffix) 18static void print_url(char *base, char *suffix)
19{ 19{
20 if (!base || !*base) 20 if (!base || !*base)
21 return; 21 return;
22 if (urls++ == 0) { 22 if (urls++ == 0) {
23 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 23 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
24 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n"); 24 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n");
25 } 25 }
26 if (suffix && *suffix) 26 if (suffix && *suffix)
27 base = fmt("%s/%s", base, suffix); 27 base = fmt("%s/%s", base, suffix);
28 html("<tr><td colspan='4'><a href='"); 28 html("<tr><td colspan='4'><a href='");
29 html_url_path(base); 29 html_url_path(base);
30 html("'>"); 30 html("'>");
31 html_txt(base); 31 html_txt(base);
32 html("</a></td></tr>\n"); 32 html("</a></td></tr>\n");
33} 33}
34 34
35static void print_urls(char *txt, char *suffix) 35static void print_urls(char *txt, char *suffix)
36{ 36{
37 char *h = txt, *t, c; 37 char *h = txt, *t, c;
38 38
39 while (h && *h) { 39 while (h && *h) {
40 while (h && *h == ' ') 40 while (h && *h == ' ')
41 h++; 41 h++;
42 t = h; 42 t = h;
43 while (t && *t && *t != ' ') 43 while (t && *t && *t != ' ')
44 t++; 44 t++;
45 c = *t; 45 c = *t;
46 *t = 0; 46 *t = 0;
47 print_url(h, suffix); 47 print_url(h, suffix);
48 *t = c; 48 *t = c;
49 h = t; 49 h = t;
50 } 50 }
51} 51}
52 52
53void cgit_print_summary() 53void cgit_print_summary()
54{ 54{
55 html("<table summary='repository info' class='list nowrap'>"); 55 html("<table summary='repository info' class='list nowrap'>");
56 cgit_print_branches(ctx.cfg.summary_branches); 56 cgit_print_branches(ctx.cfg.summary_branches);
57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
58 cgit_print_tags(ctx.cfg.summary_tags); 58 cgit_print_tags(ctx.cfg.summary_tags);
59 if (ctx.cfg.summary_log > 0) { 59 if (ctx.cfg.summary_log > 0) {
60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
61 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, 61 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
62 NULL, NULL, 0); 62 NULL, NULL, 0, 0);
63 } 63 }
64 if (ctx.repo->clone_url) 64 if (ctx.repo->clone_url)
65 print_urls(ctx.repo->clone_url, NULL); 65 print_urls(ctx.repo->clone_url, NULL);
66 else if (ctx.cfg.clone_prefix) 66 else if (ctx.cfg.clone_prefix)
67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url); 67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
68 html("</table>"); 68 html("</table>");
69} 69}
70 70
71void cgit_print_repo_readme(char *path) 71void cgit_print_repo_readme(char *path)
72{ 72{
73 char *slash, *tmp, *colon, *ref; 73 char *slash, *tmp, *colon, *ref;
74 74
75 if (!ctx.repo->readme || !(*ctx.repo->readme)) 75 if (!ctx.repo->readme || !(*ctx.repo->readme))
76 return; 76 return;
77 77
78 ref = NULL; 78 ref = NULL;
79 79
80 /* Check if the readme is tracked in the git repo. */ 80 /* Check if the readme is tracked in the git repo. */
81 colon = strchr(ctx.repo->readme, ':'); 81 colon = strchr(ctx.repo->readme, ':');
82 if (colon && strlen(colon) > 1) { 82 if (colon && strlen(colon) > 1) {
83 *colon = '\0'; 83 *colon = '\0';
84 ref = ctx.repo->readme; 84 ref = ctx.repo->readme;
85 ctx.repo->readme = colon + 1; 85 ctx.repo->readme = colon + 1;
86 if (!(*ctx.repo->readme)) 86 if (!(*ctx.repo->readme))
87 return; 87 return;
88 } 88 }
89 89
90 /* Prepend repo path to relative readme path unless tracked. */ 90 /* Prepend repo path to relative readme path unless tracked. */
91 if (!ref && *ctx.repo->readme != '/') 91 if (!ref && *ctx.repo->readme != '/')
92 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, 92 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path,
93 ctx.repo->readme)); 93 ctx.repo->readme));
94 94
95 /* If a subpath is specified for the about page, make it relative 95 /* If a subpath is specified for the about page, make it relative
96 * to the directory containing the configured readme. 96 * to the directory containing the configured readme.
97 */ 97 */
98 if (path) { 98 if (path) {
99 slash = strrchr(ctx.repo->readme, '/'); 99 slash = strrchr(ctx.repo->readme, '/');
100 if (!slash) { 100 if (!slash) {
101 if (!colon) 101 if (!colon)
102 return; 102 return;
103 slash = colon; 103 slash = colon;
104 } 104 }
105 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1); 105 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
106 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1); 106 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
107 strcpy(tmp + (slash - ctx.repo->readme + 1), path); 107 strcpy(tmp + (slash - ctx.repo->readme + 1), path);
108 } else 108 } else
109 tmp = ctx.repo->readme; 109 tmp = ctx.repo->readme;
110 110
diff --git a/vector.c b/vector.c
new file mode 100644
index 0000000..0863908
--- a/dev/null
+++ b/vector.c
@@ -0,0 +1,38 @@
1#include <stdio.h>
2#include <string.h>
3#include <errno.h>
4#include "vector.h"
5
6static int grow(struct vector *vec, int gently)
7{
8 size_t new_alloc;
9 void *new_data;
10
11 new_alloc = vec->alloc * 3 / 2;
12 if (!new_alloc)
13 new_alloc = 8;
14 new_data = realloc(vec->data, new_alloc * vec->size);
15 if (!new_data) {
16 if (gently)
17 return ENOMEM;
18 perror("vector.c:grow()");
19 exit(1);
20 }
21 vec->data = new_data;
22 vec->alloc = new_alloc;
23 return 0;
24}
25
26int vector_push(struct vector *vec, const void *data, int gently)
27{
28 int rc;
29
30 if (vec->count == vec->alloc && (rc = grow(vec, gently)))
31 return rc;
32 if (data)
33 memmove(vec->data + vec->count * vec->size, data, vec->size);
34 else
35 memset(vec->data + vec->count * vec->size, 0, vec->size);
36 vec->count++;
37 return 0;
38}
diff --git a/vector.h b/vector.h
new file mode 100644
index 0000000..c64eb1f
--- a/dev/null
+++ b/vector.h
@@ -0,0 +1,17 @@
1#ifndef CGIT_VECTOR_H
2#define CGIT_VECTOR_H
3
4#include <stdlib.h>
5
6struct vector {
7 size_t size;
8 size_t count;
9 size_t alloc;
10 void *data;
11};
12
13#define VECTOR_INIT(type) {sizeof(type), 0, 0, NULL}
14
15int vector_push(struct vector *vec, const void *data, int gently);
16
17#endif /* CGIT_VECTOR_H */