summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--Makefile97
-rw-r--r--README2
-rw-r--r--cache.h1
-rw-r--r--cgit.c99
-rw-r--r--cgit.css184
-rw-r--r--cgit.h36
-rw-r--r--cgit.pngbin1840 -> 1488 bytes
-rw-r--r--cgitrc.5.txt115
-rw-r--r--cmd.c51
-rw-r--r--cmd.h3
-rwxr-xr-xfilters/commit-links.sh16
-rwxr-xr-xfilters/syntax-highlighting.sh29
m---------git0
-rw-r--r--html.c84
-rw-r--r--html.h21
-rw-r--r--scan-tree.c141
-rw-r--r--scan-tree.h3
-rw-r--r--shared.c93
-rw-r--r--ui-atom.c14
-rw-r--r--ui-blob.c37
-rw-r--r--ui-blob.h1
-rw-r--r--ui-commit.c46
-rw-r--r--ui-commit.h2
-rw-r--r--ui-diff.c96
-rw-r--r--ui-diff.h6
-rw-r--r--ui-log.c282
-rw-r--r--ui-log.h3
-rw-r--r--ui-patch.c8
-rw-r--r--ui-patch.h2
-rw-r--r--ui-plain.c68
-rw-r--r--ui-refs.c4
-rw-r--r--ui-repolist.c6
-rw-r--r--ui-shared.c288
-rw-r--r--ui-shared.h71
-rw-r--r--ui-snapshot.c14
-rw-r--r--ui-ssdiff.c383
-rw-r--r--ui-ssdiff.h13
-rw-r--r--ui-stats.c20
-rw-r--r--ui-summary.c40
-rw-r--r--ui-tag.c24
-rw-r--r--ui-tree.c27
-rw-r--r--vector.c38
-rw-r--r--vector.h17
43 files changed, 2097 insertions, 388 deletions
diff --git a/Makefile b/Makefile
index 304521e..14b4df4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,21 +1,41 @@
1CGIT_VERSION = v0.8.3.5 1CGIT_VERSION = v0.8.3.5
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
8libdir = $(prefix)/lib
9filterdir = $(libdir)/cgit/filters
10docdir = $(prefix)/share/doc/cgit
11htmldir = $(docdir)
12pdfdir = $(docdir)
13mandir = $(prefix)/share/man
7SHA1_HEADER = <openssl/sha.h> 14SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.7.3 15GIT_VER = 1.7.4
9GIT_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
10INSTALL = install 17INSTALL = install
18MAN5_TXT = $(wildcard *.5.txt)
19MAN_TXT = $(MAN5_TXT)
20DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
21DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
22DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT))
11 23
12# Define NO_STRCASESTR if you don't have strcasestr. 24# Define NO_STRCASESTR if you don't have strcasestr.
13# 25#
26# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
27# implementation (slower).
28#
14# 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).
15# 30#
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,
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.
35#
16 36
17#-include config.mak 37#-include config.mak
18 38
19# 39#
20# Platform specific tweaks 40# Platform specific tweaks
21# 41#
@@ -56,22 +76,22 @@ ifndef V
56endif 76endif
57 77
58# 78#
59# Define a pattern rule for automatic dependency building 79# Define a pattern rule for automatic dependency building
60# 80#
61%.d: %.c 81%.d: %.c
62 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 82 $(QUIET_MM)$(CC) $(CFLAGS) -MM -MP $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
63 83
64# 84#
65# Define a pattern rule for silent object building 85# Define a pattern rule for silent object building
66# 86#
67%.o: %.c 87%.o: %.c
68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 88 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
69 89
70 90
71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto -lpthread 91EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread
72OBJECTS = 92OBJECTS =
73OBJECTS += cache.o 93OBJECTS += cache.o
74OBJECTS += cgit.o 94OBJECTS += cgit.o
75OBJECTS += cmd.o 95OBJECTS += cmd.o
76OBJECTS += configfile.o 96OBJECTS += configfile.o
77OBJECTS += html.o 97OBJECTS += html.o
@@ -87,24 +107,27 @@ OBJECTS += ui-log.o
87OBJECTS += ui-patch.o 107OBJECTS += ui-patch.o
88OBJECTS += ui-plain.o 108OBJECTS += ui-plain.o
89OBJECTS += ui-refs.o 109OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 110OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 111OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 112OBJECTS += ui-snapshot.o
113OBJECTS += ui-ssdiff.o
93OBJECTS += ui-stats.o 114OBJECTS += ui-stats.o
94OBJECTS += ui-summary.o 115OBJECTS += ui-summary.o
95OBJECTS += ui-tag.o 116OBJECTS += ui-tag.o
96OBJECTS += ui-tree.o 117OBJECTS += ui-tree.o
118OBJECTS += vector.o
97 119
98ifdef NEEDS_LIBICONV 120ifdef NEEDS_LIBICONV
99 EXTLIBS += -liconv 121 EXTLIBS += -liconv
100endif 122endif
101 123
102 124
103.PHONY: all libgit test install uninstall clean force-version get-git \ 125.PHONY: all libgit test install uninstall clean force-version get-git \
104 doc man-doc html-doc clean-doc 126 doc clean-doc install-doc install-man install-html install-pdf \
127 uninstall-doc uninstall-man uninstall-html uninstall-pdf
105 128
106all: cgit 129all: cgit
107 130
108VERSION: force-version 131VERSION: force-version
109 @./gen-version.sh "$(CGIT_VERSION)" 132 @./gen-version.sh "$(CGIT_VERSION)"
110-include VERSION 133-include VERSION
@@ -114,54 +137,104 @@ CFLAGS += -g -Wall -Igit
114CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 137CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
115CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 138CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
116CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 139CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
117CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 140CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
118CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 141CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
119 142
143GIT_OPTIONS = prefix=/usr
144
120ifdef NO_ICONV 145ifdef NO_ICONV
121 CFLAGS += -DNO_ICONV 146 CFLAGS += -DNO_ICONV
122endif 147endif
123ifdef NO_STRCASESTR 148ifdef NO_STRCASESTR
124 CFLAGS += -DNO_STRCASESTR 149 CFLAGS += -DNO_STRCASESTR
125endif 150endif
151ifdef NO_C99_FORMAT
152 CFLAGS += -DNO_C99_FORMAT
153endif
154ifdef NO_OPENSSL
155 CFLAGS += -DNO_OPENSSL
156 GIT_OPTIONS += NO_OPENSSL=1
157else
158 EXTLIBS += -lcrypto
159endif
126 160
127cgit: $(OBJECTS) libgit 161cgit: $(OBJECTS) libgit
128 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 162 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
129 163
130cgit.o: VERSION 164cgit.o: VERSION
131 165
166ifneq "$(MAKECMDGOALS)" "clean"
132-include $(OBJECTS:.o=.d) 167-include $(OBJECTS:.o=.d)
168endif
133 169
134libgit: 170libgit:
135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 171 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
136 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 172 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
137 173
138test: all 174test: all
139 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 175 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
140 176
141install: all 177install: all
142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 178 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
143 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 179 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
144 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 180 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
145 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 181 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
146 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 182 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
183 $(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir)
184 $(INSTALL) -m 0755 filters/* $(DESTDIR)$(filterdir)
185
186install-doc: install-man install-html install-pdf
187
188install-man: doc-man
189 $(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5
190 $(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5
191
192install-html: doc-html
193 $(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir)
194 $(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir)
195
196install-pdf: doc-pdf
197 $(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir)
198 $(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir)
147 199
148uninstall: 200uninstall:
149 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 201 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
150 rm -f $(CGIT_DATA_PATH)/cgit.css 202 rm -f $(CGIT_DATA_PATH)/cgit.css
151 rm -f $(CGIT_DATA_PATH)/cgit.png 203 rm -f $(CGIT_DATA_PATH)/cgit.png
152 204
153doc: man-doc html-doc pdf-doc 205uninstall-doc: uninstall-man uninstall-html uninstall-pdf
206
207uninstall-man:
208 @for i in $(DOC_MAN5); do \
209 rm -fv $(DESTDIR)$(mandir)/man5/$$i; \
210 done
211
212uninstall-html:
213 @for i in $(DOC_HTML); do \
214 rm -fv $(DESTDIR)$(htmldir)/$$i; \
215 done
216
217uninstall-pdf:
218 @for i in $(DOC_PDF); do \
219 rm -fv $(DESTDIR)$(pdfdir)/$$i; \
220 done
221
222doc: doc-man doc-html doc-pdf
223doc-man: doc-man5
224doc-man5: $(DOC_MAN5)
225doc-html: $(DOC_HTML)
226doc-pdf: $(DOC_PDF)
154 227
155man-doc: cgitrc.5.txt 228%.5 : %.5.txt
156 a2x -f manpage cgitrc.5.txt 229 a2x -f manpage $<
157 230
158html-doc: cgitrc.5.txt 231$(DOC_HTML): %.html : %.txt
159 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt 232 a2x -f xhtml --stylesheet=cgit-doc.css $<
160 233
161pdf-doc: cgitrc.5.txt 234$(DOC_PDF): %.pdf : %.txt
162 a2x -f pdf cgitrc.5.txt 235 a2x -f pdf cgitrc.5.txt
163 236
164clean: clean-doc 237clean: clean-doc
165 rm -f cgit VERSION *.o *.d 238 rm -f cgit VERSION *.o *.d
166 239
167clean-doc: 240clean-doc:
diff --git a/README b/README
index 73ec332..050e21e 100644
--- a/README
+++ b/README
@@ -46,13 +46,13 @@ Apache configuration
46 46
47A new Directory-section must probably be added for cgit, possibly something 47A new Directory-section must probably be added for cgit, possibly something
48like this: 48like this:
49 49
50 <Directory "/var/www/htdocs/cgit/"> 50 <Directory "/var/www/htdocs/cgit/">
51 AllowOverride None 51 AllowOverride None
52 Options ExecCGI 52 Options +ExecCGI
53 Order allow,deny 53 Order allow,deny
54 Allow from all 54 Allow from all
55 </Directory> 55 </Directory>
56 56
57 57
58Runtime configuration 58Runtime configuration
diff --git a/cache.h b/cache.h
index ac9276b..5cfdb4f 100644
--- a/cache.h
+++ b/cache.h
@@ -27,11 +27,12 @@ extern int cache_process(int size, const char *path, const char *key, int ttl,
27 27
28 28
29/* List info about all cache entries on stdout */ 29/* List info about all cache entries on stdout */
30extern int cache_ls(const char *path); 30extern int cache_ls(const char *path);
31 31
32/* Print a message to stdout */ 32/* Print a message to stdout */
33__attribute__((format (printf,1,2)))
33extern void cache_log(const char *format, ...); 34extern void cache_log(const char *format, ...);
34 35
35extern unsigned long hash_str(const char *str); 36extern unsigned long hash_str(const char *str);
36 37
37#endif /* CGIT_CACHE_H */ 38#endif /* CGIT_CACHE_H */
diff --git a/cgit.c b/cgit.c
index af9832f..f4dd6ef 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,9 +1,10 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
7 */ 8 */
8 9
9#include "cgit.h" 10#include "cgit.h"
@@ -53,28 +54,35 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
53 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
58 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);
59 else if (!strcmp(name, "enable-log-filecount")) 62 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 64 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
66 else if (!strcmp(name, "enable-remote-branches"))
67 repo->enable_remote_branches = atoi(value);
68 else if (!strcmp(name, "enable-subject-links"))
69 repo->enable_subject_links = atoi(value);
63 else if (!strcmp(name, "max-stats")) 70 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 71 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 72 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 73 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 74 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 75 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 76 else if (!strcmp(name, "readme") && value != NULL)
70 if (*value == '/')
71 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
72 else 78 else if (!strcmp(name, "logo") && value != NULL)
73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 79 repo->logo = xstrdup(value);
74 } else if (ctx.cfg.enable_filter_overrides) { 80 else if (!strcmp(name, "logo-link") && value != NULL)
81 repo->logo_link = xstrdup(value);
82 else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter")) 83 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0); 84 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter")) 85 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0); 86 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter")) 87 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1); 88 repo->source_filter = new_filter(value, 1);
@@ -88,12 +96,14 @@ void config_cb(const char *name, const char *value)
88 else if (!strcmp(name, "repo.url")) 96 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value); 97 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path")) 98 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/'); 99 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo.")) 100 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value); 101 repo_config(ctx.repo, name + 5, value);
102 else if (!strcmp(name, "readme"))
103 ctx.cfg.readme = xstrdup(value);
94 else if (!strcmp(name, "root-title")) 104 else if (!strcmp(name, "root-title"))
95 ctx.cfg.root_title = xstrdup(value); 105 ctx.cfg.root_title = xstrdup(value);
96 else if (!strcmp(name, "root-desc")) 106 else if (!strcmp(name, "root-desc"))
97 ctx.cfg.root_desc = xstrdup(value); 107 ctx.cfg.root_desc = xstrdup(value);
98 else if (!strcmp(name, "root-readme")) 108 else if (!strcmp(name, "root-readme"))
99 ctx.cfg.root_readme = xstrdup(value); 109 ctx.cfg.root_readme = xstrdup(value);
@@ -114,12 +124,14 @@ void config_cb(const char *name, const char *value)
114 else if (!strcmp(name, "index-info")) 124 else if (!strcmp(name, "index-info"))
115 ctx.cfg.index_info = xstrdup(value); 125 ctx.cfg.index_info = xstrdup(value);
116 else if (!strcmp(name, "logo-link")) 126 else if (!strcmp(name, "logo-link"))
117 ctx.cfg.logo_link = xstrdup(value); 127 ctx.cfg.logo_link = xstrdup(value);
118 else if (!strcmp(name, "module-link")) 128 else if (!strcmp(name, "module-link"))
119 ctx.cfg.module_link = xstrdup(value); 129 ctx.cfg.module_link = xstrdup(value);
130 else if (!strcmp(name, "strict-export"))
131 ctx.cfg.strict_export = xstrdup(value);
120 else if (!strcmp(name, "virtual-root")) { 132 else if (!strcmp(name, "virtual-root")) {
121 ctx.cfg.virtual_root = trim_end(value, '/'); 133 ctx.cfg.virtual_root = trim_end(value, '/');
122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 134 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
123 ctx.cfg.virtual_root = ""; 135 ctx.cfg.virtual_root = "";
124 } else if (!strcmp(name, "nocache")) 136 } else if (!strcmp(name, "nocache"))
125 ctx.cfg.nocache = atoi(value); 137 ctx.cfg.nocache = atoi(value);
@@ -128,26 +140,34 @@ void config_cb(const char *name, const char *value)
128 else if (!strcmp(name, "noheader")) 140 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value); 141 ctx.cfg.noheader = atoi(value);
130 else if (!strcmp(name, "snapshots")) 142 else if (!strcmp(name, "snapshots"))
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 143 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 144 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 145 ctx.cfg.enable_filter_overrides = atoi(value);
146 else if (!strcmp(name, "enable-gitweb-owner"))
147 ctx.cfg.enable_gitweb_owner = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 148 else if (!strcmp(name, "enable-index-links"))
135 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);
136 else if (!strcmp(name, "enable-log-filecount")) 152 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 153 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 154 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 155 ctx.cfg.enable_log_linecount = atoi(value);
156 else if (!strcmp(name, "enable-remote-branches"))
157 ctx.cfg.enable_remote_branches = atoi(value);
158 else if (!strcmp(name, "enable-subject-links"))
159 ctx.cfg.enable_subject_links = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 160 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 161 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 162 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 163 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 164 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 165 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 166 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 167 ctx.cfg.cache_root = xstrdup(expand_macros(value));
148 else if (!strcmp(name, "cache-root-ttl")) 168 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 169 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 170 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 171 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 172 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 173 ctx.cfg.cache_scanrc_ttl = atoi(value);
@@ -158,47 +178,64 @@ void config_cb(const char *name, const char *value)
158 else if (!strcmp(name, "about-filter")) 178 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 179 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 180 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 181 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 182 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 183 ctx.cfg.embedded = atoi(value);
184 else if (!strcmp(name, "max-atom-items"))
185 ctx.cfg.max_atom_items = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 186 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 187 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 188 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 189 ctx.cfg.max_repodesc_len = atoi(value);
190 else if (!strcmp(name, "max-blob-size"))
191 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 192 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 193 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 194 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 195 ctx.cfg.max_commit_count = atoi(value);
196 else if (!strcmp(name, "project-list"))
197 ctx.cfg.project_list = xstrdup(expand_macros(value));
172 else if (!strcmp(name, "scan-path")) 198 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 199 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 200 process_cached_repolist(expand_macros(value));
201 else if (ctx.cfg.project_list)
202 scan_projects(expand_macros(value),
203 ctx.cfg.project_list, repo_config);
175 else 204 else
176 scan_tree(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);
208 else if (!strcmp(name, "section-from-path"))
209 ctx.cfg.section_from_path = atoi(value);
177 else if (!strcmp(name, "source-filter")) 210 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 211 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 212 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 213 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 214 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 215 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 216 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 217 ctx.cfg.summary_tags = atoi(value);
218 else if (!strcmp(name, "side-by-side-diffs"))
219 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 220 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 221 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 222 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 223 ctx.cfg.renamelimit = atoi(value);
224 else if (!strcmp(name, "remove-suffix"))
225 ctx.cfg.remove_suffix = atoi(value);
189 else if (!strcmp(name, "robots")) 226 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 227 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 228 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 229 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 230 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 231 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 232 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 233 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 234 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 235 parse_configfile(expand_macros(value), config_cb);
199} 236}
200 237
201static void querystring_cb(const char *name, const char *value) 238static void querystring_cb(const char *name, const char *value)
202{ 239{
203 if (!value) 240 if (!value)
204 value = ""; 241 value = "";
@@ -206,12 +243,14 @@ static void querystring_cb(const char *name, const char *value)
206 if (!strcmp(name,"r")) { 243 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 244 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 245 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 246 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 247 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 248 } else if (!strcmp(name, "url")) {
249 if (*value == '/')
250 value++;
212 ctx.qry.url = xstrdup(value); 251 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 252 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 253 } else if (!strcmp(name, "qt")) {
215 ctx.qry.grep = xstrdup(value); 254 ctx.qry.grep = xstrdup(value);
216 } else if (!strcmp(name, "q")) { 255 } else if (!strcmp(name, "q")) {
217 ctx.qry.search = xstrdup(value); 256 ctx.qry.search = xstrdup(value);
@@ -235,12 +274,20 @@ static void querystring_cb(const char *name, const char *value)
235 } else if (!strcmp(name, "s")){ 274 } else if (!strcmp(name, "s")){
236 ctx.qry.sort = xstrdup(value); 275 ctx.qry.sort = xstrdup(value);
237 } else if (!strcmp(name, "showmsg")) { 276 } else if (!strcmp(name, "showmsg")) {
238 ctx.qry.showmsg = atoi(value); 277 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 278 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 279 ctx.qry.period = xstrdup(value);
280 } else if (!strcmp(name, "ss")) {
281 ctx.qry.ssdiff = atoi(value);
282 } else if (!strcmp(name, "all")) {
283 ctx.qry.show_all = atoi(value);
284 } else if (!strcmp(name, "context")) {
285 ctx.qry.context = atoi(value);
286 } else if (!strcmp(name, "ignorews")) {
287 ctx.qry.ignorews = atoi(value);
241 } 288 }
242} 289}
243 290
244char *xstrdupn(const char *str) 291char *xstrdupn(const char *str)
245{ 292{
246 return (str ? xstrdup(str) : NULL); 293 return (str ? xstrdup(str) : NULL);
@@ -259,29 +306,36 @@ static void prepare_context(struct cgit_context *ctx)
259 ctx->cfg.cache_root_ttl = 5; 306 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15; 307 ctx->cfg.cache_scanrc_ttl = 15;
261 ctx->cfg.cache_static_ttl = -1; 308 ctx->cfg.cache_static_ttl = -1;
262 ctx->cfg.css = "/cgit.css"; 309 ctx->cfg.css = "/cgit.css";
263 ctx->cfg.logo = "/cgit.png"; 310 ctx->cfg.logo = "/cgit.png";
264 ctx->cfg.local_time = 0; 311 ctx->cfg.local_time = 0;
312 ctx->cfg.enable_gitweb_owner = 1;
265 ctx->cfg.enable_tree_linenumbers = 1; 313 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 314 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 315 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 316 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 317 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 318 ctx->cfg.max_repodesc_len = 80;
319 ctx->cfg.max_blob_size = 0;
271 ctx->cfg.max_stats = 0; 320 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 321 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
322 ctx->cfg.project_list = NULL;
273 ctx->cfg.renamelimit = -1; 323 ctx->cfg.renamelimit = -1;
324 ctx->cfg.remove_suffix = 0;
274 ctx->cfg.robots = "index, nofollow"; 325 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 326 ctx->cfg.root_title = "Git repository browser";
276 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;
277 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 329 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = ""; 330 ctx->cfg.section = "";
279 ctx->cfg.summary_branches = 10; 331 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 332 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 333 ctx->cfg.summary_tags = 10;
334 ctx->cfg.max_atom_items = 10;
335 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 336 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 337 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 338 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 339 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 340 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 341 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
@@ -407,12 +461,18 @@ static void process_request(void *cbdata)
407 cgit_print_pageheader(ctx); 461 cgit_print_pageheader(ctx);
408 cgit_print_error("Invalid request"); 462 cgit_print_error("Invalid request");
409 cgit_print_docend(); 463 cgit_print_docend();
410 return; 464 return;
411 } 465 }
412 466
467 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
468 * in-project path limit to be made available at ctx->qry.vpath.
469 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
470 */
471 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
472
413 if (cmd->want_repo && !ctx->repo) { 473 if (cmd->want_repo && !ctx->repo) {
414 cgit_print_http_headers(ctx); 474 cgit_print_http_headers(ctx);
415 cgit_print_docstart(ctx); 475 cgit_print_docstart(ctx);
416 cgit_print_pageheader(ctx); 476 cgit_print_pageheader(ctx);
417 cgit_print_error(fmt("No repository selected")); 477 cgit_print_error(fmt("No repository selected"));
418 cgit_print_docend(); 478 cgit_print_docend();
@@ -488,12 +548,14 @@ void print_repo(FILE *f, struct cgit_repo *repo)
488 if (repo->module_link) 548 if (repo->module_link)
489 fprintf(f, "repo.module-link=%s\n", repo->module_link); 549 fprintf(f, "repo.module-link=%s\n", repo->module_link);
490 if (repo->section) 550 if (repo->section)
491 fprintf(f, "repo.section=%s\n", repo->section); 551 fprintf(f, "repo.section=%s\n", repo->section);
492 if (repo->clone_url) 552 if (repo->clone_url)
493 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);
494 fprintf(f, "repo.enable-log-filecount=%d\n", 556 fprintf(f, "repo.enable-log-filecount=%d\n",
495 repo->enable_log_filecount); 557 repo->enable_log_filecount);
496 fprintf(f, "repo.enable-log-linecount=%d\n", 558 fprintf(f, "repo.enable-log-linecount=%d\n",
497 repo->enable_log_linecount); 559 repo->enable_log_linecount);
498 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 560 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
499 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 561 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
@@ -538,12 +600,15 @@ static int generate_cached_repolist(const char *path, const char *cached_rc)
538 if (errno != EEXIST) 600 if (errno != EEXIST)
539 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 601 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
540 locked_rc, strerror(errno), errno); 602 locked_rc, strerror(errno), errno);
541 return errno; 603 return errno;
542 } 604 }
543 idx = cgit_repolist.count; 605 idx = cgit_repolist.count;
606 if (ctx.cfg.project_list)
607 scan_projects(path, ctx.cfg.project_list, repo_config);
608 else
544 scan_tree(path, repo_config); 609 scan_tree(path, repo_config);
545 print_repolist(f, &cgit_repolist, idx); 610 print_repolist(f, &cgit_repolist, idx);
546 if (rename(locked_rc, cached_rc)) 611 if (rename(locked_rc, cached_rc))
547 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 612 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
548 locked_rc, cached_rc, strerror(errno), errno); 613 locked_rc, cached_rc, strerror(errno), errno);
549 fclose(f); 614 fclose(f);
@@ -552,23 +617,31 @@ static int generate_cached_repolist(const char *path, const char *cached_rc)
552 617
553static void process_cached_repolist(const char *path) 618static void process_cached_repolist(const char *path)
554{ 619{
555 struct stat st; 620 struct stat st;
556 char *cached_rc; 621 char *cached_rc;
557 time_t age; 622 time_t age;
623 unsigned long hash;
558 624
559 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 625 hash = hash_str(path);
560 hash_str(path))); 626 if (ctx.cfg.project_list)
627 hash += hash_str(ctx.cfg.project_list);
628 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
561 629
562 if (stat(cached_rc, &st)) { 630 if (stat(cached_rc, &st)) {
563 /* Nothing is cached, we need to scan without forking. And 631 /* Nothing is cached, we need to scan without forking. And
564 * if we fail to generate a cached repolist, we need to 632 * if we fail to generate a cached repolist, we need to
565 * invoke scan_tree manually. 633 * invoke scan_tree manually.
566 */ 634 */
567 if (generate_cached_repolist(path, cached_rc)) 635 if (generate_cached_repolist(path, cached_rc)) {
636 if (ctx.cfg.project_list)
637 scan_projects(path, ctx.cfg.project_list,
638 repo_config);
639 else
568 scan_tree(path, repo_config); 640 scan_tree(path, repo_config);
641 }
569 return; 642 return;
570 } 643 }
571 644
572 parse_configfile(cached_rc, config_cb); 645 parse_configfile(cached_rc, config_cb);
573 646
574 /* If the cached configfile hasn't expired, lets exit now */ 647 /* If the cached configfile hasn't expired, lets exit now */
@@ -671,13 +744,13 @@ int main(int argc, const char **argv)
671 prepare_context(&ctx); 744 prepare_context(&ctx);
672 cgit_repolist.length = 0; 745 cgit_repolist.length = 0;
673 cgit_repolist.count = 0; 746 cgit_repolist.count = 0;
674 cgit_repolist.repos = NULL; 747 cgit_repolist.repos = NULL;
675 748
676 cgit_parse_args(argc, argv); 749 cgit_parse_args(argc, argv);
677 parse_configfile(ctx.env.cgit_config, config_cb); 750 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
678 ctx.repo = NULL; 751 ctx.repo = NULL;
679 http_parse_querystring(ctx.qry.raw, querystring_cb); 752 http_parse_querystring(ctx.qry.raw, querystring_cb);
680 753
681 /* If virtual-root isn't specified in cgitrc, lets pretend 754 /* If virtual-root isn't specified in cgitrc, lets pretend
682 * that virtual-root equals SCRIPT_NAME, minus any possibly 755 * that virtual-root equals SCRIPT_NAME, minus any possibly
683 * trailing slashes. 756 * trailing slashes.
diff --git a/cgit.css b/cgit.css
index c47ebc9..1d90057 100644
--- a/cgit.css
+++ b/cgit.css
@@ -61,13 +61,13 @@ table#header td.sub {
61 color: #777; 61 color: #777;
62 border-top: solid 1px #ccc; 62 border-top: solid 1px #ccc;
63 padding-left: 10px; 63 padding-left: 10px;
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 border-bottom: solid 3px #ccc;
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
71 width: 100%; 71 width: 100%;
72} 72}
73 73
@@ -99,16 +99,22 @@ table.tabs td.form form {
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.path {
106 margin: 0px;
107 padding: 5px 2em 2px 2em;
108 color: #000;
109 background-color: #eee;
110}
111
105div.content { 112div.content {
106 margin: 0px; 113 margin: 0px;
107 padding: 2em; 114 padding: 2em;
108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
110} 116}
111 117
112 118
113table.list { 119table.list {
114 width: 100%; 120 width: 100%;
@@ -144,27 +150,61 @@ table.list th {
144 150
145table.list td { 151table.list td {
146 border: none; 152 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
148} 154}
149 155
156table.list td.commitgraph {
157 font-family: monospace;
158 white-space: pre;
159}
160
161table.list td.commitgraph .column1 {
162 color: #a00;
163}
164
165table.list td.commitgraph .column2 {
166 color: #0a0;
167}
168
169table.list td.commitgraph .column3 {
170 color: #aa0;
171}
172
173table.list td.commitgraph .column4 {
174 color: #00a;
175}
176
177table.list td.commitgraph .column5 {
178 color: #a0a;
179}
180
181table.list td.commitgraph .column6 {
182 color: #0aa;
183}
184
150table.list td.logsubject { 185table.list td.logsubject {
151 font-family: monospace; 186 font-family: monospace;
152 font-weight: bold; 187 font-weight: bold;
153} 188}
154 189
155table.list td.logmsg { 190table.list td.logmsg {
156 font-family: monospace; 191 font-family: monospace;
157 white-space: pre; 192 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 193 padding: 0 0.5em;
159} 194}
160 195
161table.list td a { 196table.list td a {
162 color: black; 197 color: black;
163} 198}
164 199
200table.list td a.ls-dir {
201 font-weight: bold;
202 color: #00f;
203}
204
165table.list td a:hover { 205table.list td a:hover {
166 color: #00f; 206 color: #00f;
167} 207}
168 208
169img { 209img {
170 border: none; 210 border: none;
@@ -250,13 +290,13 @@ table.blob td.linenumbers {
250} 290}
251 291
252table.blob pre { 292table.blob pre {
253 padding: 0; margin: 0; 293 padding: 0; margin: 0;
254} 294}
255 295
256table.blob a.no { 296table.blob a.no, table.ssdiff a.no {
257 color: gray; 297 color: gray;
258 text-align: right; 298 text-align: right;
259 text-decoration: none; 299 text-decoration: none;
260} 300}
261 301
262table.blob a.no a:hover { 302table.blob a.no a:hover {
@@ -312,12 +352,30 @@ div.commit-subject {
312 352
313div.commit-msg { 353div.commit-msg {
314 white-space: pre; 354 white-space: pre;
315 font-family: monospace; 355 font-family: monospace;
316} 356}
317 357
358div.notes-header {
359 font-weight: bold;
360 padding-top: 1.5em;
361}
362
363div.notes {
364 white-space: pre;
365 font-family: monospace;
366 border: solid 1px #ee9;
367 background-color: #ffd;
368 padding: 0.3em 2em 0.3em 1em;
369 float: left;
370}
371
372div.notes-footer {
373 clear: left;
374}
375
318div.diffstat-header { 376div.diffstat-header {
319 font-weight: bold; 377 font-weight: bold;
320 padding-top: 1.5em; 378 padding-top: 1.5em;
321} 379}
322 380
323table.diffstat { 381table.diffstat {
@@ -517,13 +575,16 @@ a.deco {
517 margin: 0px 0.5em; 575 margin: 0px 0.5em;
518 padding: 0px 0.25em; 576 padding: 0px 0.25em;
519 background-color: #ff8888; 577 background-color: #ff8888;
520 border: solid 1px #770000; 578 border: solid 1px #770000;
521} 579}
522 580
523div.commit-subject a { 581div.commit-subject a.branch-deco,
582div.commit-subject a.tag-deco,
583div.commit-subject a.remote-deco,
584div.commit-subject a.deco {
524 margin-left: 1em; 585 margin-left: 1em;
525 font-size: 75%; 586 font-size: 75%;
526} 587}
527 588
528table.stats { 589table.stats {
529 border: solid 1px black; 590 border: solid 1px black;
@@ -598,6 +659,119 @@ table.hgraph td {
598} 659}
599 660
600table.hgraph div.bar { 661table.hgraph div.bar {
601 background-color: #eee; 662 background-color: #eee;
602 height: 1em; 663 height: 1em;
603} 664}
665
666table.ssdiff {
667 width: 100%;
668}
669
670table.ssdiff td {
671 font-size: 75%;
672 font-family: monospace;
673 white-space: pre;
674 padding: 1px 4px 1px 4px;
675 border-left: solid 1px #aaa;
676 border-right: solid 1px #aaa;
677}
678
679table.ssdiff td.add {
680 color: black;
681 background: #cfc;
682 min-width: 50%;
683}
684
685table.ssdiff td.add_dark {
686 color: black;
687 background: #aca;
688 min-width: 50%;
689}
690
691table.ssdiff span.add {
692 background: #cfc;
693 font-weight: bold;
694}
695
696table.ssdiff td.del {
697 color: black;
698 background: #fcc;
699 min-width: 50%;
700}
701
702table.ssdiff td.del_dark {
703 color: black;
704 background: #caa;
705 min-width: 50%;
706}
707
708table.ssdiff span.del {
709 background: #fcc;
710 font-weight: bold;
711}
712
713table.ssdiff td.changed {
714 color: black;
715 background: #ffc;
716 min-width: 50%;
717}
718
719table.ssdiff td.changed_dark {
720 color: black;
721 background: #cca;
722 min-width: 50%;
723}
724
725table.ssdiff td.lineno {
726 color: black;
727 background: #eee;
728 text-align: right;
729 width: 3em;
730 min-width: 3em;
731}
732
733table.ssdiff td.hunk {
734 color: #black;
735 background: #ccf;
736 border-top: solid 1px #aaa;
737 border-bottom: solid 1px #aaa;
738}
739
740table.ssdiff td.head {
741 border-top: solid 1px #aaa;
742 border-bottom: solid 1px #aaa;
743}
744
745table.ssdiff td.head div.head {
746 font-weight: bold;
747 color: black;
748}
749
750table.ssdiff td.foot {
751 border-top: solid 1px #aaa;
752 border-left: none;
753 border-right: none;
754 border-bottom: none;
755}
756
757table.ssdiff td.space {
758 border: none;
759}
760
761table.ssdiff td.space div {
762 min-height: 3em;
763}
764
765/* Syntax highlighting */
766table.blob .num { color:#2928ff; }
767table.blob .esc { color:#ff00ff; }
768table.blob .str { color:#ff0000; }
769table.blob .dstr { color:#818100; }
770table.blob .slc { color:#838183; font-style:italic; }
771table.blob .com { color:#838183; font-style:italic; }
772table.blob .dir { color:#008200; }
773table.blob .sym { color:#000000; }
774table.blob .kwa { color:#000000; font-weight:bold; }
775table.blob .kwb { color:#830000; }
776table.blob .kwc { color:#000000; font-weight:bold; }
777table.blob .kwd { color:#010181; }
diff --git a/cgit.h b/cgit.h
index 6c6c460..b5f00fc 100644
--- a/cgit.h
+++ b/cgit.h
@@ -16,12 +16,14 @@
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>
23#include <graph.h>
22 24
23 25
24/* 26/*
25 * Dateformats used on misc. pages 27 * Dateformats used on misc. pages
26 */ 28 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 29#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
@@ -66,15 +68,20 @@ struct cgit_repo {
66 char *owner; 68 char *owner;
67 char *defbranch; 69 char *defbranch;
68 char *module_link; 70 char *module_link;
69 char *readme; 71 char *readme;
70 char *section; 72 char *section;
71 char *clone_url; 73 char *clone_url;
74 char *logo;
75 char *logo_link;
72 int snapshots; 76 int snapshots;
77 int enable_commit_graph;
73 int enable_log_filecount; 78 int enable_log_filecount;
74 int enable_log_linecount; 79 int enable_log_linecount;
80 int enable_remote_branches;
81 int enable_subject_links;
75 int max_stats; 82 int max_stats;
76 time_t mtime; 83 time_t mtime;
77 struct cgit_filter *about_filter; 84 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 85 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 86 struct cgit_filter *source_filter;
80}; 87};
@@ -140,12 +147,17 @@ struct cgit_query {
140 char *url; 147 char *url;
141 char *period; 148 char *period;
142 int ofs; 149 int ofs;
143 int nohead; 150 int nohead;
144 char *sort; 151 char *sort;
145 int showmsg; 152 int showmsg;
153 int ssdiff;
154 int show_all;
155 int context;
156 int ignorews;
157 char *vpath;
146}; 158};
147 159
148struct cgit_config { 160struct cgit_config {
149 char *agefile; 161 char *agefile;
150 char *cache_root; 162 char *cache_root;
151 char *clone_prefix; 163 char *clone_prefix;
@@ -156,47 +168,60 @@ struct cgit_config {
156 char *header; 168 char *header;
157 char *index_header; 169 char *index_header;
158 char *index_info; 170 char *index_info;
159 char *logo; 171 char *logo;
160 char *logo_link; 172 char *logo_link;
161 char *module_link; 173 char *module_link;
174 char *project_list;
175 char *readme;
162 char *robots; 176 char *robots;
163 char *root_title; 177 char *root_title;
164 char *root_desc; 178 char *root_desc;
165 char *root_readme; 179 char *root_readme;
166 char *script_name; 180 char *script_name;
167 char *section; 181 char *section;
168 char *virtual_root; 182 char *virtual_root;
183 char *strict_export;
169 int cache_size; 184 int cache_size;
170 int cache_dynamic_ttl; 185 int cache_dynamic_ttl;
171 int cache_max_create_time; 186 int cache_max_create_time;
172 int cache_repo_ttl; 187 int cache_repo_ttl;
173 int cache_root_ttl; 188 int cache_root_ttl;
174 int cache_scanrc_ttl; 189 int cache_scanrc_ttl;
175 int cache_static_ttl; 190 int cache_static_ttl;
176 int embedded; 191 int embedded;
177 int enable_filter_overrides; 192 int enable_filter_overrides;
193 int enable_gitweb_owner;
178 int enable_index_links; 194 int enable_index_links;
195 int enable_commit_graph;
179 int enable_log_filecount; 196 int enable_log_filecount;
180 int enable_log_linecount; 197 int enable_log_linecount;
198 int enable_remote_branches;
199 int enable_subject_links;
181 int enable_tree_linenumbers; 200 int enable_tree_linenumbers;
182 int local_time; 201 int local_time;
202 int max_atom_items;
183 int max_repo_count; 203 int max_repo_count;
184 int max_commit_count; 204 int max_commit_count;
185 int max_lock_attempts; 205 int max_lock_attempts;
186 int max_msg_len; 206 int max_msg_len;
187 int max_repodesc_len; 207 int max_repodesc_len;
208 int max_blob_size;
188 int max_stats; 209 int max_stats;
189 int nocache; 210 int nocache;
190 int noplainemail; 211 int noplainemail;
191 int noheader; 212 int noheader;
192 int renamelimit; 213 int renamelimit;
214 int remove_suffix;
215 int scan_hidden_path;
216 int section_from_path;
193 int snapshots; 217 int snapshots;
194 int summary_branches; 218 int summary_branches;
195 int summary_log; 219 int summary_log;
196 int summary_tags; 220 int summary_tags;
221 int ssdiff;
197 struct string_list mimetypes; 222 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 223 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 224 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 225 struct cgit_filter *source_filter;
201}; 226};
202 227
@@ -265,20 +290,23 @@ extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
265 290
266extern void *cgit_free_commitinfo(struct commitinfo *info); 291extern void *cgit_free_commitinfo(struct commitinfo *info);
267 292
268extern int cgit_diff_files(const unsigned char *old_sha1, 293extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 294 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 295 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 296 int *binary, int context, int ignorews,
297 linediff_fn fn);
272 298
273extern void cgit_diff_tree(const unsigned char *old_sha1, 299extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 300 const unsigned char *new_sha1,
275 filepair_fn fn, const char *prefix); 301 filepair_fn fn, const char *prefix, int ignorews);
276 302
277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 303extern void cgit_diff_commit(struct commit *commit, filepair_fn fn,
304 const char *prefix);
278 305
306__attribute__((format (printf,1,2)))
279extern char *fmt(const char *format,...); 307extern char *fmt(const char *format,...);
280 308
281extern struct commitinfo *cgit_parse_commit(struct commit *commit); 309extern struct commitinfo *cgit_parse_commit(struct commit *commit);
282extern struct taginfo *cgit_parse_tag(struct tag *tag); 310extern struct taginfo *cgit_parse_tag(struct tag *tag);
283extern void cgit_parse_url(const char *url); 311extern void cgit_parse_url(const char *url);
284 312
@@ -288,7 +316,9 @@ extern int cgit_parse_snapshots_mask(const char *str);
288 316
289extern int cgit_open_filter(struct cgit_filter *filter); 317extern int cgit_open_filter(struct cgit_filter *filter);
290extern int cgit_close_filter(struct cgit_filter *filter); 318extern int cgit_close_filter(struct cgit_filter *filter);
291 319
292extern int readfile(const char *path, char **buf, size_t *size); 320extern int readfile(const char *path, char **buf, size_t *size);
293 321
322extern char *expand_macros(const char *txt);
323
294#endif /* CGIT_H */ 324#endif /* CGIT_H */
diff --git a/cgit.png b/cgit.png
index d7f70bc..0bdf5a7 100644
--- a/cgit.png
+++ b/cgit.png
Binary files differ
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..c3698a6 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -88,16 +88,26 @@ 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
103enable-gitweb-owner::
104 If set to "1" and scan-path is enabled, we first check each repository
105 for the git config value "gitweb.owner" to determine the owner.
106 Default value: "1". See also: scan-path.
107
98enable-index-links:: 108enable-index-links::
99 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
100 each repo in the repository index (specifically, to the "summary", 110 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 111 "commit" and "tree" pages). Default value: "0".
102 112
103enable-log-filecount:: 113enable-log-filecount::
@@ -107,12 +117,23 @@ enable-log-filecount::
107 117
108enable-log-linecount:: 118enable-log-linecount::
109 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
110 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
111 value: "0". 121 value: "0".
112 122
123enable-remote-branches::
124 Flag which, when set to "1", will make cgit display remote branches
125 in the summary and refs views. Default value: "0". See also:
126 "repo.enable-remote-branches".
127
128enable-subject-links::
129 Flag which, when set to "1", will make cgit use the subject of the
130 parent commit as link text when generating links to parent commits
131 in commit view. Default value: "0". See also:
132 "repo.enable-subject-links".
133
113enable-tree-linenumbers:: 134enable-tree-linenumbers::
114 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
115 for plaintext blobs printed in the tree view. Default value: "1". 136 for plaintext blobs printed in the tree view. Default value: "1".
116 137
117favicon:: 138favicon::
118 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
@@ -158,12 +179,16 @@ logo::
158 179
159logo-link:: 180logo-link::
160 Url loaded when clicking on the cgit logo image. If unspecified the 181 Url loaded when clicking on the cgit logo image. If unspecified the
161 calculated url of the repository index page will be used. Default 182 calculated url of the repository index page will be used. Default
162 value: none. 183 value: none.
163 184
185max-atom-items::
186 Specifies the number of items to display in atom feeds view. Default
187 value: "10".
188
164max-commit-count:: 189max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 190 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 191 value: "50".
167 192
168max-message-length:: 193max-message-length::
169 Specifies the maximum number of commit message characters to display in 194 Specifies the maximum number of commit message characters to display in
@@ -174,12 +199,16 @@ max-repo-count::
174 index page. Default value: "50". 199 index page. Default value: "50".
175 200
176max-repodesc-length:: 201max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 202 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 203 on the repository index page. Default value: "80".
179 204
205max-blob-size::
206 Specifies the maximum size of a blob to display HTML for in KBytes.
207 Default value: "0" (limit disabled).
208
180max-stats:: 209max-stats::
181 Set the default maximum statistics period. Valid values are "week", 210 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 211 "month", "quarter" and "year". If unspecified, statistics are
183 disabled. Default value: none. See also: "repo.max-stats". 212 disabled. Default value: none. See also: "repo.max-stats".
184 213
185mimetype.<ext>:: 214mimetype.<ext>::
@@ -202,12 +231,26 @@ noplainemail::
202 Default value: "0". 231 Default value: "0".
203 232
204noheader:: 233noheader::
205 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
206 on all pages. Default value: none. See also: "embedded". 235 on all pages. Default value: none. See also: "embedded".
207 236
237project-list::
238 A list of subdirectories inside of scan-path, relative to it, that
239 should loaded as git repositories. This must be defined prior to
240 scan-path. Default value: none. See also: scan-path.
241
242readme::
243 Text which will be used as default value for "repo.readme". Default
244 value: none.
245
246remove-suffix::
247 If set to "1" and scan-path is enabled, if any repositories are found
248 with a suffix of ".git", this suffix will be removed for the url and
249 name. Default value: "0". See also: scan-path.
250
208renamelimit:: 251renamelimit::
209 Maximum number of files to consider when detecting renames. The value 252 Maximum number of files to consider when detecting renames. The value
210 "-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
211 `man git-diff`). Default value: "-1". 254 `man git-diff`). Default value: "-1".
212 255
213repo.group:: 256repo.group::
@@ -228,22 +271,43 @@ root-readme::
228 value: none. 271 value: none.
229 272
230root-title:: 273root-title::
231 Text printed as heading on the repository index page. Default value: 274 Text printed as heading on the repository index page. Default value:
232 "Git Repository Browser". 275 "Git Repository Browser".
233 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
234scan-path:: 285scan-path::
235 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,
236 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
237 directory. Default value: none. See also: cache-scanrc-ttl. 288 directory. If project-list has been defined prior to scan-path,
289 scan-path loads only the directories listed in the file pointed to by
290 project-list. Default value: none. See also: cache-scanrc-ttl,
291 project-list.
238 292
239section:: 293section::
240 The name of the current repository section - all repositories defined 294 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 295 after this option will inherit the current section name. Default value:
242 none. 296 none.
243 297
298section-from-path::
299 A number which, if specified before scan-path, specifies how many
300 path elements from each repo path to use as a default section name.
301 If negative, cgit will discard the specified number of path elements
302 above the repo directory. Default value: 0.
303
304side-by-side-diffs::
305 If set to "1" shows side-by-side diffs instead of unidiffs per
306 default. Default value: "0".
307
244snapshots:: 308snapshots::
245 Text which specifies the default set of snapshot formats generated by 309 Text which specifies the default set of snapshot formats generated by
246 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
247 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 311 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
248 312
249source-filter:: 313source-filter::
@@ -263,12 +327,19 @@ summary-log::
263 "summary" view. Default value: "10". 327 "summary" view. Default value: "10".
264 328
265summary-tags:: 329summary-tags::
266 Specifies the number of tags to display in the repository "summary" 330 Specifies the number of tags to display in the repository "summary"
267 view. Default value: "10". 331 view. Default value: "10".
268 332
333strict-export::
334 Filename which, if specified, needs to be present within the repository
335 for cgit to allow access to that repository. This can be used to emulate
336 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's
337 repositories to match those exported by git-daemon. This option MUST come
338 before 'scan-path'.
339
269virtual-root:: 340virtual-root::
270 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
271 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
272 '/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
273 value: none. 344 value: none.
274 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
@@ -293,20 +364,41 @@ repo.defbranch::
293 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
294 as default instead. Default value: "master". 365 as default instead. Default value: "master".
295 366
296repo.desc:: 367repo.desc::
297 The value to show as repository description. Default value: none. 368 The value to show as repository description. Default value: none.
298 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
299repo.enable-log-filecount:: 374repo.enable-log-filecount::
300 A flag which can be used to disable the global setting 375 A flag which can be used to disable the global setting
301 `enable-log-filecount'. Default value: none. 376 `enable-log-filecount'. Default value: none.
302 377
303repo.enable-log-linecount:: 378repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 379 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 380 `enable-log-linecount'. Default value: none.
306 381
382repo.enable-remote-branches::
383 Flag which, when set to "1", will make cgit display remote branches
384 in the summary and refs views. Default value: <enable-remote-branches>.
385
386repo.enable-subject-links::
387 A flag which can be used to override the global setting
388 `enable-subject-links'. Default value: none.
389
390repo.logo::
391 Url which specifies the source of an image which will be used as a logo
392 on this repo's pages. Default value: global logo.
393
394repo.logo-link::
395 Url loaded when clicking on the cgit logo image. If unspecified the
396 calculated url of the repository index page will be used. Default
397 value: global logo-link.
398
307repo.max-stats:: 399repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 400 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 401 to the values specified for the global "max-stats" setting. Default
310 value: none. 402 value: none.
311 403
312repo.name:: 404repo.name::
@@ -319,13 +411,15 @@ repo.owner::
319repo.path:: 411repo.path::
320 An absolute path to the repository directory. For non-bare repositories 412 An absolute path to the repository directory. For non-bare repositories
321 this is the .git-directory. Default value: none. 413 this is the .git-directory. Default value: none.
322 414
323repo.readme:: 415repo.readme::
324 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
325 verbatim as the "About" page for this repo. Default value: none. 417 verbatim as the "About" page for this repo. You may also specify a
418 git refspec by head or by hash by prepending the refspec followed by
419 a colon. For example, "master:docs/readme.mkd" Default value: <readme>.
326 420
327repo.snapshots:: 421repo.snapshots::
328 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
329 "snapshots" global setting. Default value: <snapshots>. 423 "snapshots" global setting. Default value: <snapshots>.
330 424
331repo.section:: 425repo.section::
@@ -360,22 +454,26 @@ EXAMPLE CGITRC FILE
360.... 454....
361# Enable caching of up to 1000 output entriess 455# Enable caching of up to 1000 output entriess
362cache-size=1000 456cache-size=1000
363 457
364 458
365# Specify some default clone prefixes 459# Specify some default clone prefixes
366clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 460clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git
367 461
368# Specify the css url 462# Specify the css url
369css=/css/cgit.css 463css=/css/cgit.css
370 464
371 465
372# Show extra links for each repository on the index page 466# Show extra links for each repository on the index page
373enable-index-links=1 467enable-index-links=1
374 468
375 469
470# Enable ASCII art commit history graph on the log pages
471enable-commit-graph=1
472
473
376# Show number of affected files per commit on the log pages 474# Show number of affected files per commit on the log pages
377enable-log-filecount=1 475enable-log-filecount=1
378 476
379 477
380# 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
381enable-log-linecount=1 479enable-log-linecount=1
@@ -391,32 +489,32 @@ logo=/img/mylogo.png
391 489
392# Enable statistics per week, month and quarter 490# Enable statistics per week, month and quarter
393max-stats=quarter 491max-stats=quarter
394 492
395 493
396# Set the title and heading of the repository index page 494# Set the title and heading of the repository index page
397root-title=foobar.com git repositories 495root-title=example.com git repositories
398 496
399 497
400# Set a subheading for the repository index page 498# Set a subheading for the repository index page
401root-desc=tracking the foobar development 499root-desc=tracking the foobar development
402 500
403 501
404# Include some more info about foobar.com on the index page 502# Include some more info about example.com on the index page
405root-readme=/var/www/htdocs/about.html 503root-readme=/var/www/htdocs/about.html
406 504
407 505
408# Allow download of tar.gz, tar.bz2 and zip-files 506# Allow download of tar.gz, tar.bz2 and zip-files
409snapshots=tar.gz tar.bz2 zip 507snapshots=tar.gz tar.bz2 zip
410 508
411 509
412## 510##
413## List of common mimetypes 511## List of common mimetypes
414## 512##
415 513
416mimetype.git=image/git 514mimetype.gif=image/gif
417mimetype.html=text/html 515mimetype.html=text/html
418mimetype.jpg=image/jpeg 516mimetype.jpg=image/jpeg
419mimetype.jpeg=image/jpeg 517mimetype.jpeg=image/jpeg
420mimetype.pdf=application/pdf 518mimetype.pdf=application/pdf
421mimetype.png=image/png 519mimetype.png=image/png
422mimetype.svg=image/svg+xml 520mimetype.svg=image/svg+xml
@@ -432,20 +530,20 @@ mimetype.svg=image/svg+xml
432## 530##
433 531
434 532
435repo.url=foo 533repo.url=foo
436repo.path=/pub/git/foo.git 534repo.path=/pub/git/foo.git
437repo.desc=the master foo repository 535repo.desc=the master foo repository
438repo.owner=fooman@foobar.com 536repo.owner=fooman@example.com
439repo.readme=info/web/about.html 537repo.readme=info/web/about.html
440 538
441 539
442repo.url=bar 540repo.url=bar
443repo.path=/pub/git/bar.git 541repo.path=/pub/git/bar.git
444repo.desc=the bars for your foo 542repo.desc=the bars for your foo
445repo.owner=barman@foobar.com 543repo.owner=barman@example.com
446repo.readme=info/web/about.html 544repo.readme=info/web/about.html
447 545
448 546
449# The next repositories will be displayed under the 'extras' heading 547# The next repositories will be displayed under the 'extras' heading
450section=extras 548section=extras
451 549
@@ -496,6 +594,7 @@ will generate the following html element:
496 594
497 595
498 596
499AUTHOR 597AUTHOR
500------ 598------
501Lars Hjemli <hjemli@gmail.com> 599Lars Hjemli <hjemli@gmail.com>
600Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/cmd.c b/cmd.c
index 766f903..536515b 100644
--- a/cmd.c
+++ b/cmd.c
@@ -30,13 +30,13 @@ static 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, 10); 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);
@@ -48,13 +48,13 @@ static 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); 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}
@@ -64,13 +64,14 @@ static void info_fn(struct cgit_context *ctx)
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";
@@ -87,13 +88,13 @@ static 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); 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}
@@ -126,37 +127,37 @@ static void tag_fn(struct cgit_context *ctx)
126 127
127static void tree_fn(struct cgit_context *ctx) 128static void tree_fn(struct cgit_context *ctx)
128{ 129{
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 130 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 131}
131 132
132#define def_cmd(name, want_repo, want_layout) \ 133#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout} 134 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 135
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 136struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 137{
137 static struct cgit_cmd cmds[] = { 138 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 139 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0), 140 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1), 141 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0), 142 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1), 143 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1), 144 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0), 145 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1), 146 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0), 147 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0), 148 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0), 149 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0), 150 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1), 151 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0), 152 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0), 153 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1), 154 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1), 155 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1), 156 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1), 157 def_cmd(tree, 1, 1, 1),
157 }; 158 };
158 int i; 159 int i;
159 160
160 if (ctx->qry.page == NULL) { 161 if (ctx->qry.page == NULL) {
161 if (ctx->repo) 162 if (ctx->repo)
162 ctx->qry.page = "summary"; 163 ctx->qry.page = "summary";
diff --git a/cmd.h b/cmd.h
index ec9e691..8dc01bd 100644
--- a/cmd.h
+++ b/cmd.h
@@ -4,12 +4,13 @@
4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx); 4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx);
5 5
6struct cgit_cmd { 6struct cgit_cmd {
7 const char *name; 7 const char *name;
8 cgit_cmd_fn fn; 8 cgit_cmd_fn fn;
9 unsigned int want_repo:1, 9 unsigned int want_repo:1,
10 want_layout:1; 10 want_layout:1,
11 want_vpath:1;
11}; 12};
12 13
13extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); 14extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx);
14 15
15#endif /* CMD_H */ 16#endif /* CMD_H */
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
index 165a533..110c609 100755
--- a/filters/commit-links.sh
+++ b/filters/commit-links.sh
@@ -1,12 +1,14 @@
1#!/bin/sh 1#!/bin/sh
2# This script can be used to generate links in commit messages - the first 2# This script can be used to generate links in commit messages.
3# sed expression generates links to commits referenced by their SHA1, while
4# the second expression generates links to a fictional bugtracker.
5# 3#
6# To use this script, refer to this file with either the commit-filter or the 4# To use this script, refer to this file with either the commit-filter or the
7# repo.commit-filter options in cgitrc. 5# repo.commit-filter options in cgitrc.
8 6
9sed -re ' 7# This expression generates links to commits referenced by their SHA1.
10s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g 8regex=$regex'
11s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g 9s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g'
12' 10# This expression generates links to a fictional bugtracker.
11regex=$regex'
12s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g'
13
14sed -re "$regex"
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -1,11 +1,15 @@
1#!/bin/sh 1#!/bin/sh
2# This script can be used to implement syntax highlighting in the cgit 2# This script can be used to implement syntax highlighting in the cgit
3# tree-view by refering to this file with the source-filter or repo.source- 3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc. 4# filter options in cgitrc.
5# 5#
6# This script requires a shell supporting the ${var##pattern} syntax.
7# It is supported by at least dash and bash, however busybox environments
8# might have to use an external call to sed instead.
9#
6# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 10# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
7# highlighting, so you'll probably want something like the following included 11# highlighting, so you'll probably want something like the following included
8# in your css file (generated by highlight 2.4.8 and adapted for cgit): 12# in your css file (generated by highlight 2.4.8 and adapted for cgit):
9# 13#
10# table.blob .num { color:#2928ff; } 14# table.blob .num { color:#2928ff; }
11# table.blob .esc { color:#ff00ff; } 15# table.blob .esc { color:#ff00ff; }
@@ -17,23 +21,14 @@
17# table.blob .sym { color:#000000; } 21# table.blob .sym { color:#000000; }
18# table.blob .kwa { color:#000000; font-weight:bold; } 22# table.blob .kwa { color:#000000; font-weight:bold; }
19# table.blob .kwb { color:#830000; } 23# table.blob .kwb { color:#830000; }
20# table.blob .kwc { color:#000000; font-weight:bold; } 24# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; } 25# table.blob .kwd { color:#010181; }
22 26
23case "$1" in 27# store filename and extension in local vars
24 *.c) 28BASENAME="$1"
25 highlight -f -I -X -S c 29EXTENSION="${BASENAME##*.}"
26 ;; 30
27 *.h) 31# map Makefile and Makefile.* to .mk
28 highlight -f -I -X -S c 32[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk
29 ;; 33
30 *.sh) 34exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null
31 highlight -f -I -X -S sh
32 ;;
33 *.css)
34 highlight -f -I -X -S css
35 ;;
36 *)
37 highlight -f -I -X -S txt
38 ;;
39esac
diff --git a/git b/git
Subproject 87b50542a08ac6caa083ddc376e674424e37940 Subproject 7ed863a85a6ce2c4ac4476848310b8f917ab41f
diff --git a/html.c b/html.c
index 5336596..a60bc13 100644
--- a/html.c
+++ b/html.c
@@ -10,12 +10,38 @@
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!$()*,./:;@- */
17static const char* url_escape_table[256] = {
18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13",
20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d",
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",
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,
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",
27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99",
29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3",
30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad",
31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1",
33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb",
34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5",
35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9",
37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3",
38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd",
39 "%fe", "%ff"
40};
41
16int htmlfd = STDOUT_FILENO; 42int htmlfd = STDOUT_FILENO;
17 43
18char *fmt(const char *format, ...) 44char *fmt(const char *format, ...)
19{ 45{
20 static char buf[8][1024]; 46 static char buf[8][1024];
21 static int bufidx; 47 static int bufidx;
@@ -60,19 +86,19 @@ void html_status(int code, const char *msg, int more_headers)
60{ 86{
61 htmlf("Status: %d %s\n", code, msg); 87 htmlf("Status: %d %s\n", code, msg);
62 if (!more_headers) 88 if (!more_headers)
63 html("\n"); 89 html("\n");
64} 90}
65 91
66void html_txt(char *txt) 92void html_txt(const char *txt)
67{ 93{
68 char *t = txt; 94 const char *t = txt;
69 while(t && *t){ 95 while(t && *t){
70 int c = *t; 96 int c = *t;
71 if (c=='<' || c=='>' || c=='&') { 97 if (c=='<' || c=='>' || c=='&') {
72 write(htmlfd, txt, t - txt); 98 html_raw(txt, t - txt);
73 if (c=='>') 99 if (c=='>')
74 html("&gt;"); 100 html("&gt;");
75 else if (c=='<') 101 else if (c=='<')
76 html("&lt;"); 102 html("&lt;");
77 else if (c=='&') 103 else if (c=='&')
78 html("&amp;"); 104 html("&amp;");
@@ -81,42 +107,42 @@ void html_txt(char *txt)
81 t++; 107 t++;
82 } 108 }
83 if (t!=txt) 109 if (t!=txt)
84 html(txt); 110 html(txt);
85} 111}
86 112
87void html_ntxt(int len, char *txt) 113void html_ntxt(int len, const char *txt)
88{ 114{
89 char *t = txt; 115 const char *t = txt;
90 while(t && *t && len--){ 116 while(t && *t && len--){
91 int c = *t; 117 int c = *t;
92 if (c=='<' || c=='>' || c=='&') { 118 if (c=='<' || c=='>' || c=='&') {
93 write(htmlfd, txt, t - txt); 119 html_raw(txt, t - txt);
94 if (c=='>') 120 if (c=='>')
95 html("&gt;"); 121 html("&gt;");
96 else if (c=='<') 122 else if (c=='<')
97 html("&lt;"); 123 html("&lt;");
98 else if (c=='&') 124 else if (c=='&')
99 html("&amp;"); 125 html("&amp;");
100 txt = t+1; 126 txt = t+1;
101 } 127 }
102 t++; 128 t++;
103 } 129 }
104 if (t!=txt) 130 if (t!=txt)
105 write(htmlfd, txt, t - txt); 131 html_raw(txt, t - txt);
106 if (len<0) 132 if (len<0)
107 html("..."); 133 html("...");
108} 134}
109 135
110void html_attr(char *txt) 136void html_attr(const char *txt)
111{ 137{
112 char *t = txt; 138 const char *t = txt;
113 while(t && *t){ 139 while(t && *t){
114 int c = *t; 140 int c = *t;
115 if (c=='<' || c=='>' || c=='\'' || c=='\"') { 141 if (c=='<' || c=='>' || c=='\'' || c=='\"') {
116 write(htmlfd, txt, t - txt); 142 html_raw(txt, t - txt);
117 if (c=='>') 143 if (c=='>')
118 html("&gt;"); 144 html("&gt;");
119 else if (c=='<') 145 else if (c=='<')
120 html("&lt;"); 146 html("&lt;");
121 else if (c=='\'') 147 else if (c=='\'')
122 html("&#x27;"); 148 html("&#x27;");
@@ -127,66 +153,68 @@ void html_attr(char *txt)
127 t++; 153 t++;
128 } 154 }
129 if (t!=txt) 155 if (t!=txt)
130 html(txt); 156 html(txt);
131} 157}
132 158
133void html_url_path(char *txt) 159void html_url_path(const char *txt)
134{ 160{
135 char *t = txt; 161 const char *t = txt;
136 while(t && *t){ 162 while(t && *t){
137 int c = *t; 163 int c = *t;
138 if (c=='"' || c=='#' || c=='\'' || c=='?') { 164 const char *e = url_escape_table[c];
139 write(htmlfd, txt, t - txt); 165 if (e && c!='+' && c!='&' && c!='+') {
140 write(htmlfd, fmt("%%%2x", c), 3); 166 html_raw(txt, t - txt);
167 html_raw(e, 3);
141 txt = t+1; 168 txt = t+1;
142 } 169 }
143 t++; 170 t++;
144 } 171 }
145 if (t!=txt) 172 if (t!=txt)
146 html(txt); 173 html(txt);
147} 174}
148 175
149void html_url_arg(char *txt) 176void html_url_arg(const char *txt)
150{ 177{
151 char *t = txt; 178 const char *t = txt;
152 while(t && *t){ 179 while(t && *t){
153 int c = *t; 180 int c = *t;
154 if (c=='"' || c=='#' || c=='%' || c=='&' || c=='\'' || c=='+' || c=='?') { 181 const char *e = url_escape_table[c];
155 write(htmlfd, txt, t - txt); 182 if (e) {
156 write(htmlfd, fmt("%%%2x", c), 3); 183 html_raw(txt, t - txt);
184 html_raw(e, strlen(e));
157 txt = t+1; 185 txt = t+1;
158 } 186 }
159 t++; 187 t++;
160 } 188 }
161 if (t!=txt) 189 if (t!=txt)
162 html(txt); 190 html(txt);
163} 191}
164 192
165void html_hidden(char *name, char *value) 193void html_hidden(const char *name, const char *value)
166{ 194{
167 html("<input type='hidden' name='"); 195 html("<input type='hidden' name='");
168 html_attr(name); 196 html_attr(name);
169 html("' value='"); 197 html("' value='");
170 html_attr(value); 198 html_attr(value);
171 html("'/>"); 199 html("'/>");
172} 200}
173 201
174void html_option(char *value, char *text, char *selected_value) 202void html_option(const char *value, const char *text, const char *selected_value)
175{ 203{
176 html("<option value='"); 204 html("<option value='");
177 html_attr(value); 205 html_attr(value);
178 html("'"); 206 html("'");
179 if (selected_value && !strcmp(selected_value, value)) 207 if (selected_value && !strcmp(selected_value, value))
180 html(" selected='selected'"); 208 html(" selected='selected'");
181 html(">"); 209 html(">");
182 html_txt(text); 210 html_txt(text);
183 html("</option>\n"); 211 html("</option>\n");
184} 212}
185 213
186void html_link_open(char *url, char *title, char *class) 214void html_link_open(const char *url, const char *title, const char *class)
187{ 215{
188 html("<a href='"); 216 html("<a href='");
189 html_attr(url); 217 html_attr(url);
190 if (title) { 218 if (title) {
191 html("' title='"); 219 html("' title='");
192 html_attr(title); 220 html_attr(title);
@@ -218,13 +246,13 @@ int html_include(const char *filename)
218 if (!(f = fopen(filename, "r"))) { 246 if (!(f = fopen(filename, "r"))) {
219 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n", 247 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
220 filename, strerror(errno), errno); 248 filename, strerror(errno), errno);
221 return -1; 249 return -1;
222 } 250 }
223 while((len = fread(buf, 1, 4096, f)) > 0) 251 while((len = fread(buf, 1, 4096, f)) > 0)
224 write(htmlfd, buf, len); 252 html_raw(buf, len);
225 fclose(f); 253 fclose(f);
226 return 0; 254 return 0;
227} 255}
228 256
229int hextoint(char c) 257int hextoint(char c)
230{ 258{
@@ -255,20 +283,20 @@ char *convert_query_hexchar(char *txt)
255 *txt = d1 * 16 + d2; 283 *txt = d1 * 16 + d2;
256 memmove(txt+1, txt+3, n-2); 284 memmove(txt+1, txt+3, n-2);
257 return txt; 285 return txt;
258 } 286 }
259} 287}
260 288
261int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)) 289int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value))
262{ 290{
263 char *t, *value = NULL, c; 291 char *t, *txt, *value = NULL, c;
264 292
265 if (!txt) 293 if (!txt_)
266 return 0; 294 return 0;
267 295
268 t = txt = strdup(txt); 296 t = txt = strdup(txt_);
269 if (t == NULL) { 297 if (t == NULL) {
270 printf("Out of memory\n"); 298 printf("Out of memory\n");
271 exit(1); 299 exit(1);
272 } 300 }
273 while((c=*t) != '\0') { 301 while((c=*t) != '\0') {
274 if (c=='=') { 302 if (c=='=') {
diff --git a/html.h b/html.h
index a55d4b2..1135fb8 100644
--- a/html.h
+++ b/html.h
@@ -2,23 +2,26 @@
2#define HTML_H 2#define HTML_H
3 3
4extern int htmlfd; 4extern int htmlfd;
5 5
6extern void html_raw(const char *txt, size_t size); 6extern void html_raw(const char *txt, size_t size);
7extern void html(const char *txt); 7extern void html(const char *txt);
8
9__attribute__((format (printf,1,2)))
8extern void htmlf(const char *format,...); 10extern void htmlf(const char *format,...);
11
9extern void html_status(int code, const char *msg, int more_headers); 12extern void html_status(int code, const char *msg, int more_headers);
10extern void html_txt(char *txt); 13extern void html_txt(const char *txt);
11extern void html_ntxt(int len, char *txt); 14extern void html_ntxt(int len, const char *txt);
12extern void html_attr(char *txt); 15extern void html_attr(const char *txt);
13extern void html_url_path(char *txt); 16extern void html_url_path(const char *txt);
14extern void html_url_arg(char *txt); 17extern void html_url_arg(const char *txt);
15extern void html_hidden(char *name, char *value); 18extern void html_hidden(const char *name, const char *value);
16extern void html_option(char *value, char *text, char *selected_value); 19extern void html_option(const char *value, const char *text, const char *selected_value);
17extern void html_link_open(char *url, char *title, char *class); 20extern void html_link_open(const char *url, const char *title, const char *class);
18extern void html_link_close(void); 21extern void html_link_close(void);
19extern void html_fileperm(unsigned short mode); 22extern void html_fileperm(unsigned short mode);
20extern int html_include(const char *filename); 23extern int html_include(const char *filename);
21 24
22extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)); 25extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
23 26
24#endif /* HTML_H */ 27#endif /* HTML_H */
diff --git a/scan-tree.c b/scan-tree.c
index ebfd41e..627af1b 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,6 +1,15 @@
1/* scan-tree.c
2 *
3 * Copyright (C) 2008-2009 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 *
6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text)
8 */
9
1#include "cgit.h" 10#include "cgit.h"
2#include "configfile.h" 11#include "configfile.h"
3#include "html.h" 12#include "html.h"
4 13
5#define MAX_PATH 4096 14#define MAX_PATH 4096
6 15
@@ -35,93 +44,150 @@ static int is_git_dir(const char *path)
35 44
36 return 1; 45 return 1;
37} 46}
38 47
39struct cgit_repo *repo; 48struct cgit_repo *repo;
40repo_config_fn config_fn; 49repo_config_fn config_fn;
50char *owner;
41 51
42static void repo_config(const char *name, const char *value) 52static void repo_config(const char *name, const char *value)
43{ 53{
44 config_fn(repo, name, value); 54 config_fn(repo, name, value);
45} 55}
46 56
57static int git_owner_config(const char *key, const char *value, void *cb)
58{
59 if (!strcmp(key, "gitweb.owner"))
60 owner = xstrdup(value);
61 return 0;
62}
63
64static char *xstrrchr(char *s, char *from, int c)
65{
66 while (from >= s && *from != c)
67 from--;
68 return from < s ? NULL : from;
69}
70
47static void add_repo(const char *base, const char *path, repo_config_fn fn) 71static void add_repo(const char *base, const char *path, repo_config_fn fn)
48{ 72{
49 struct stat st; 73 struct stat st;
50 struct passwd *pwd; 74 struct passwd *pwd;
51 char *p; 75 char *rel, *p, *slash;
76 int n;
52 size_t size; 77 size_t size;
53 78
54 if (stat(path, &st)) { 79 if (stat(path, &st)) {
55 fprintf(stderr, "Error accessing %s: %s (%d)\n", 80 fprintf(stderr, "Error accessing %s: %s (%d)\n",
56 path, strerror(errno), errno); 81 path, strerror(errno), errno);
57 return; 82 return;
58 } 83 }
59 if ((pwd = getpwuid(st.st_uid)) == NULL) { 84
60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 85 if (ctx.cfg.strict_export && stat(fmt("%s/%s", path, ctx.cfg.strict_export), &st))
61 path, strerror(errno), errno);
62 return; 86 return;
63 } 87
88 if (!stat(fmt("%s/noweb", path), &st))
89 return;
90
91 owner = NULL;
92 if (ctx.cfg.enable_gitweb_owner)
93 git_config_from_file(git_owner_config, fmt("%s/config", path), NULL);
64 if (base == path) 94 if (base == path)
65 p = fmt("%s", path); 95 rel = xstrdup(fmt("%s", path));
66 else 96 else
67 p = fmt("%s", path + strlen(base) + 1); 97 rel = xstrdup(fmt("%s", path + strlen(base) + 1));
68 98
69 if (!strcmp(p + strlen(p) - 5, "/.git")) 99 if (!strcmp(rel + strlen(rel) - 5, "/.git"))
70 p[strlen(p) - 5] = '\0'; 100 rel[strlen(rel) - 5] = '\0';
71 101
72 repo = cgit_add_repo(xstrdup(p)); 102 repo = cgit_add_repo(rel);
103 if (ctx.cfg.remove_suffix)
104 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git"))
105 *p = '\0';
73 repo->name = repo->url; 106 repo->name = repo->url;
74 repo->path = xstrdup(path); 107 repo->path = xstrdup(path);
75 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 108 while (!owner) {
76 if (p) 109 if ((pwd = getpwuid(st.st_uid)) == NULL) {
110 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
111 path, strerror(errno), errno);
112 break;
113 }
114 if (pwd->pw_gecos)
115 if ((p = strchr(pwd->pw_gecos, ',')))
77 *p = '\0'; 116 *p = '\0';
78 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 117 owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
118 }
119 repo->owner = owner;
79 120
80 p = fmt("%s/description", path); 121 p = fmt("%s/description", path);
81 if (!stat(p, &st)) 122 if (!stat(p, &st))
82 readfile(p, &repo->desc, &size); 123 readfile(p, &repo->desc, &size);
83 124
125 if (!repo->readme) {
84 p = fmt("%s/README.html", path); 126 p = fmt("%s/README.html", path);
85 if (!stat(p, &st)) 127 if (!stat(p, &st))
86 repo->readme = xstrdup(p); 128 repo->readme = "README.html";
129 }
130 if (ctx.cfg.section_from_path) {
131 n = ctx.cfg.section_from_path;
132 if (n > 0) {
133 slash = rel;
134 while (slash && n && (slash = strchr(slash, '/')))
135 n--;
136 } else {
137 slash = rel + strlen(rel);
138 while (slash && n && (slash = xstrrchr(rel, slash, '/')))
139 n++;
140 }
141 if (slash && !n) {
142 *slash = '\0';
143 repo->section = xstrdup(rel);
144 *slash = '/';
145 if (!prefixcmp(repo->name, repo->section)) {
146 repo->name += strlen(repo->section);
147 if (*repo->name == '/')
148 repo->name++;
149 }
150 }
151 }
87 152
88 p = fmt("%s/cgitrc", path); 153 p = fmt("%s/cgitrc", path);
89 if (!stat(p, &st)) { 154 if (!stat(p, &st)) {
90 config_fn = fn; 155 config_fn = fn;
91 parse_configfile(xstrdup(p), &repo_config); 156 parse_configfile(xstrdup(p), &repo_config);
92 } 157 }
93} 158}
94 159
95static 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)
96{ 161{
97 DIR *dir; 162 DIR *dir = opendir(path);
98 struct dirent *ent; 163 struct dirent *ent;
99 char *buf; 164 char *buf;
100 struct stat st; 165 struct stat st;
101 166
167 if (!dir) {
168 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
169 path, strerror(errno), errno);
170 return;
171 }
102 if (is_git_dir(path)) { 172 if (is_git_dir(path)) {
103 add_repo(base, path, fn); 173 add_repo(base, path, fn);
104 return; 174 goto end;
105 } 175 }
106 if (is_git_dir(fmt("%s/.git", path))) { 176 if (is_git_dir(fmt("%s/.git", path))) {
107 add_repo(base, fmt("%s/.git", path), fn); 177 add_repo(base, fmt("%s/.git", path), fn);
108 return; 178 goto end;
109 }
110 dir = opendir(path);
111 if (!dir) {
112 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
113 path, strerror(errno), errno);
114 return;
115 } 179 }
116 while((ent = readdir(dir)) != NULL) { 180 while((ent = readdir(dir)) != NULL) {
117 if (ent->d_name[0] == '.') { 181 if (ent->d_name[0] == '.') {
118 if (ent->d_name[1] == '\0') 182 if (ent->d_name[1] == '\0')
119 continue; 183 continue;
120 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 184 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
121 continue; 185 continue;
186 if (!ctx.cfg.scan_hidden_path)
187 continue;
122 } 188 }
123 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 189 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
124 if (!buf) { 190 if (!buf) {
125 fprintf(stderr, "Alloc error on %s: %s (%d)\n", 191 fprintf(stderr, "Alloc error on %s: %s (%d)\n",
126 path, strerror(errno), errno); 192 path, strerror(errno), errno);
127 exit(1); 193 exit(1);
@@ -134,13 +200,42 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
134 continue; 200 continue;
135 } 201 }
136 if (S_ISDIR(st.st_mode)) 202 if (S_ISDIR(st.st_mode))
137 scan_path(base, buf, fn); 203 scan_path(base, buf, fn);
138 free(buf); 204 free(buf);
139 } 205 }
206end:
140 closedir(dir); 207 closedir(dir);
141} 208}
142 209
210#define lastc(s) s[strlen(s) - 1]
211
212void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
213{
214 char line[MAX_PATH * 2], *z;
215 FILE *projects;
216 int err;
217
218 projects = fopen(projectsfile, "r");
219 if (!projects) {
220 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
221 projectsfile, strerror(errno), errno);
222 }
223 while (fgets(line, sizeof(line), projects) != NULL) {
224 for (z = &lastc(line);
225 strlen(line) && strchr("\n\r", *z);
226 z = &lastc(line))
227 *z = '\0';
228 if (strlen(line))
229 scan_path(path, fmt("%s/%s", path, line), fn);
230 }
231 if ((err = ferror(projects))) {
232 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
233 projectsfile, strerror(err), err);
234 }
235 fclose(projects);
236}
237
143void scan_tree(const char *path, repo_config_fn fn) 238void scan_tree(const char *path, repo_config_fn fn)
144{ 239{
145 scan_path(path, path, fn); 240 scan_path(path, path, fn);
146} 241}
diff --git a/scan-tree.h b/scan-tree.h
index 11539f4..1afbd4b 100644
--- a/scan-tree.h
+++ b/scan-tree.h
@@ -1,3 +1,2 @@
1 1extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
2
3extern void scan_tree(const char *path, repo_config_fn fn); 2extern void scan_tree(const char *path, repo_config_fn fn);
diff --git a/shared.c b/shared.c
index 6adf2b6..7ec2e19 100644
--- a/shared.c
+++ b/shared.c
@@ -7,13 +7,12 @@
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd;
14 13
15int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
16{ 15{
17 if (result != 0) 16 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
19 return result; 18 return result;
@@ -54,17 +53,20 @@ struct cgit_repo *cgit_add_repo(const char *url)
54 ret->path = NULL; 53 ret->path = NULL;
55 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
56 ret->owner = NULL; 55 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 57 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
63 ret->enable_subject_links = ctx.cfg.enable_subject_links;
62 ret->max_stats = ctx.cfg.max_stats; 64 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 65 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 66 ret->readme = ctx.cfg.readme;
65 ret->mtime = -1; 67 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter; 68 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter; 69 ret->commit_filter = ctx.cfg.commit_filter;
68 ret->source_filter = ctx.cfg.source_filter; 70 ret->source_filter = ctx.cfg.source_filter;
69 return ret; 71 return ret;
70} 72}
@@ -259,13 +261,14 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
259 } 261 }
260 return 0; 262 return 0;
261} 263}
262 264
263int cgit_diff_files(const unsigned char *old_sha1, 265int cgit_diff_files(const unsigned char *old_sha1,
264 const unsigned char *new_sha1, unsigned long *old_size, 266 const unsigned char *new_sha1, unsigned long *old_size,
265 unsigned long *new_size, int *binary, linediff_fn fn) 267 unsigned long *new_size, int *binary, int context,
268 int ignorews, linediff_fn fn)
266{ 269{
267 mmfile_t file1, file2; 270 mmfile_t file1, file2;
268 xpparam_t diff_params; 271 xpparam_t diff_params;
269 xdemitconf_t emit_params; 272 xdemitconf_t emit_params;
270 xdemitcb_t emit_cb; 273 xdemitcb_t emit_cb;
271 274
@@ -286,13 +289,15 @@ int cgit_diff_files(const unsigned char *old_sha1,
286 } 289 }
287 290
288 memset(&diff_params, 0, sizeof(diff_params)); 291 memset(&diff_params, 0, sizeof(diff_params));
289 memset(&emit_params, 0, sizeof(emit_params)); 292 memset(&emit_params, 0, sizeof(emit_params));
290 memset(&emit_cb, 0, sizeof(emit_cb)); 293 memset(&emit_cb, 0, sizeof(emit_cb));
291 diff_params.flags = XDF_NEED_MINIMAL; 294 diff_params.flags = XDF_NEED_MINIMAL;
292 emit_params.ctxlen = 3; 295 if (ignorews)
296 diff_params.flags |= XDF_IGNORE_WHITESPACE;
297 emit_params.ctxlen = context > 0 ? context : 3;
293 emit_params.flags = XDL_EMIT_FUNCNAMES; 298 emit_params.flags = XDL_EMIT_FUNCNAMES;
294 emit_cb.outf = filediff_cb; 299 emit_cb.outf = filediff_cb;
295 emit_cb.priv = fn; 300 emit_cb.priv = fn;
296 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 301 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
297 if (file1.size) 302 if (file1.size)
298 free(file1.ptr); 303 free(file1.ptr);
@@ -300,23 +305,25 @@ int cgit_diff_files(const unsigned char *old_sha1,
300 free(file2.ptr); 305 free(file2.ptr);
301 return 0; 306 return 0;
302} 307}
303 308
304void cgit_diff_tree(const unsigned char *old_sha1, 309void cgit_diff_tree(const unsigned char *old_sha1,
305 const unsigned char *new_sha1, 310 const unsigned char *new_sha1,
306 filepair_fn fn, const char *prefix) 311 filepair_fn fn, const char *prefix, int ignorews)
307{ 312{
308 struct diff_options opt; 313 struct diff_options opt;
309 int ret; 314 int ret;
310 int prefixlen; 315 int prefixlen;
311 316
312 diff_setup(&opt); 317 diff_setup(&opt);
313 opt.output_format = DIFF_FORMAT_CALLBACK; 318 opt.output_format = DIFF_FORMAT_CALLBACK;
314 opt.detect_rename = 1; 319 opt.detect_rename = 1;
315 opt.rename_limit = ctx.cfg.renamelimit; 320 opt.rename_limit = ctx.cfg.renamelimit;
316 DIFF_OPT_SET(&opt, RECURSIVE); 321 DIFF_OPT_SET(&opt, RECURSIVE);
322 if (ignorews)
323 DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
317 opt.format_callback = cgit_diff_tree_cb; 324 opt.format_callback = cgit_diff_tree_cb;
318 opt.format_callback_data = fn; 325 opt.format_callback_data = fn;
319 if (prefix) { 326 if (prefix) {
320 opt.nr_paths = 1; 327 opt.nr_paths = 1;
321 opt.paths = &prefix; 328 opt.paths = &prefix;
322 prefixlen = strlen(prefix); 329 prefixlen = strlen(prefix);
@@ -329,19 +336,20 @@ void cgit_diff_tree(const unsigned char *old_sha1,
329 else 336 else
330 ret = diff_root_tree_sha1(new_sha1, "", &opt); 337 ret = diff_root_tree_sha1(new_sha1, "", &opt);
331 diffcore_std(&opt); 338 diffcore_std(&opt);
332 diff_flush(&opt); 339 diff_flush(&opt);
333} 340}
334 341
335void cgit_diff_commit(struct commit *commit, filepair_fn fn) 342void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
336{ 343{
337 unsigned char *old_sha1 = NULL; 344 unsigned char *old_sha1 = NULL;
338 345
339 if (commit->parents) 346 if (commit->parents)
340 old_sha1 = commit->parents->item->object.sha1; 347 old_sha1 = commit->parents->item->object.sha1;
341 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 348 cgit_diff_tree(old_sha1, commit->object.sha1, fn, prefix,
349 ctx.qry.ignorews);
342} 350}
343 351
344int cgit_parse_snapshots_mask(const char *str) 352int cgit_parse_snapshots_mask(const char *str)
345{ 353{
346 const struct cgit_snapshot_format *f; 354 const struct cgit_snapshot_format *f;
347 static const char *delim = " \t,:/|;"; 355 static const char *delim = " \t,:/|;";
@@ -427,6 +435,77 @@ int readfile(const char *path, char **buf, size_t *size)
427 *size = read_in_full(fd, *buf, st.st_size); 435 *size = read_in_full(fd, *buf, st.st_size);
428 e = errno; 436 e = errno;
429 (*buf)[*size] = '\0'; 437 (*buf)[*size] = '\0';
430 close(fd); 438 close(fd);
431 return (*size == st.st_size ? 0 : e); 439 return (*size == st.st_size ? 0 : e);
432} 440}
441
442int is_token_char(char c)
443{
444 return isalnum(c) || c == '_';
445}
446
447/* Replace name with getenv(name), return pointer to zero-terminating char
448 */
449char *expand_macro(char *name, int maxlength)
450{
451 char *value;
452 int len;
453
454 len = 0;
455 value = getenv(name);
456 if (value) {
457 len = strlen(value);
458 if (len > maxlength)
459 len = maxlength;
460 strncpy(name, value, len);
461 }
462 return name + len;
463}
464
465#define EXPBUFSIZE (1024 * 8)
466
467/* Replace all tokens prefixed by '$' in the specified text with the
468 * value of the named environment variable.
469 * NB: the return value is a static buffer, i.e. it must be strdup'd
470 * by the caller.
471 */
472char *expand_macros(const char *txt)
473{
474 static char result[EXPBUFSIZE];
475 char *p, *start;
476 int len;
477
478 p = result;
479 start = NULL;
480 while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
481 *p = *txt;
482 if (start) {
483 if (!is_token_char(*txt)) {
484 if (p - start > 0) {
485 *p = '\0';
486 len = result + EXPBUFSIZE - start - 1;
487 p = expand_macro(start, len) - 1;
488 }
489 start = NULL;
490 txt--;
491 }
492 p++;
493 txt++;
494 continue;
495 }
496 if (*txt == '$') {
497 start = p;
498 txt++;
499 continue;
500 }
501 p++;
502 txt++;
503 }
504 *p = '\0';
505 if (start && p - start > 0) {
506 len = result + EXPBUFSIZE - start - 1;
507 p = expand_macro(start, len);
508 *p = '\0';
509 }
510 return result;
511}
diff --git a/ui-atom.c b/ui-atom.c
index 881872c..b218456 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -21,13 +21,13 @@ void add_entry(struct commit *commit, char *host)
21 hex = sha1_to_hex(commit->object.sha1); 21 hex = sha1_to_hex(commit->object.sha1);
22 html("<entry>\n"); 22 html("<entry>\n");
23 html("<title>"); 23 html("<title>");
24 html_txt(info->subject); 24 html_txt(info->subject);
25 html("</title>\n"); 25 html("</title>\n");
26 html("<updated>"); 26 html("<updated>");
27 cgit_print_date(info->author_date, FMT_ATOMDATE, 0); 27 cgit_print_date(info->committer_date, FMT_ATOMDATE, 0);
28 html("</updated>\n"); 28 html("</updated>\n");
29 html("<author>\n"); 29 html("<author>\n");
30 if (info->author) { 30 if (info->author) {
31 html("<name>"); 31 html("<name>");
32 html_txt(info->author); 32 html_txt(info->author);
33 html("</name>\n"); 33 html("</name>\n");
@@ -82,13 +82,15 @@ void cgit_print_atom(char *tip, char *path, int max_count)
82 char *host; 82 char *host;
83 const char *argv[] = {NULL, tip, NULL, NULL, NULL}; 83 const char *argv[] = {NULL, tip, NULL, NULL, NULL};
84 struct commit *commit; 84 struct commit *commit;
85 struct rev_info rev; 85 struct rev_info rev;
86 int argc = 2; 86 int argc = 2;
87 87
88 if (!tip) 88 if (ctx.qry.show_all)
89 argv[1] = "--all";
90 else if (!tip)
89 argv[1] = ctx.qry.head; 91 argv[1] = ctx.qry.head;
90 92
91 if (path) { 93 if (path) {
92 argv[argc++] = "--"; 94 argv[argc++] = "--";
93 argv[argc++] = path; 95 argv[argc++] = path;
94 } 96 }
@@ -106,12 +108,20 @@ void cgit_print_atom(char *tip, char *path, int max_count)
106 ctx.page.mimetype = "text/xml"; 108 ctx.page.mimetype = "text/xml";
107 ctx.page.charset = "utf-8"; 109 ctx.page.charset = "utf-8";
108 cgit_print_http_headers(&ctx); 110 cgit_print_http_headers(&ctx);
109 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n"); 111 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
110 html("<title>"); 112 html("<title>");
111 html_txt(ctx.repo->name); 113 html_txt(ctx.repo->name);
114 if (path) {
115 html("/");
116 html_txt(path);
117 }
118 if (tip && !ctx.qry.show_all) {
119 html(", branch ");
120 html_txt(tip);
121 }
112 html("</title>\n"); 122 html("</title>\n");
113 html("<subtitle>"); 123 html("<subtitle>");
114 html_txt(ctx.repo->desc); 124 html_txt(ctx.repo->desc);
115 html("</subtitle>\n"); 125 html("</subtitle>\n");
116 if (host) { 126 if (host) {
117 html("<link rel='alternate' type='text/html' href='"); 127 html("<link rel='alternate' type='text/html' href='");
diff --git a/ui-blob.c b/ui-blob.c
index 89330ce..ec435e1 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -1,33 +1,66 @@
1/* ui-blob.c: show blob content 1/* ui-blob.c: show blob content
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
7 */ 8 */
8 9
9#include "cgit.h" 10#include "cgit.h"
10#include "html.h" 11#include "html.h"
11#include "ui-shared.h" 12#include "ui-shared.h"
12 13
13static char *match_path; 14static char *match_path;
14static unsigned char *matched_sha1; 15static unsigned char *matched_sha1;
16static int found_path;
15 17
16static int walk_tree(const unsigned char *sha1, const char *base,int baselen, 18static int walk_tree(const unsigned char *sha1, const char *base,int baselen,
17 const char *pathname, unsigned mode, int stage, void *cbdata) { 19 const char *pathname, unsigned mode, int stage, void *cbdata) {
18 if(strncmp(base,match_path,baselen) 20 if(strncmp(base,match_path,baselen)
19 || strcmp(match_path+baselen,pathname) ) 21 || strcmp(match_path+baselen,pathname) )
20 return READ_TREE_RECURSIVE; 22 return READ_TREE_RECURSIVE;
21 memmove(matched_sha1,sha1,20); 23 memmove(matched_sha1,sha1,20);
24 found_path = 1;
22 return 0; 25 return 0;
23} 26}
24 27
25void cgit_print_blob(const char *hex, char *path, const char *head) 28int cgit_print_file(char *path, const char *head)
26{ 29{
30 unsigned char sha1[20];
31 enum object_type type;
32 char *buf;
33 unsigned long size;
34 struct commit *commit;
35 const char *paths[] = {path, NULL};
36 if (get_sha1(head, sha1))
37 return -1;
38 type = sha1_object_info(sha1, &size);
39 if(type == OBJ_COMMIT && path) {
40 commit = lookup_commit_reference(sha1);
41 match_path = path;
42 matched_sha1 = sha1;
43 found_path = 0;
44 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
45 if (!found_path)
46 return -1;
47 type = sha1_object_info(sha1, &size);
48 }
49 if (type == OBJ_BAD)
50 return -1;
51 buf = read_sha1_file(sha1, &type, &size);
52 if (!buf)
53 return -1;
54 buf[size] = '\0';
55 html_raw(buf, size);
56 return 0;
57}
27 58
59void cgit_print_blob(const char *hex, char *path, const char *head)
60{
28 unsigned char sha1[20]; 61 unsigned char sha1[20];
29 enum object_type type; 62 enum object_type type;
30 char *buf; 63 char *buf;
31 unsigned long size; 64 unsigned long size;
32 struct commit *commit; 65 struct commit *commit;
33 const char *paths[] = {path, NULL}; 66 const char *paths[] = {path, NULL};
@@ -72,8 +105,8 @@ void cgit_print_blob(const char *hex, char *path, const char *head)
72 ctx.page.mimetype = "application/octet-stream"; 105 ctx.page.mimetype = "application/octet-stream";
73 else 106 else
74 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
75 } 108 }
76 ctx.page.filename = path; 109 ctx.page.filename = path;
77 cgit_print_http_headers(&ctx); 110 cgit_print_http_headers(&ctx);
78 write(htmlfd, buf, size); 111 html_raw(buf, size);
79} 112}
diff --git a/ui-blob.h b/ui-blob.h
index dad275a..d7e7d45 100644
--- a/ui-blob.h
+++ b/ui-blob.h
@@ -1,6 +1,7 @@
1#ifndef UI_BLOB_H 1#ifndef UI_BLOB_H
2#define UI_BLOB_H 2#define UI_BLOB_H
3 3
4extern int cgit_print_file(char *path, const char *head);
4extern void cgit_print_blob(const char *hex, char *path, const char *head); 5extern void cgit_print_blob(const char *hex, char *path, const char *head);
5 6
6#endif /* UI_BLOB_H */ 7#endif /* UI_BLOB_H */
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..2b4f677 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -9,19 +9,20 @@
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 "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 struct strbuf notes = STRBUF_INIT;
20 unsigned char sha1[20]; 21 unsigned char sha1[20];
21 char *tmp; 22 char *tmp, *tmp2;
22 int parents = 0; 23 int parents = 0;
23 24
24 if (!hex) 25 if (!hex)
25 hex = ctx.qry.head; 26 hex = ctx.qry.head;
26 27
27 if (get_sha1(hex, sha1)) { 28 if (get_sha1(hex, sha1)) {
@@ -32,12 +33,14 @@ void cgit_print_commit(char *hex)
32 if (!commit) { 33 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 34 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 35 return;
35 } 36 }
36 info = cgit_parse_commit(commit); 37 info = cgit_parse_commit(commit);
37 38
39 format_note(NULL, sha1, &notes, PAGE_ENCODING, 0);
40
38 load_ref_decorations(DECORATE_FULL_REFS); 41 load_ref_decorations(DECORATE_FULL_REFS);
39 42
40 html("<table summary='commit info' class='commit-info'>\n"); 43 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 44 html("<tr><th>author</th><td>");
42 html_txt(info->author); 45 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 46 if (!ctx.cfg.noplainemail) {
@@ -55,36 +58,49 @@ void cgit_print_commit(char *hex)
55 } 58 }
56 html("</td><td class='right'>"); 59 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 60 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 61 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 62 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 63 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 64 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
62 html(" ("); 65 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 66 cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
67 html(") (");
68 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
69 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
70 else
71 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
64 html(")</td></tr>\n"); 72 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 73 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 74 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 75 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 76 ctx.qry.head, tmp, NULL);
77 if (prefix) {
78 html(" /");
79 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
80 }
69 html("</td></tr>\n"); 81 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 82 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 83 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 84 if (!parent) {
73 html("<tr><td colspan='3'>"); 85 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 86 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 87 html("</td></tr>");
76 continue; 88 continue;
77 } 89 }
78 html("<tr><th>parent</th>" 90 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 91 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 92 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 93 if (ctx.repo->enable_subject_links) {
94 parent_info = cgit_parse_commit(parent);
95 tmp2 = parent_info->subject;
96 }
97 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
82 html(" ("); 98 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 99 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 100 sha1_to_hex(p->item->object.sha1), prefix, 0);
85 html(")</td></tr>"); 101 html(")</td></tr>");
86 parents++; 102 parents++;
87 } 103 }
88 if (ctx.repo->snapshots) { 104 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 105 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 106 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
@@ -104,15 +120,27 @@ void cgit_print_commit(char *hex)
104 if (ctx.repo->commit_filter) 120 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter); 121 cgit_open_filter(ctx.repo->commit_filter);
106 html_txt(info->msg); 122 html_txt(info->msg);
107 if (ctx.repo->commit_filter) 123 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter); 124 cgit_close_filter(ctx.repo->commit_filter);
109 html("</div>"); 125 html("</div>");
126 if (notes.len != 0) {
127 html("<div class='notes-header'>Notes</div>");
128 html("<div class='notes'>");
129 if (ctx.repo->commit_filter)
130 cgit_open_filter(ctx.repo->commit_filter);
131 html_txt(notes.buf);
132 if (ctx.repo->commit_filter)
133 cgit_close_filter(ctx.repo->commit_filter);
134 html("</div>");
135 html("<div class='notes-footer'></div>");
136 }
110 if (parents < 3) { 137 if (parents < 3) {
111 if (parents) 138 if (parents)
112 tmp = sha1_to_hex(commit->parents->item->object.sha1); 139 tmp = sha1_to_hex(commit->parents->item->object.sha1);
113 else 140 else
114 tmp = NULL; 141 tmp = NULL;
115 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 142 cgit_print_diff(ctx.qry.sha1, tmp, prefix);
116 } 143 }
144 strbuf_release(&notes);
117 cgit_free_commitinfo(info); 145 cgit_free_commitinfo(info);
118} 146}
diff --git a/ui-commit.h b/ui-commit.h
index 40bcb31..8198b4b 100644
--- a/ui-commit.h
+++ b/ui-commit.h
@@ -1,6 +1,6 @@
1#ifndef UI_COMMIT_H 1#ifndef UI_COMMIT_H
2#define UI_COMMIT_H 2#define UI_COMMIT_H
3 3
4extern void cgit_print_commit(char *hex); 4extern void cgit_print_commit(char *hex, const char *prefix);
5 5
6#endif /* UI_COMMIT_H */ 6#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..a53425d 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -6,12 +6,13 @@
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 "ui-ssdiff.h"
12 13
13unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
14unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
15 16
16static int files, slots; 17static int files, slots;
17static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
@@ -29,12 +30,24 @@ static struct fileinfo {
29 unsigned int removed; 30 unsigned int removed;
30 unsigned long old_size; 31 unsigned long old_size;
31 unsigned long new_size; 32 unsigned long new_size;
32 int binary:1; 33 int binary:1;
33} *items; 34} *items;
34 35
36static int use_ssdiff = 0;
37static struct diff_filepair *current_filepair;
38
39struct diff_filespec *cgit_get_current_old_file(void)
40{
41 return current_filepair->one;
42}
43
44struct diff_filespec *cgit_get_current_new_file(void)
45{
46 return current_filepair->two;
47}
35 48
36static void print_fileinfo(struct fileinfo *info) 49static void print_fileinfo(struct fileinfo *info)
37{ 50{
38 char *class; 51 char *class;
39 52
40 switch (info->status) { 53 switch (info->status) {
@@ -80,20 +93,20 @@ static void print_fileinfo(struct fileinfo *info)
80 html("<span class='modechange'>["); 93 html("<span class='modechange'>[");
81 cgit_print_filemode(info->old_mode); 94 cgit_print_filemode(info->old_mode);
82 html("]</span>"); 95 html("]</span>");
83 } 96 }
84 htmlf("</td><td class='%s'>", class); 97 htmlf("</td><td class='%s'>", class);
85 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 98 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86 ctx.qry.sha2, info->new_path); 99 ctx.qry.sha2, info->new_path, 0);
87 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 100 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88 htmlf(" (%s from %s)", 101 htmlf(" (%s from %s)",
89 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 102 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
90 info->old_path); 103 info->old_path);
91 html("</td><td class='right'>"); 104 html("</td><td class='right'>");
92 if (info->binary) { 105 if (info->binary) {
93 htmlf("bin</td><td class='graph'>%d -> %d bytes", 106 htmlf("bin</td><td class='graph'>%ld -> %ld bytes",
94 info->old_size, info->new_size); 107 info->old_size, info->new_size);
95 return; 108 return;
96 } 109 }
97 htmlf("%d", info->added + info->removed); 110 htmlf("%d", info->added + info->removed);
98 html("</td><td class='graph'>"); 111 html("</td><td class='graph'>");
99 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 112 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
@@ -122,13 +135,13 @@ static void inspect_filepair(struct diff_filepair *pair)
122 unsigned long old_size = 0; 135 unsigned long old_size = 0;
123 unsigned long new_size = 0; 136 unsigned long new_size = 0;
124 files++; 137 files++;
125 lines_added = 0; 138 lines_added = 0;
126 lines_removed = 0; 139 lines_removed = 0;
127 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 140 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
128 &binary, count_diff_lines); 141 &binary, 0, ctx.qry.ignorews, count_diff_lines);
129 if (files >= slots) { 142 if (files >= slots) {
130 if (slots == 0) 143 if (slots == 0)
131 slots = 4; 144 slots = 4;
132 else 145 else
133 slots = slots * 2; 146 slots = slots * 2;
134 items = xrealloc(items, slots * sizeof(struct fileinfo)); 147 items = xrealloc(items, slots * sizeof(struct fileinfo));
@@ -149,23 +162,39 @@ static void inspect_filepair(struct diff_filepair *pair)
149 max_changes = lines_added + lines_removed; 162 max_changes = lines_added + lines_removed;
150 total_adds += lines_added; 163 total_adds += lines_added;
151 total_rems += lines_removed; 164 total_rems += lines_removed;
152} 165}
153 166
154void cgit_print_diffstat(const unsigned char *old_sha1, 167void cgit_print_diffstat(const unsigned char *old_sha1,
155 const unsigned char *new_sha1) 168 const unsigned char *new_sha1, const char *prefix)
156{ 169{
157 int i; 170 int i, save_context = ctx.qry.context;
158 171
159 html("<div class='diffstat-header'>"); 172 html("<div class='diffstat-header'>");
160 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 173 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
161 ctx.qry.sha2, NULL); 174 ctx.qry.sha2, NULL, 0);
175 if (prefix)
176 htmlf(" (limited to '%s')", prefix);
177 html(" (");
178 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1;
179 cgit_self_link("more", NULL, NULL, &ctx);
180 html("/");
181 ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1;
182 cgit_self_link("less", NULL, NULL, &ctx);
183 ctx.qry.context = save_context;
184 html(" context)");
185 html(" (");
186 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
187 cgit_self_link(ctx.qry.ignorews ? "ignore" : "show", NULL, NULL, &ctx);
188 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
189 html(" whitespace changes)");
162 html("</div>"); 190 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 191 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 192 max_changes = 0;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 193 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix,
194 ctx.qry.ignorews);
166 for(i = 0; i<files; i++) 195 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 196 print_fileinfo(&items[i]);
168 html("</table>"); 197 html("</table>");
169 html("<div class='diffstat-summary'>"); 198 html("<div class='diffstat-summary'>");
170 htmlf("%d files changed, %d insertions, %d deletions", 199 htmlf("%d files changed, %d insertions, %d deletions",
171 files, total_adds, total_rems); 200 files, total_adds, total_rems);
@@ -243,33 +272,63 @@ static void header(unsigned char *sha1, char *path1, int mode1,
243 else 272 else
244 html_txt(path2); 273 html_txt(path2);
245 } 274 }
246 html("</div>"); 275 html("</div>");
247} 276}
248 277
278static void print_ssdiff_link()
279{
280 if (!strcmp(ctx.qry.page, "diff")) {
281 if (use_ssdiff)
282 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
283 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
284 else
285 cgit_diff_link("Side-by-side diff", NULL, NULL,
286 ctx.qry.head, ctx.qry.sha1,
287 ctx.qry.sha2, ctx.qry.path, 1);
288 }
289}
290
249static void filepair_cb(struct diff_filepair *pair) 291static void filepair_cb(struct diff_filepair *pair)
250{ 292{
251 unsigned long old_size = 0; 293 unsigned long old_size = 0;
252 unsigned long new_size = 0; 294 unsigned long new_size = 0;
253 int binary = 0; 295 int binary = 0;
296 linediff_fn print_line_fn = print_line;
254 297
298 current_filepair = pair;
299 if (use_ssdiff) {
300 cgit_ssdiff_header_begin();
301 print_line_fn = cgit_ssdiff_line_cb;
302 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 303 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 304 pair->two->sha1, pair->two->path, pair->two->mode);
305 if (use_ssdiff)
306 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 307 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 308 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 309 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 310 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 311 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
312 if (use_ssdiff)
313 cgit_ssdiff_footer();
262 return; 314 return;
263 } 315 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 316 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 317 &new_size, &binary, ctx.qry.context,
318 ctx.qry.ignorews, print_line_fn))
266 cgit_print_error("Error running diff"); 319 cgit_print_error("Error running diff");
267 if (binary) 320 if (binary) {
321 if (use_ssdiff)
322 html("<tr><td colspan='4'>Binary files differ</td></tr>");
323 else
268 html("Binary files differ"); 324 html("Binary files differ");
269} 325}
326 if (use_ssdiff)
327 cgit_ssdiff_footer();
328}
270 329
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 330void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
272{ 331{
273 enum object_type type; 332 enum object_type type;
274 unsigned long size; 333 unsigned long size;
275 struct commit *commit, *commit2; 334 struct commit *commit, *commit2;
@@ -300,14 +359,25 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi
300 return; 359 return;
301 } 360 }
302 commit2 = lookup_commit_reference(old_rev_sha1); 361 commit2 = lookup_commit_reference(old_rev_sha1);
303 if (!commit2 || parse_commit(commit2)) 362 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 363 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 364 }
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 365
366 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
367 use_ssdiff = 1;
368
369 print_ssdiff_link();
370 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
371
372 if (use_ssdiff) {
373 html("<table summary='ssdiff' class='ssdiff'>");
374 } else {
308 html("<table summary='diff' class='diff'>"); 375 html("<table summary='diff' class='diff'>");
309 html("<tr><td>"); 376 html("<tr><td>");
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 377 }
378 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix,
379 ctx.qry.ignorews);
380 if (!use_ssdiff)
311 html("</td></tr>"); 381 html("</td></tr>");
312 html("</table>"); 382 html("</table>");
313} 383}
diff --git a/ui-diff.h b/ui-diff.h
index 70b2926..12d0c62 100644
--- a/ui-diff.h
+++ b/ui-diff.h
@@ -4,7 +4,13 @@
4extern void cgit_print_diffstat(const unsigned char *old_sha1, 4extern void cgit_print_diffstat(const unsigned char *old_sha1,
5 const unsigned char *new_sha1); 5 const unsigned char *new_sha1);
6 6
7extern void cgit_print_diff(const char *new_hex, const char *old_hex, 7extern void cgit_print_diff(const char *new_hex, const char *old_hex,
8 const char *prefix); 8 const char *prefix);
9 9
10extern struct diff_filespec *cgit_get_current_old_file(void);
11extern struct diff_filespec *cgit_get_current_new_file(void);
12
13extern unsigned char old_rev_sha1[20];
14extern unsigned char new_rev_sha1[20];
15
10#endif /* UI_DIFF_H */ 16#endif /* UI_DIFF_H */
diff --git a/ui-log.c b/ui-log.c
index f3132c9..8add66a 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -6,15 +6,31 @@
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] == '+')
@@ -30,96 +46,210 @@ void inspect_files(struct diff_filepair *pair)
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, count_lines); 52 &new_size, &binary, 0, ctx.qry.ignorews,
53 count_lines);
37} 54}
38 55
39void show_commit_decorations(struct commit *commit) 56void show_commit_decorations(struct commit *commit)
40{ 57{
41 struct name_decoration *deco; 58 struct name_decoration *deco;
42 static char buf[1024]; 59 static char buf[1024];
43 60
44 buf[sizeof(buf) - 1] = 0; 61 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 62 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 63 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 64 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 65 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 66 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 67 ctx.qry.vpath, 0, NULL, NULL,
68 ctx.qry.showmsg);
51 } 69 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 70 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 71 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 72 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 73 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 74 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 75 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 76 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 77 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 78 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 79 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 80 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 81 sha1_to_hex(commit->object.sha1),
64 0, NULL, NULL, ctx.qry.showmsg); 82 ctx.qry.vpath, 0, NULL, NULL,
83 ctx.qry.showmsg);
65 } 84 }
66 else { 85 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 86 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 87 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 88 sha1_to_hex(commit->object.sha1),
89 ctx.qry.vpath, 0);
70 } 90 }
71 deco = deco->next; 91 deco = deco->next;
72 } 92 }
73} 93}
74 94
75void print_commit(struct commit *commit) 95void print_commit(struct commit *commit, struct rev_info *revs)
76{ 96{
77 struct commitinfo *info; 97 struct commitinfo *info;
78 char *tmp; 98 char *tmp;
79 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 }
80 120
81 info = cgit_parse_commit(commit); 121 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 122 htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
83 ctx.qry.showmsg ? " class='logheader'" : ""); 123
124 if (revs->graph) {
125 /* Print graph segment for current commit */
126 html("<td class='commitgraph'>");
127 html(graphbuf.buf);
128 html("</td>");
129 strbuf_setlen(&graphbuf, 0);
130 }
131 else {
132 html("<td>");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 133 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 134 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
86 html_link_open(tmp, NULL, NULL); 135 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 136 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 137 html_link_close();
89 htmlf("</td><td%s>", 138 html("</td>");
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 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 }
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 167 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 168 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
93 show_commit_decorations(commit); 169 show_commit_decorations(commit);
94 html("</td><td>"); 170 html("</td><td>");
95 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
96 if (ctx.repo->enable_log_filecount) { 182 if (ctx.repo->enable_log_filecount) {
97 files = 0; 183 files = 0;
98 add_lines = 0; 184 add_lines = 0;
99 rem_lines = 0; 185 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 186 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
101 html("</td><td>"); 187 html("</td><td>");
102 htmlf("%d", files); 188 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 189 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 190 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 191 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 192 }
107 } 193 }
108 html("</td></tr>\n"); 194 html("</td></tr>\n");
195
196 if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
197 html("<tr class='nohover'>");
198
109 if (ctx.qry.showmsg) { 199 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 200 /* Concatenate commit message + notes in msgbuf */
111 cols++; 201 if (info->msg && *(info->msg)) {
112 if (ctx.repo->enable_log_linecount) 202 strbuf_addstr(&msgbuf, info->msg);
113 cols++; 203 strbuf_addch(&msgbuf, '\n');
114 } 204 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 205 format_note(NULL, commit->object.sha1, &msgbuf,
116 cols); 206 PAGE_ENCODING,
117 html_txt(info->msg); 207 NOTES_SHOW_HEADER | NOTES_INDENT);
208 strbuf_addch(&msgbuf, '\n');
209 strbuf_ltrim(&msgbuf);
210 }
211
212 if (revs->graph) {
213 int lines = 0;
214
215 /* Calculate graph padding */
216 if (ctx.qry.showmsg) {
217 /* Count #lines in commit message + notes */
218 const char *p = msgbuf.buf;
219 lines = 1;
220 while ((p = strchr(p, '\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");
237 }
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);
118 html("</td></tr>\n"); 245 html("</td></tr>\n");
119 } 246 }
247
248 strbuf_release(&msgbuf);
249 strbuf_release(&graphbuf);
120 cgit_free_commitinfo(info); 250 cgit_free_commitinfo(info);
121} 251}
122 252
123static const char *disambiguate_ref(const char *ref) 253static const char *disambiguate_ref(const char *ref)
124{ 254{
125 unsigned char sha1[20]; 255 unsigned char sha1[20];
@@ -129,61 +259,126 @@ static const char *disambiguate_ref(const char *ref)
129 if (get_sha1(longref, sha1) == 0) 259 if (get_sha1(longref, sha1) == 0)
130 return longref; 260 return longref;
131 261
132 return ref; 262 return ref;
133} 263}
134 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
135void 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,
136 char *path, int pager) 288 char *path, int pager, int commit_graph)
137{ 289{
138 struct rev_info rev; 290 struct rev_info rev;
139 struct commit *commit; 291 struct commit *commit;
140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 292 struct vector vec = VECTOR_INIT(char *);
141 int argc = 2;
142 int i, columns = 3; 293 int i, columns = 3;
294 char *arg;
295
296 /* First argv is NULL */
297 vector_push(&vec, NULL, 0);
143 298
144 if (!tip) 299 if (!tip)
145 tip = ctx.qry.head; 300 tip = ctx.qry.head;
301 tip = disambiguate_ref(tip);
302 vector_push(&vec, &tip, 0);
146 303
147 argv[1] = disambiguate_ref(tip); 304 if (grep && pattern && *pattern) {
148 305 pattern = xstrdup(pattern);
149 if (grep && pattern && (!strcmp(grep, "grep") || 306 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
150 !strcmp(grep, "author") || 307 !strcmp(grep, "committer")) {
151 !strcmp(grep, "committer"))) 308 arg = fmt("--%s=%s", grep, pattern);
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 309 vector_push(&vec, &arg, 0);
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);
335 }
153 336
154 if (path) { 337 if (path) {
155 argv[argc++] = "--"; 338 arg = "--";
156 argv[argc++] = path; 339 vector_push(&vec, &arg, 0);
340 vector_push(&vec, &path, 0);
157 } 341 }
342
343 /* Make sure the vector is NULL-terminated */
344 vector_push(&vec, NULL, 0);
345 vec.count--;
346
158 init_revisions(&rev, NULL); 347 init_revisions(&rev, NULL);
159 rev.abbrev = DEFAULT_ABBREV; 348 rev.abbrev = DEFAULT_ABBREV;
160 rev.commit_format = CMIT_FMT_DEFAULT; 349 rev.commit_format = CMIT_FMT_DEFAULT;
161 rev.verbose_header = 1; 350 rev.verbose_header = 1;
162 rev.show_root_diff = 0; 351 rev.show_root_diff = 0;
163 setup_revisions(argc, argv, &rev, NULL); 352 setup_revisions(vec.count, vec.data, &rev, NULL);
164 load_ref_decorations(DECORATE_FULL_REFS); 353 load_ref_decorations(DECORATE_FULL_REFS);
165 rev.show_decorations = 1; 354 rev.show_decorations = 1;
166 rev.grep_filter.regflags |= REG_ICASE; 355 rev.grep_filter.regflags |= REG_ICASE;
167 compile_grep_patterns(&rev.grep_filter); 356 compile_grep_patterns(&rev.grep_filter);
168 prepare_revision_walk(&rev); 357 prepare_revision_walk(&rev);
169 358
170 if (pager) 359 if (pager)
171 html("<table class='list nowrap'>"); 360 html("<table class='list nowrap'>");
172 361
173 html("<tr class='nohover'><th class='left'>Age</th>" 362 html("<tr class='nohover'>");
174 "<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");
175 if (pager) { 368 if (pager) {
176 html(" ("); 369 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 370 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 371 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 372 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 373 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 374 html(")");
182 } 375 }
183 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>");
184 if (ctx.repo->enable_log_filecount) { 379 if (ctx.repo->enable_log_filecount) {
185 html("<th class='left'>Files</th>"); 380 html("<th class='left'>Files</th>");
186 columns++; 381 columns++;
187 if (ctx.repo->enable_log_linecount) { 382 if (ctx.repo->enable_log_linecount) {
188 html("<th class='left'>Lines</th>"); 383 html("<th class='left'>Lines</th>");
189 columns++; 384 columns++;
@@ -199,36 +394,35 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
199 commit->buffer = NULL; 394 commit->buffer = NULL;
200 free_commit_list(commit->parents); 395 free_commit_list(commit->parents);
201 commit->parents = NULL; 396 commit->parents = NULL;
202 } 397 }
203 398
204 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 399 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
205 print_commit(commit); 400 print_commit(commit, &rev);
206 free(commit->buffer); 401 free(commit->buffer);
207 commit->buffer = NULL; 402 commit->buffer = NULL;
208 free_commit_list(commit->parents); 403 free_commit_list(commit->parents);
209 commit->parents = NULL; 404 commit->parents = NULL;
210 } 405 }
211 if (pager) { 406 if (pager) {
212 htmlf("</table><div class='pager'>", 407 html("</table><div class='pager'>");
213 columns);
214 if (ofs > 0) { 408 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 409 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 410 ctx.qry.sha1, ctx.qry.vpath,
217 ofs - cnt, ctx.qry.grep, 411 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 412 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 413 html("&nbsp;");
220 } 414 }
221 if ((commit = get_revision(&rev)) != NULL) { 415 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 416 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 417 ctx.qry.sha1, ctx.qry.vpath,
224 ofs + cnt, ctx.qry.grep, 418 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 419 ctx.qry.search, ctx.qry.showmsg);
226 } 420 }
227 html("</div>"); 421 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 422 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 423 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 424 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
231 NULL, NULL, ctx.qry.showmsg); 425 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 426 html("</td></tr>\n");
233 } 427 }
234} 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-patch.c b/ui-patch.c
index 2a8f7a5..ca008f3 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -68,19 +68,19 @@ static void filepair_cb(struct diff_filepair *pair)
68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
69 if (S_ISGITLINK(pair->two->mode)) 69 if (S_ISGITLINK(pair->two->mode))
70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, 0, 0, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex, const char *prefix)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
84 unsigned char sha1[20], old_sha1[20]; 84 unsigned char sha1[20], old_sha1[20];
85 char *patchname; 85 char *patchname;
86 86
@@ -119,11 +119,13 @@ void cgit_print_patch(char *hex)
119 if (info->msg && *info->msg) { 119 if (info->msg && *info->msg) {
120 htmlf("%s", info->msg); 120 htmlf("%s", info->msg);
121 if (info->msg[strlen(info->msg) - 1] != '\n') 121 if (info->msg[strlen(info->msg) - 1] != '\n')
122 html("\n"); 122 html("\n");
123 } 123 }
124 html("---\n"); 124 html("---\n");
125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix, 0);
126 html("--\n"); 128 html("--\n");
127 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
128 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
129} 131}
diff --git a/ui-patch.h b/ui-patch.h
index 9f68212..1641cea 100644
--- a/ui-patch.h
+++ b/ui-patch.h
@@ -1,6 +1,6 @@
1#ifndef UI_PATCH_H 1#ifndef UI_PATCH_H
2#define UI_PATCH_H 2#define UI_PATCH_H
3 3
4extern void cgit_print_patch(char *hex); 4extern void cgit_print_patch(char *hex, const char *prefix);
5 5
6#endif /* UI_PATCH_H */ 6#endif /* UI_PATCH_H */
diff --git a/ui-plain.c b/ui-plain.c
index 5569a7c..1b2b672 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -7,14 +7,13 @@
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 12
13char *curr_rev; 13int match_baselen;
14char *match_path;
15int match; 14int match;
16 15
17static void print_object(const unsigned char *sha1, const char *path) 16static void print_object(const unsigned char *sha1, const char *path)
18{ 17{
19 enum object_type type; 18 enum object_type type;
20 char *buf, *ext; 19 char *buf, *ext;
@@ -50,23 +49,69 @@ static void print_object(const unsigned char *sha1, const char *path)
50 ctx.page.etag = sha1_to_hex(sha1); 49 ctx.page.etag = sha1_to_hex(sha1);
51 cgit_print_http_headers(&ctx); 50 cgit_print_http_headers(&ctx);
52 html_raw(buf, size); 51 html_raw(buf, size);
53 match = 1; 52 match = 1;
54} 53}
55 54
55static void print_dir(const unsigned char *sha1, const char *path,
56 const char *base)
57{
58 char *fullpath;
59 if (path[0] || base[0])
60 fullpath = fmt("/%s%s/", base, path);
61 else
62 fullpath = "/";
63 ctx.page.etag = sha1_to_hex(sha1);
64 cgit_print_http_headers(&ctx);
65 htmlf("<html><head><title>%s</title></head>\n<body>\n"
66 " <h2>%s</h2>\n <ul>\n", fullpath, fullpath);
67 if (path[0] || base[0])
68 html(" <li><a href=\"../\">../</a></li>\n");
69 match = 2;
70}
71
72static void print_dir_entry(const unsigned char *sha1, const char *path,
73 unsigned mode)
74{
75 const char *sep = "";
76 if (S_ISDIR(mode))
77 sep = "/";
78 htmlf(" <li><a href=\"%s%s\">%s%s</a></li>\n", path, sep, path, sep);
79 match = 2;
80}
81
82static void print_dir_tail(void)
83{
84 html(" </ul>\n</body></html>\n");
85}
86
56static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 87static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
57 const char *pathname, unsigned mode, int stage, 88 const char *pathname, unsigned mode, int stage,
58 void *cbdata) 89 void *cbdata)
59{ 90{
60 if (S_ISDIR(mode)) 91 if (baselen == match_baselen) {
92 if (S_ISREG(mode))
93 print_object(sha1, pathname);
94 else if (S_ISDIR(mode)) {
95 print_dir(sha1, pathname, base);
96 return READ_TREE_RECURSIVE;
97 }
98 }
99 else if (baselen > match_baselen)
100 print_dir_entry(sha1, pathname, mode);
101 else if (S_ISDIR(mode))
61 return READ_TREE_RECURSIVE; 102 return READ_TREE_RECURSIVE;
62 103
63 if (S_ISREG(mode) && !strncmp(base, match_path, baselen) && 104 return 0;
64 !strcmp(pathname, match_path + baselen)) 105}
65 print_object(sha1, pathname);
66 106
107static int basedir_len(const char *path)
108{
109 char *p = strrchr(path, '/');
110 if (p)
111 return p - path + 1;
67 return 0; 112 return 0;
68} 113}
69 114
70void cgit_print_plain(struct cgit_context *ctx) 115void cgit_print_plain(struct cgit_context *ctx)
71{ 116{
72 const char *rev = ctx->qry.sha1; 117 const char *rev = ctx->qry.sha1;
@@ -74,21 +119,28 @@ void cgit_print_plain(struct cgit_context *ctx)
74 struct commit *commit; 119 struct commit *commit;
75 const char *paths[] = {ctx->qry.path, NULL}; 120 const char *paths[] = {ctx->qry.path, NULL};
76 121
77 if (!rev) 122 if (!rev)
78 rev = ctx->qry.head; 123 rev = ctx->qry.head;
79 124
80 curr_rev = xstrdup(rev);
81 if (get_sha1(rev, sha1)) { 125 if (get_sha1(rev, sha1)) {
82 html_status(404, "Not found", 0); 126 html_status(404, "Not found", 0);
83 return; 127 return;
84 } 128 }
85 commit = lookup_commit_reference(sha1); 129 commit = lookup_commit_reference(sha1);
86 if (!commit || parse_commit(commit)) { 130 if (!commit || parse_commit(commit)) {
87 html_status(404, "Not found", 0); 131 html_status(404, "Not found", 0);
88 return; 132 return;
89 } 133 }
90 match_path = ctx->qry.path; 134 if (!paths[0]) {
135 paths[0] = "";
136 match_baselen = -1;
137 print_dir(commit->tree->object.sha1, "", "");
138 }
139 else
140 match_baselen = basedir_len(paths[0]);
91 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 141 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
92 if (!match) 142 if (!match)
93 html_status(404, "Not found", 0); 143 html_status(404, "Not found", 0);
144 else if (match == 2)
145 print_dir_tail();
94} 146}
diff --git a/ui-refs.c b/ui-refs.c
index 6571cc4..caddfbc 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -73,13 +73,13 @@ static int print_branch(struct refinfo *ref)
73 html("<tr><td>"); 73 html("<tr><td>");
74 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 74 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
75 ctx.qry.showmsg); 75 ctx.qry.showmsg);
76 html("</td><td>"); 76 html("</td><td>");
77 77
78 if (ref->object->type == OBJ_COMMIT) { 78 if (ref->object->type == OBJ_COMMIT) {
79 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 79 cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0);
80 html("</td><td>"); 80 html("</td><td>");
81 html_txt(info->author); 81 html_txt(info->author);
82 html("</td><td colspan='2'>"); 82 html("</td><td colspan='2'>");
83 cgit_print_age(info->commit->date, -1, NULL); 83 cgit_print_age(info->commit->date, -1, NULL);
84 } else { 84 } else {
85 html("</td><td></td><td>"); 85 html("</td><td></td><td>");
@@ -186,12 +186,14 @@ void cgit_print_branches(int maxcount)
186 "<th class='left'>Author</th>" 186 "<th class='left'>Author</th>"
187 "<th class='left' colspan='2'>Age</th></tr>\n"); 187 "<th class='left' colspan='2'>Age</th></tr>\n");
188 188
189 list.refs = NULL; 189 list.refs = NULL;
190 list.alloc = list.count = 0; 190 list.alloc = list.count = 0;
191 for_each_branch_ref(cgit_refs_cb, &list); 191 for_each_branch_ref(cgit_refs_cb, &list);
192 if (ctx.repo->enable_remote_branches)
193 for_each_remote_ref(cgit_refs_cb, &list);
192 194
193 if (maxcount == 0 || maxcount > list.count) 195 if (maxcount == 0 || maxcount > list.count)
194 maxcount = list.count; 196 maxcount = list.count;
195 197
196 if (maxcount < list.count) { 198 if (maxcount < list.count) {
197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 199 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
diff --git a/ui-repolist.c b/ui-repolist.c
index 0a0b6ca..2c98668 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -3,18 +3,12 @@
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/* This is needed for strcasestr to be defined by <string.h> */
10#define _GNU_SOURCE 1
11#include <string.h>
12
13#include <time.h>
14
15#include "cgit.h" 9#include "cgit.h"
16#include "html.h" 10#include "html.h"
17#include "ui-shared.h" 11#include "ui-shared.h"
18 12
19time_t read_agefile(char *path) 13time_t read_agefile(char *path)
20{ 14{
diff --git a/ui-shared.c b/ui-shared.c
index d1b020b..5aa9119 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -24,13 +24,13 @@ static char *http_date(time_t t)
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
@@ -130,13 +130,13 @@ char *cgit_currurl()
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
@@ -157,14 +157,14 @@ static void site_url(char *page, char *search, int ofs)
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(char *page, char *name, char *title, char *class, 163static void site_link(const char *page, const char *name, const char *title,
164 char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
@@ -178,20 +178,20 @@ static void site_link(char *page, char *name, char *title, char *class,
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(char *name, char *title, char *class, char *pattern, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(char *title, char *class, char *page, char *head, 190static char *repolink(const char *title, const char *class, const char *page,
191 char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
@@ -237,14 +237,15 @@ static char *repolink(char *title, char *class, char *page, char *head,
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(const char *page, const char *name, const char *title,
244 char *head, char *rev, char *path) 244 const char *class, const char *head, const char *rev,
245 const char *path)
245{ 246{
246 char *delim; 247 char *delim;
247 248
248 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
249 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { 250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
250 html(delim); 251 html(delim);
@@ -253,38 +254,39 @@ static void reporevlink(char *page, char *name, char *title, char *class,
253 } 254 }
254 html("'>"); 255 html("'>");
255 html_txt(name); 256 html_txt(name);
256 html("</a>"); 257 html("</a>");
257} 258}
258 259
259void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head)
260{ 262{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 264}
263 265
264void cgit_tag_link(char *name, char *title, char *class, char *head, 266void cgit_tag_link(const char *name, const char *title, const char *class,
265 char *rev) 267 const char *head, const char *rev)
266{ 268{
267 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
268} 270}
269 271
270void cgit_tree_link(char *name, char *title, char *class, char *head, 272void cgit_tree_link(const char *name, const char *title, const char *class,
271 char *rev, char *path) 273 const char *head, const char *rev, const char *path)
272{ 274{
273 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
274} 276}
275 277
276void cgit_plain_link(char *name, char *title, char *class, char *head, 278void cgit_plain_link(const char *name, const char *title, const char *class,
277 char *rev, char *path) 279 const char *head, const char *rev, const char *path)
278{ 280{
279 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
280} 282}
281 283
282void cgit_log_link(char *name, char *title, char *class, char *head, 284void cgit_log_link(const char *name, const char *title, const char *class,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 285 const char *head, const char *rev, const char *path,
284 int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
285{ 287{
286 char *delim; 288 char *delim;
287 289
288 delim = repolink(title, class, "log", head, path); 290 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 291 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 292 html(delim);
@@ -313,38 +315,69 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
313 } 315 }
314 html("'>"); 316 html("'>");
315 html_txt(name); 317 html_txt(name);
316 html("</a>"); 318 html("</a>");
317} 319}
318 320
319void cgit_commit_link(char *name, char *title, char *class, char *head, 321void cgit_commit_link(char *name, const char *title, const char *class,
320 char *rev) 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff)
321{ 324{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 327 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 330 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 331
332 char *delim;
333
334 delim = repolink(title, class, "commit", head, path);
335 if (rev && strcmp(rev, ctx.qry.head)) {
336 html(delim);
337 html("id=");
338 html_url_arg(rev);
339 delim = "&amp;";
340 }
341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
342 html(delim);
343 html("ss=1");
344 delim = "&amp;";
345 }
346 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
347 html(delim);
348 html("context=");
349 htmlf("%d", ctx.qry.context);
350 delim = "&amp;";
351 }
352 if (ctx.qry.ignorews) {
353 html(delim);
354 html("ignorews=1");
355 delim = "&amp;";
356 }
357 html("'>");
358 html_txt(name);
359 html("</a>");
329} 360}
330 361
331void cgit_refs_link(char *name, char *title, char *class, char *head, 362void cgit_refs_link(const char *name, const char *title, const char *class,
332 char *rev, char *path) 363 const char *head, const char *rev, const char *path)
333{ 364{
334 reporevlink("refs", name, title, class, head, rev, path); 365 reporevlink("refs", name, title, class, head, rev, path);
335} 366}
336 367
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 368void cgit_snapshot_link(const char *name, const char *title, const char *class,
338 char *rev, char *archivename) 369 const char *head, const char *rev,
370 const char *archivename)
339{ 371{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 372 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 373}
342 374
343void cgit_diff_link(char *name, char *title, char *class, char *head, 375void cgit_diff_link(const char *name, const char *title, const char *class,
344 char *new_rev, char *old_rev, char *path) 376 const char *head, const char *new_rev, const char *old_rev,
377 const char *path, int toggle_ssdiff)
345{ 378{
346 char *delim; 379 char *delim;
347 380
348 delim = repolink(title, class, "diff", head, path); 381 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 382 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 383 html(delim);
@@ -353,52 +386,127 @@ void cgit_diff_link(char *name, char *title, char *class, char *head,
353 delim = "&amp;"; 386 delim = "&amp;";
354 } 387 }
355 if (old_rev) { 388 if (old_rev) {
356 html(delim); 389 html(delim);
357 html("id2="); 390 html("id2=");
358 html_url_arg(old_rev); 391 html_url_arg(old_rev);
392 delim = "&amp;";
393 }
394 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
395 html(delim);
396 html("ss=1");
397 delim = "&amp;";
398 }
399 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
400 html(delim);
401 html("context=");
402 htmlf("%d", ctx.qry.context);
403 delim = "&amp;";
404 }
405 if (ctx.qry.ignorews) {
406 html(delim);
407 html("ignorews=1");
408 delim = "&amp;";
359 } 409 }
360 html("'>"); 410 html("'>");
361 html_txt(name); 411 html_txt(name);
362 html("</a>"); 412 html("</a>");
363} 413}
364 414
365void cgit_patch_link(char *name, char *title, char *class, char *head, 415void cgit_patch_link(const char *name, const char *title, const char *class,
366 char *rev) 416 const char *head, const char *rev, const char *path)
367{ 417{
368 reporevlink("patch", name, title, class, head, rev, NULL); 418 reporevlink("patch", name, title, class, head, rev, path);
369} 419}
370 420
371void cgit_stats_link(char *name, char *title, char *class, char *head, 421void cgit_stats_link(const char *name, const char *title, const char *class,
372 char *path) 422 const char *head, const char *path)
373{ 423{
374 reporevlink("stats", name, title, class, head, NULL, path); 424 reporevlink("stats", name, title, class, head, NULL, path);
375} 425}
376 426
427void cgit_self_link(char *name, const char *title, const char *class,
428 struct cgit_context *ctx)
429{
430 if (!strcmp(ctx->qry.page, "repolist"))
431 return cgit_index_link(name, title, class, ctx->qry.search,
432 ctx->qry.ofs);
433 else if (!strcmp(ctx->qry.page, "summary"))
434 return cgit_summary_link(name, title, class, ctx->qry.head);
435 else if (!strcmp(ctx->qry.page, "tag"))
436 return cgit_tag_link(name, title, class, ctx->qry.head,
437 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
438 else if (!strcmp(ctx->qry.page, "tree"))
439 return cgit_tree_link(name, title, class, ctx->qry.head,
440 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
441 ctx->qry.path);
442 else if (!strcmp(ctx->qry.page, "plain"))
443 return cgit_plain_link(name, title, class, ctx->qry.head,
444 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
445 ctx->qry.path);
446 else if (!strcmp(ctx->qry.page, "log"))
447 return cgit_log_link(name, title, class, ctx->qry.head,
448 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
449 ctx->qry.path, ctx->qry.ofs,
450 ctx->qry.grep, ctx->qry.search,
451 ctx->qry.showmsg);
452 else if (!strcmp(ctx->qry.page, "commit"))
453 return cgit_commit_link(name, title, class, ctx->qry.head,
454 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
455 ctx->qry.path, 0);
456 else if (!strcmp(ctx->qry.page, "patch"))
457 return cgit_patch_link(name, title, class, ctx->qry.head,
458 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
459 ctx->qry.path);
460 else if (!strcmp(ctx->qry.page, "refs"))
461 return cgit_refs_link(name, title, class, ctx->qry.head,
462 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
463 ctx->qry.path);
464 else if (!strcmp(ctx->qry.page, "snapshot"))
465 return cgit_snapshot_link(name, title, class, ctx->qry.head,
466 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
467 ctx->qry.path);
468 else if (!strcmp(ctx->qry.page, "diff"))
469 return cgit_diff_link(name, title, class, ctx->qry.head,
470 ctx->qry.sha1, ctx->qry.sha2,
471 ctx->qry.path, 0);
472 else if (!strcmp(ctx->qry.page, "stats"))
473 return cgit_stats_link(name, title, class, ctx->qry.head,
474 ctx->qry.path);
475
476 /* Don't known how to make link for this page */
477 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
478 html("><!-- cgit_self_link() doesn't know how to make link for page '");
479 html_txt(ctx->qry.page);
480 html("' -->");
481 html_txt(name);
482 html("</a>");
483}
484
377void cgit_object_link(struct object *obj) 485void cgit_object_link(struct object *obj)
378{ 486{
379 char *page, *shortrev, *fullrev, *name; 487 char *page, *shortrev, *fullrev, *name;
380 488
381 fullrev = sha1_to_hex(obj->sha1); 489 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 490 shortrev = xstrdup(fullrev);
383 shortrev[10] = '\0'; 491 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 492 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 493 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 494 ctx.qry.head, fullrev, NULL, 0);
387 return; 495 return;
388 } else if (obj->type == OBJ_TREE) 496 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 497 page = "tree";
390 else if (obj->type == OBJ_TAG) 498 else if (obj->type == OBJ_TAG)
391 page = "tag"; 499 page = "tag";
392 else 500 else
393 page = "blob"; 501 page = "blob";
394 name = fmt("%s %s...", typename(obj->type), shortrev); 502 name = fmt("%s %s...", typename(obj->type), shortrev);
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 503 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 504}
397 505
398void cgit_print_date(time_t secs, char *format, int local_time) 506void cgit_print_date(time_t secs, const char *format, int local_time)
399{ 507{
400 char buf[64]; 508 char buf[64];
401 struct tm *time; 509 struct tm *time;
402 510
403 if (!secs) 511 if (!secs)
404 return; 512 return;
@@ -407,13 +515,13 @@ void cgit_print_date(time_t secs, char *format, int local_time)
407 else 515 else
408 time = gmtime(&secs); 516 time = gmtime(&secs);
409 strftime(buf, sizeof(buf)-1, format, time); 517 strftime(buf, sizeof(buf)-1, format, time);
410 html_txt(buf); 518 html_txt(buf);
411} 519}
412 520
413void cgit_print_age(time_t t, time_t max_relative, char *format) 521void cgit_print_age(time_t t, time_t max_relative, const char *format)
414{ 522{
415 time_t now, secs; 523 time_t now, secs;
416 524
417 if (!t) 525 if (!t)
418 return; 526 return;
419 time(&now); 527 time(&now);
@@ -506,13 +614,13 @@ void cgit_print_docstart(struct cgit_context *ctx)
506 html("'/>\n"); 614 html("'/>\n");
507 } 615 }
508 if (host && ctx->repo) { 616 if (host && ctx->repo) {
509 html("<link rel='alternate' title='Atom feed' href='"); 617 html("<link rel='alternate' title='Atom feed' href='");
510 html(cgit_httpscheme()); 618 html(cgit_httpscheme());
511 html_attr(cgit_hosturl()); 619 html_attr(cgit_hosturl());
512 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 620 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
513 fmt("h=%s", ctx->qry.head))); 621 fmt("h=%s", ctx->qry.head)));
514 html("' type='application/atom+xml'/>\n"); 622 html("' type='application/atom+xml'/>\n");
515 } 623 }
516 if (ctx->cfg.head_include) 624 if (ctx->cfg.head_include)
517 html_include(ctx->cfg.head_include); 625 html_include(ctx->cfg.head_include);
518 html("</head>\n"); 626 html("</head>\n");
@@ -586,20 +694,21 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
586 html_link_open(url, NULL, "menu"); 694 html_link_open(url, NULL, "menu");
587 html_txt(strlpart(buf, 20)); 695 html_txt(strlpart(buf, 20));
588 html_link_close(); 696 html_link_close();
589 return 0; 697 return 0;
590} 698}
591 699
592void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 700void cgit_add_hidden_formfields(int incl_head, int incl_search,
701 const char *page)
593{ 702{
594 char *url; 703 char *url;
595 704
596 if (!ctx.cfg.virtual_root) { 705 if (!ctx.cfg.virtual_root) {
597 url = fmt("%s/%s", ctx.qry.repo, page); 706 url = fmt("%s/%s", ctx.qry.repo, page);
598 if (ctx.qry.path) 707 if (ctx.qry.vpath)
599 url = fmt("%s/%s", url, ctx.qry.path); 708 url = fmt("%s/%s", url, ctx.qry.vpath);
600 html_hidden("url", url); 709 html_hidden("url", url);
601 } 710 }
602 711
603 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 712 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
604 strcmp(ctx.qry.head, ctx.repo->defbranch)) 713 strcmp(ctx.qry.head, ctx.repo->defbranch))
605 html_hidden("h", ctx.qry.head); 714 html_hidden("h", ctx.qry.head);
@@ -616,32 +725,61 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
616 html_hidden("qt", ctx.qry.grep); 725 html_hidden("qt", ctx.qry.grep);
617 if (ctx.qry.search) 726 if (ctx.qry.search)
618 html_hidden("q", ctx.qry.search); 727 html_hidden("q", ctx.qry.search);
619 } 728 }
620} 729}
621 730
622const char *fallback_cmd = "repolist"; 731static const char *hc(struct cgit_context *ctx, const char *page)
732{
733 return strcmp(ctx->qry.page, page) ? NULL : "active";
734}
623 735
624char *hc(struct cgit_cmd *cmd, const char *page) 736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
625{ 737{
626 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 738 char *old_path = ctx->qry.path;
739 char *p = path, *q, *end = path + strlen(path);
740
741 ctx->qry.path = NULL;
742 cgit_self_link("root", NULL, NULL, ctx);
743 ctx->qry.path = p = path;
744 while (p < end) {
745 if (!(q = strchr(p, '/')))
746 q = end;
747 *q = '\0';
748 html_txt("/");
749 cgit_self_link(p, NULL, NULL, ctx);
750 if (q < end)
751 *q = '/';
752 p = q + 1;
753 }
754 ctx->qry.path = old_path;
627} 755}
628 756
629static void print_header(struct cgit_context *ctx) 757static void print_header(struct cgit_context *ctx)
630{ 758{
759 char *logo = NULL, *logo_link = NULL;
760
631 html("<table id='header'>\n"); 761 html("<table id='header'>\n");
632 html("<tr>\n"); 762 html("<tr>\n");
633 763
634 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 764 if (ctx->repo && ctx->repo->logo && *ctx->repo->logo)
765 logo = ctx->repo->logo;
766 else
767 logo = ctx->cfg.logo;
768 if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link)
769 logo_link = ctx->repo->logo_link;
770 else
771 logo_link = ctx->cfg.logo_link;
772 if (logo && *logo) {
635 html("<td class='logo' rowspan='2'><a href='"); 773 html("<td class='logo' rowspan='2'><a href='");
636 if (ctx->cfg.logo_link) 774 if (logo_link && *logo_link)
637 html_attr(ctx->cfg.logo_link); 775 html_attr(logo_link);
638 else 776 else
639 html_attr(cgit_rooturl()); 777 html_attr(cgit_rooturl());
640 html("'><img src='"); 778 html("'><img src='");
641 html_attr(ctx->cfg.logo); 779 html_attr(logo);
642 html("' alt='cgit logo'/></a></td>\n"); 780 html("' alt='cgit logo'/></a></td>\n");
643 } 781 }
644 782
645 html("<td class='main'>"); 783 html("<td class='main'>");
646 if (ctx->repo) { 784 if (ctx->repo) {
647 cgit_index_link("index", NULL, NULL, NULL, 0); 785 cgit_index_link("index", NULL, NULL, NULL, 0);
@@ -672,75 +810,78 @@ static void print_header(struct cgit_context *ctx)
672 } 810 }
673 html("</td></tr></table>\n"); 811 html("</td></tr></table>\n");
674} 812}
675 813
676void cgit_print_pageheader(struct cgit_context *ctx) 814void cgit_print_pageheader(struct cgit_context *ctx)
677{ 815{
678 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
679
680 if (!cmd && ctx->repo)
681 fallback_cmd = "summary";
682
683 html("<div id='cgit'>"); 816 html("<div id='cgit'>");
684 if (!ctx->cfg.noheader) 817 if (!ctx->cfg.noheader)
685 print_header(ctx); 818 print_header(ctx);
686 819
687 html("<table class='tabs'><tr><td>\n"); 820 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 821 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 822 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
690 ctx->qry.head); 823 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 824 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 825 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 826 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 827 NULL, ctx->qry.vpath, 0, NULL, NULL,
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 828 ctx->qry.showmsg);
696 ctx->qry.sha1, NULL); 829 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 830 ctx->qry.sha1, ctx->qry.vpath);
698 ctx->qry.head, ctx->qry.sha1); 831 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 832 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 833 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
834 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
701 if (ctx->repo->max_stats) 835 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 836 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
703 ctx->qry.head, NULL); 837 ctx->qry.head, ctx->qry.vpath);
704 if (ctx->repo->readme) 838 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 839 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 840 hc(ctx, "about"), ctx->qry.head, NULL,
707 NULL); 841 NULL);
708 html("</td><td class='form'>"); 842 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 843 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 844 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 845 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 846 ctx->qry.vpath, NULL));
713 html("'>\n"); 847 html("'>\n");
714 cgit_add_hidden_formfields(1, 0, "log"); 848 cgit_add_hidden_formfields(1, 0, "log");
715 html("<select name='qt'>\n"); 849 html("<select name='qt'>\n");
716 html_option("grep", "log msg", ctx->qry.grep); 850 html_option("grep", "log msg", ctx->qry.grep);
717 html_option("author", "author", ctx->qry.grep); 851 html_option("author", "author", ctx->qry.grep);
718 html_option("committer", "committer", ctx->qry.grep); 852 html_option("committer", "committer", ctx->qry.grep);
853 html_option("range", "range", ctx->qry.grep);
719 html("</select>\n"); 854 html("</select>\n");
720 html("<input class='txt' type='text' size='10' name='q' value='"); 855 html("<input class='txt' type='text' size='10' name='q' value='");
721 html_attr(ctx->qry.search); 856 html_attr(ctx->qry.search);
722 html("'/>\n"); 857 html("'/>\n");
723 html("<input type='submit' value='search'/>\n"); 858 html("<input type='submit' value='search'/>\n");
724 html("</form>\n"); 859 html("</form>\n");
725 } else { 860 } else {
726 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 861 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
727 if (ctx->cfg.root_readme) 862 if (ctx->cfg.root_readme)
728 site_link("about", "about", NULL, hc(cmd, "about"), 863 site_link("about", "about", NULL, hc(ctx, "about"),
729 NULL, 0); 864 NULL, 0);
730 html("</td><td class='form'>"); 865 html("</td><td class='form'>");
731 html("<form method='get' action='"); 866 html("<form method='get' action='");
732 html_attr(cgit_rooturl()); 867 html_attr(cgit_rooturl());
733 html("'>\n"); 868 html("'>\n");
734 html("<input type='text' name='q' size='10' value='"); 869 html("<input type='text' name='q' size='10' value='");
735 html_attr(ctx->qry.search); 870 html_attr(ctx->qry.search);
736 html("'/>\n"); 871 html("'/>\n");
737 html("<input type='submit' value='search'/>\n"); 872 html("<input type='submit' value='search'/>\n");
738 html("</form>"); 873 html("</form>");
739 } 874 }
740 html("</td></tr></table>\n"); 875 html("</td></tr></table>\n");
876 if (ctx->qry.vpath) {
877 html("<div class='path'>");
878 html("path: ");
879 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
880 html("</div>");
881 }
741 html("<div class='content'>"); 882 html("<div class='content'>");
742} 883}
743 884
744void cgit_print_filemode(unsigned short mode) 885void cgit_print_filemode(unsigned short mode)
745{ 886{
746 if (S_ISDIR(mode)) 887 if (S_ISDIR(mode))
@@ -757,17 +898,22 @@ void cgit_print_filemode(unsigned short mode)
757} 898}
758 899
759void cgit_print_snapshot_links(const char *repo, const char *head, 900void cgit_print_snapshot_links(const char *repo, const char *head,
760 const char *hex, int snapshots) 901 const char *hex, int snapshots)
761{ 902{
762 const struct cgit_snapshot_format* f; 903 const struct cgit_snapshot_format* f;
904 char *prefix;
763 char *filename; 905 char *filename;
906 unsigned char sha1[20];
764 907
908 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
909 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
910 hex++;
911 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
765 for (f = cgit_snapshot_formats; f->suffix; f++) { 912 for (f = cgit_snapshot_formats; f->suffix; f++) {
766 if (!(snapshots & f->bit)) 913 if (!(snapshots & f->bit))
767 continue; 914 continue;
768 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 915 filename = fmt("%s%s", prefix, f->suffix);
769 f->suffix);
770 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 916 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
771 html("<br/>"); 917 html("<br/>");
772 } 918 }
773} 919}
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..3cc1258 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -7,45 +7,60 @@ extern char *cgit_rooturl();
7extern char *cgit_repourl(const char *reponame); 7extern char *cgit_repourl(const char *reponame);
8extern char *cgit_fileurl(const char *reponame, const char *pagename, 8extern char *cgit_fileurl(const char *reponame, const char *pagename,
9 const char *filename, const char *query); 9 const char *filename, const char *query);
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(const char *name, const char *title,
14 char *pattern, int ofs); 14 const char *class, const char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(const char *name, const char *title,
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16 const char *class, const char *head);
17 char *rev); 17extern void cgit_tag_link(const char *name, const char *title,
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18 const char *class, const char *head,
19 char *rev, char *path); 19 const char *rev);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_tree_link(const char *name, const char *title,
21 char *rev, char *path); 21 const char *class, const char *head,
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22 const char *rev, const char *path);
23 char *rev, char *path, int ofs, char *grep, 23extern void cgit_plain_link(const char *name, const char *title,
24 char *pattern, int showmsg); 24 const char *class, const char *head,
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25 const char *rev, const char *path);
26 char *rev); 26extern void cgit_log_link(const char *name, const char *title,
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27 const char *class, const char *head, const char *rev,
28 char *rev); 28 const char *path, int ofs, const char *grep,
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29 const char *pattern, int showmsg);
30 char *rev, char *path); 30extern void cgit_commit_link(char *name, const char *title,
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31 const char *class, const char *head,
32 char *head, char *rev, char *archivename); 32 const char *rev, const char *path,
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33 int toggle_ssdiff);
34 char *new_rev, char *old_rev, char *path); 34extern void cgit_patch_link(const char *name, const char *title,
35extern void cgit_stats_link(char *name, char *title, char *class, char *head, 35 const char *class, const char *head,
36 char *path); 36 const char *rev, const char *path);
37extern void cgit_refs_link(const char *name, const char *title,
38 const char *class, const char *head,
39 const char *rev, const char *path);
40extern void cgit_snapshot_link(const char *name, const char *title,
41 const char *class, const char *head,
42 const char *rev, const char *archivename);
43extern void cgit_diff_link(const char *name, const char *title,
44 const char *class, const char *head,
45 const char *new_rev, const char *old_rev,
46 const char *path, int toggle_ssdiff);
47extern void cgit_stats_link(const char *name, const char *title,
48 const char *class, const char *head,
49 const char *path);
50extern void cgit_self_link(char *name, const char *title,
51 const char *class, struct cgit_context *ctx);
37extern void cgit_object_link(struct object *obj); 52extern void cgit_object_link(struct object *obj);
38 53
39extern void cgit_print_error(char *msg); 54extern void cgit_print_error(const char *msg);
40extern void cgit_print_date(time_t secs, char *format, int local_time); 55extern void cgit_print_date(time_t secs, const char *format, int local_time);
41extern void cgit_print_age(time_t t, time_t max_relative, char *format); 56extern void cgit_print_age(time_t t, time_t max_relative, const char *format);
42extern void cgit_print_http_headers(struct cgit_context *ctx); 57extern void cgit_print_http_headers(struct cgit_context *ctx);
43extern void cgit_print_docstart(struct cgit_context *ctx); 58extern void cgit_print_docstart(struct cgit_context *ctx);
44extern void cgit_print_docend(); 59extern void cgit_print_docend();
45extern void cgit_print_pageheader(struct cgit_context *ctx); 60extern void cgit_print_pageheader(struct cgit_context *ctx);
46extern void cgit_print_filemode(unsigned short mode); 61extern void cgit_print_filemode(unsigned short mode);
47extern void cgit_print_snapshot_links(const char *repo, const char *head, 62extern void cgit_print_snapshot_links(const char *repo, const char *head,
48 const char *hex, int snapshots); 63 const char *hex, int snapshots);
49extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 64extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
50 char *page); 65 const char *page);
51#endif /* UI_SHARED_H */ 66#endif /* UI_SHARED_H */
diff --git a/ui-snapshot.c b/ui-snapshot.c
index dbb5564..6e3412c 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -32,17 +32,23 @@ static int write_tar_gzip_archive(struct archiver_args *args)
32 32
33static int write_tar_bzip2_archive(struct archiver_args *args) 33static int write_tar_bzip2_archive(struct archiver_args *args)
34{ 34{
35 return write_compressed_tar_archive(args,"bzip2"); 35 return write_compressed_tar_archive(args,"bzip2");
36} 36}
37 37
38static int write_tar_xz_archive(struct archiver_args *args)
39{
40 return write_compressed_tar_archive(args,"xz");
41}
42
38const struct cgit_snapshot_format cgit_snapshot_formats[] = { 43const struct cgit_snapshot_format cgit_snapshot_formats[] = {
39 { ".zip", "application/x-zip", write_zip_archive, 0x1 }, 44 { ".zip", "application/x-zip", write_zip_archive, 0x01 },
40 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 }, 45 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x02 },
41 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 }, 46 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 },
42 { ".tar", "application/x-tar", write_tar_archive, 0x8 }, 47 { ".tar", "application/x-tar", write_tar_archive, 0x08 },
48 { ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 },
43 {} 49 {}
44}; 50};
45 51
46static const struct cgit_snapshot_format *get_format(const char *filename) 52static const struct cgit_snapshot_format *get_format(const char *filename)
47{ 53{
48 const struct cgit_snapshot_format *fmt; 54 const struct cgit_snapshot_format *fmt;
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
new file mode 100644
index 0000000..2481585
--- a/dev/null
+++ b/ui-ssdiff.c
@@ -0,0 +1,383 @@
1#include "cgit.h"
2#include "html.h"
3#include "ui-shared.h"
4#include "ui-diff.h"
5
6extern int use_ssdiff;
7
8static int current_old_line, current_new_line;
9
10struct deferred_lines {
11 int line_no;
12 char *line;
13 struct deferred_lines *next;
14};
15
16static struct deferred_lines *deferred_old, *deferred_old_last;
17static struct deferred_lines *deferred_new, *deferred_new_last;
18
19static char *longest_common_subsequence(char *A, char *B)
20{
21 int i, j, ri;
22 int m = strlen(A);
23 int n = strlen(B);
24 int L[m + 1][n + 1];
25 int tmp1, tmp2;
26 int lcs_length;
27 char *result;
28
29 for (i = m; i >= 0; i--) {
30 for (j = n; j >= 0; j--) {
31 if (A[i] == '\0' || B[j] == '\0') {
32 L[i][j] = 0;
33 } else if (A[i] == B[j]) {
34 L[i][j] = 1 + L[i + 1][j + 1];
35 } else {
36 tmp1 = L[i + 1][j];
37 tmp2 = L[i][j + 1];
38 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
39 }
40 }
41 }
42
43 lcs_length = L[0][0];
44 result = xmalloc(lcs_length + 2);
45 memset(result, 0, sizeof(*result) * (lcs_length + 2));
46
47 ri = 0;
48 i = 0;
49 j = 0;
50 while (i < m && j < n) {
51 if (A[i] == B[j]) {
52 result[ri] = A[i];
53 ri += 1;
54 i += 1;
55 j += 1;
56 } else if (L[i + 1][j] >= L[i][j + 1]) {
57 i += 1;
58 } else {
59 j += 1;
60 }
61 }
62 return result;
63}
64
65static int line_from_hunk(char *line, char type)
66{
67 char *buf1, *buf2;
68 int len;
69
70 buf1 = strchr(line, type);
71 if (buf1 == NULL)
72 return 0;
73 buf1 += 1;
74 buf2 = strchr(buf1, ',');
75 if (buf2 == NULL)
76 return 0;
77 len = buf2 - buf1;
78 buf2 = xmalloc(len + 1);
79 strncpy(buf2, buf1, len);
80 buf2[len] = '\0';
81 int res = atoi(buf2);
82 free(buf2);
83 return res;
84}
85
86static char *replace_tabs(char *line)
87{
88 char *prev_buf = line;
89 char *cur_buf;
90 int linelen = strlen(line);
91 int n_tabs = 0;
92 int i;
93 char *result;
94 char *spaces = " ";
95
96 if (linelen == 0) {
97 result = xmalloc(1);
98 result[0] = '\0';
99 return result;
100 }
101
102 for (i = 0; i < linelen; i++)
103 if (line[i] == '\t')
104 n_tabs += 1;
105 result = xmalloc(linelen + n_tabs * 8 + 1);
106 result[0] = '\0';
107
108 while (1) {
109 cur_buf = strchr(prev_buf, '\t');
110 if (!cur_buf) {
111 strcat(result, prev_buf);
112 break;
113 } else {
114 strcat(result, " ");
115 strncat(result, spaces, 8 - (strlen(result) % 8));
116 strncat(result, prev_buf, cur_buf - prev_buf);
117 }
118 prev_buf = cur_buf + 1;
119 }
120 return result;
121}
122
123static int calc_deferred_lines(struct deferred_lines *start)
124{
125 struct deferred_lines *item = start;
126 int result = 0;
127 while (item) {
128 result += 1;
129 item = item->next;
130 }
131 return result;
132}
133
134static void deferred_old_add(char *line, int line_no)
135{
136 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
137 item->line = xstrdup(line);
138 item->line_no = line_no;
139 item->next = NULL;
140 if (deferred_old) {
141 deferred_old_last->next = item;
142 deferred_old_last = item;
143 } else {
144 deferred_old = deferred_old_last = item;
145 }
146}
147
148static void deferred_new_add(char *line, int line_no)
149{
150 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
151 item->line = xstrdup(line);
152 item->line_no = line_no;
153 item->next = NULL;
154 if (deferred_new) {
155 deferred_new_last->next = item;
156 deferred_new_last = item;
157 } else {
158 deferred_new = deferred_new_last = item;
159 }
160}
161
162static void print_part_with_lcs(char *class, char *line, char *lcs)
163{
164 int line_len = strlen(line);
165 int i, j;
166 char c[2] = " ";
167 int same = 1;
168
169 j = 0;
170 for (i = 0; i < line_len; i++) {
171 c[0] = line[i];
172 if (same) {
173 if (line[i] == lcs[j])
174 j += 1;
175 else {
176 same = 0;
177 htmlf("<span class='%s'>", class);
178 }
179 } else if (line[i] == lcs[j]) {
180 same = 1;
181 htmlf("</span>");
182 j += 1;
183 }
184 html_txt(c);
185 }
186}
187
188static void print_ssdiff_line(char *class,
189 int old_line_no,
190 char *old_line,
191 int new_line_no,
192 char *new_line, int individual_chars)
193{
194 char *lcs = NULL;
195
196 if (old_line)
197 old_line = replace_tabs(old_line + 1);
198 if (new_line)
199 new_line = replace_tabs(new_line + 1);
200 if (individual_chars && old_line && new_line)
201 lcs = longest_common_subsequence(old_line, new_line);
202 html("<tr>\n");
203 if (old_line_no > 0) {
204 struct diff_filespec *old_file = cgit_get_current_old_file();
205 char *lineno_str = fmt("n%d", old_line_no);
206 char *id_str = fmt("%s#%s", is_null_sha1(old_file->sha1)?"HEAD":sha1_to_hex(old_rev_sha1), lineno_str);
207 html("<td class='lineno'><a class='no' href='");
208 html(cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str));
209 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
210 html("</td>");
211 htmlf("<td class='%s'>", class);
212 } else if (old_line)
213 htmlf("<td class='lineno'></td><td class='%s'>", class);
214 else
215 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
216 if (old_line) {
217 if (lcs)
218 print_part_with_lcs("del", old_line, lcs);
219 else
220 html_txt(old_line);
221 }
222
223 html("</td>\n");
224 if (new_line_no > 0) {
225 struct diff_filespec *new_file = cgit_get_current_new_file();
226 char *lineno_str = fmt("n%d", new_line_no);
227 char *id_str = fmt("%s#%s", is_null_sha1(new_file->sha1)?"HEAD":sha1_to_hex(new_rev_sha1), lineno_str);
228 html("<td class='lineno'><a class='no' href='");
229 html(cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str));
230 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
231 html("</td>");
232 htmlf("<td class='%s'>", class);
233 } else if (new_line)
234 htmlf("<td class='lineno'></td><td class='%s'>", class);
235 else
236 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
237 if (new_line) {
238 if (lcs)
239 print_part_with_lcs("add", new_line, lcs);
240 else
241 html_txt(new_line);
242 }
243
244 html("</td></tr>");
245 if (lcs)
246 free(lcs);
247 if (new_line)
248 free(new_line);
249 if (old_line)
250 free(old_line);
251}
252
253static void print_deferred_old_lines()
254{
255 struct deferred_lines *iter_old, *tmp;
256 iter_old = deferred_old;
257 while (iter_old) {
258 print_ssdiff_line("del", iter_old->line_no,
259 iter_old->line, -1, NULL, 0);
260 tmp = iter_old->next;
261 free(iter_old);
262 iter_old = tmp;
263 }
264}
265
266static void print_deferred_new_lines()
267{
268 struct deferred_lines *iter_new, *tmp;
269 iter_new = deferred_new;
270 while (iter_new) {
271 print_ssdiff_line("add", -1, NULL,
272 iter_new->line_no, iter_new->line, 0);
273 tmp = iter_new->next;
274 free(iter_new);
275 iter_new = tmp;
276 }
277}
278
279static void print_deferred_changed_lines()
280{
281 struct deferred_lines *iter_old, *iter_new, *tmp;
282 int n_old_lines = calc_deferred_lines(deferred_old);
283 int n_new_lines = calc_deferred_lines(deferred_new);
284 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
285
286 iter_old = deferred_old;
287 iter_new = deferred_new;
288 while (iter_old || iter_new) {
289 if (iter_old && iter_new)
290 print_ssdiff_line("changed", iter_old->line_no,
291 iter_old->line,
292 iter_new->line_no, iter_new->line,
293 individual_chars);
294 else if (iter_old)
295 print_ssdiff_line("changed", iter_old->line_no,
296 iter_old->line, -1, NULL, 0);
297 else if (iter_new)
298 print_ssdiff_line("changed", -1, NULL,
299 iter_new->line_no, iter_new->line, 0);
300 if (iter_old) {
301 tmp = iter_old->next;
302 free(iter_old);
303 iter_old = tmp;
304 }
305
306 if (iter_new) {
307 tmp = iter_new->next;
308 free(iter_new);
309 iter_new = tmp;
310 }
311 }
312}
313
314void cgit_ssdiff_print_deferred_lines()
315{
316 if (!deferred_old && !deferred_new)
317 return;
318 if (deferred_old && !deferred_new)
319 print_deferred_old_lines();
320 else if (!deferred_old && deferred_new)
321 print_deferred_new_lines();
322 else
323 print_deferred_changed_lines();
324 deferred_old = deferred_old_last = NULL;
325 deferred_new = deferred_new_last = NULL;
326}
327
328/*
329 * print a single line returned from xdiff
330 */
331void cgit_ssdiff_line_cb(char *line, int len)
332{
333 char c = line[len - 1];
334 line[len - 1] = '\0';
335 if (line[0] == '@') {
336 current_old_line = line_from_hunk(line, '-');
337 current_new_line = line_from_hunk(line, '+');
338 }
339
340 if (line[0] == ' ') {
341 if (deferred_old || deferred_new)
342 cgit_ssdiff_print_deferred_lines();
343 print_ssdiff_line("ctx", current_old_line, line,
344 current_new_line, line, 0);
345 current_old_line += 1;
346 current_new_line += 1;
347 } else if (line[0] == '+') {
348 deferred_new_add(line, current_new_line);
349 current_new_line += 1;
350 } else if (line[0] == '-') {
351 deferred_old_add(line, current_old_line);
352 current_old_line += 1;
353 } else if (line[0] == '@') {
354 html("<tr><td colspan='4' class='hunk'>");
355 html_txt(line);
356 html("</td></tr>");
357 } else {
358 html("<tr><td colspan='4' class='ctx'>");
359 html_txt(line);
360 html("</td></tr>");
361 }
362 line[len - 1] = c;
363}
364
365void cgit_ssdiff_header_begin()
366{
367 current_old_line = -1;
368 current_new_line = -1;
369 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
370 html("<tr><td class='head' colspan='4'>");
371}
372
373void cgit_ssdiff_header_end()
374{
375 html("</td><tr>");
376}
377
378void cgit_ssdiff_footer()
379{
380 if (deferred_old || deferred_new)
381 cgit_ssdiff_print_deferred_lines();
382 html("<tr><td class='foot' colspan='4'></td></tr>");
383}
diff --git a/ui-ssdiff.h b/ui-ssdiff.h
new file mode 100644
index 0000000..64b4b12
--- a/dev/null
+++ b/ui-ssdiff.h
@@ -0,0 +1,13 @@
1#ifndef UI_SSDIFF_H
2#define UI_SSDIFF_H
3
4extern void cgit_ssdiff_print_deferred_lines();
5
6extern void cgit_ssdiff_line_cb(char *line, int len);
7
8extern void cgit_ssdiff_header_begin();
9extern void cgit_ssdiff_header_end();
10
11extern void cgit_ssdiff_footer();
12
13#endif /* UI_SSDIFF_H */
diff --git a/ui-stats.c b/ui-stats.c
index 50c2540..2a0c174 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -1,13 +1,17 @@
1#include <string-list.h>
2
3#include "cgit.h" 1#include "cgit.h"
4#include "html.h" 2#include "html.h"
5#include "ui-shared.h" 3#include "ui-shared.h"
6#include "ui-stats.h" 4#include "ui-stats.h"
7 5
6#ifdef NO_C99_FORMAT
7#define SZ_FMT "%u"
8#else
9#define SZ_FMT "%zu"
10#endif
11
8#define MONTHS 6 12#define MONTHS 6
9 13
10struct authorstat { 14struct authorstat {
11 long total; 15 long total;
12 struct string_list list; 16 struct string_list list;
13}; 17};
@@ -280,16 +284,16 @@ void print_combined_authorrow(struct string_list *authors, int from, int to,
280 authorstat = author->util; 284 authorstat = author->util;
281 items = &authorstat->list; 285 items = &authorstat->list;
282 date = string_list_lookup(items, tmp); 286 date = string_list_lookup(items, tmp);
283 if (date) 287 if (date)
284 subtotal += (size_t)date->util; 288 subtotal += (size_t)date->util;
285 } 289 }
286 htmlf("<td class='%s'>%d</td>", centerclass, subtotal); 290 htmlf("<td class='%s'>%ld</td>", centerclass, subtotal);
287 total += subtotal; 291 total += subtotal;
288 } 292 }
289 htmlf("<td class='%s'>%d</td></tr>", rightclass, total); 293 htmlf("<td class='%s'>%ld</td></tr>", rightclass, total);
290} 294}
291 295
292void print_authors(struct string_list *authors, int top, 296void print_authors(struct string_list *authors, int top,
293 struct cgit_period *period) 297 struct cgit_period *period)
294{ 298{
295 struct string_list_item *author; 299 struct string_list_item *author;
@@ -332,22 +336,22 @@ void print_authors(struct string_list *authors, int top,
332 tmp = period->pretty(tm); 336 tmp = period->pretty(tm);
333 period->inc(tm); 337 period->inc(tm);
334 date = string_list_lookup(items, tmp); 338 date = string_list_lookup(items, tmp);
335 if (!date) 339 if (!date)
336 html("<td>0</td>"); 340 html("<td>0</td>");
337 else { 341 else {
338 htmlf("<td>%d</td>", date->util); 342 htmlf("<td>"SZ_FMT"</td>", (size_t)date->util);
339 total += (size_t)date->util; 343 total += (size_t)date->util;
340 } 344 }
341 } 345 }
342 htmlf("<td class='sum'>%d</td></tr>", total); 346 htmlf("<td class='sum'>%ld</td></tr>", total);
343 } 347 }
344 348
345 if (top < authors->nr) 349 if (top < authors->nr)
346 print_combined_authorrow(authors, top, authors->nr - 1, 350 print_combined_authorrow(authors, top, authors->nr - 1,
347 "Others (%d)", "left", "", "sum", period); 351 "Others (%ld)", "left", "", "sum", period);
348 352
349 print_combined_authorrow(authors, 0, authors->nr - 1, "Total", 353 print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
350 "total", "sum", "sum", period); 354 "total", "sum", "sum", period);
351 html("</table>"); 355 html("</table>");
352} 356}
353 357
@@ -364,13 +368,13 @@ void cgit_show_stats(struct cgit_context *ctx)
364 368
365 if (ctx->qry.period) 369 if (ctx->qry.period)
366 code = ctx->qry.period; 370 code = ctx->qry.period;
367 371
368 i = cgit_find_stats_period(code, &period); 372 i = cgit_find_stats_period(code, &period);
369 if (!i) { 373 if (!i) {
370 cgit_print_error(fmt("Unknown statistics type: %c", code)); 374 cgit_print_error(fmt("Unknown statistics type: %c", code[0]));
371 return; 375 return;
372 } 376 }
373 if (i > ctx->repo->max_stats) { 377 if (i > ctx->repo->max_stats) {
374 cgit_print_error(fmt("Statistics type disabled: %s", 378 cgit_print_error(fmt("Statistics type disabled: %s",
375 period->name)); 379 period->name));
376 return; 380 return;
diff --git a/ui-summary.c b/ui-summary.c
index a2c018e..5be2545 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -1,18 +1,20 @@
1/* ui-summary.c: functions for generating repo summary page 1/* ui-summary.c: functions for generating repo summary page
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
7 */ 8 */
8 9
9#include "cgit.h" 10#include "cgit.h"
10#include "html.h" 11#include "html.h"
11#include "ui-log.h" 12#include "ui-log.h"
12#include "ui-refs.h" 13#include "ui-refs.h"
14#include "ui-blob.h"
13 15
14int urls = 0; 16int urls = 0;
15 17
16static void print_url(char *base, char *suffix) 18static void print_url(char *base, char *suffix)
17{ 19{
18 if (!base || !*base) 20 if (!base || !*base)
@@ -54,39 +56,69 @@ void cgit_print_summary()
54 cgit_print_branches(ctx.cfg.summary_branches); 56 cgit_print_branches(ctx.cfg.summary_branches);
55 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
56 cgit_print_tags(ctx.cfg.summary_tags); 58 cgit_print_tags(ctx.cfg.summary_tags);
57 if (ctx.cfg.summary_log > 0) { 59 if (ctx.cfg.summary_log > 0) {
58 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
59 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,
60 NULL, NULL, 0); 62 NULL, NULL, 0, 0);
61 } 63 }
62 if (ctx.repo->clone_url) 64 if (ctx.repo->clone_url)
63 print_urls(ctx.repo->clone_url, NULL); 65 print_urls(ctx.repo->clone_url, NULL);
64 else if (ctx.cfg.clone_prefix) 66 else if (ctx.cfg.clone_prefix)
65 print_urls(ctx.cfg.clone_prefix, ctx.repo->url); 67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
66 html("</table>"); 68 html("</table>");
67} 69}
68 70
69void cgit_print_repo_readme(char *path) 71void cgit_print_repo_readme(char *path)
70{ 72{
71 char *slash, *tmp; 73 char *slash, *tmp, *colon, *ref;
72 74
73 if (!ctx.repo->readme) 75 if (!ctx.repo->readme || !(*ctx.repo->readme))
74 return; 76 return;
75 77
78 ref = NULL;
79
80 /* Check if the readme is tracked in the git repo. */
81 colon = strchr(ctx.repo->readme, ':');
82 if (colon && strlen(colon) > 1) {
83 *colon = '\0';
84 ref = ctx.repo->readme;
85 ctx.repo->readme = colon + 1;
86 if (!(*ctx.repo->readme))
87 return;
88 }
89
90 /* Prepend repo path to relative readme path unless tracked. */
91 if (!ref && *ctx.repo->readme != '/')
92 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path,
93 ctx.repo->readme));
94
95 /* If a subpath is specified for the about page, make it relative
96 * to the directory containing the configured readme.
97 */
76 if (path) { 98 if (path) {
77 slash = strrchr(ctx.repo->readme, '/'); 99 slash = strrchr(ctx.repo->readme, '/');
78 if (!slash) 100 if (!slash) {
101 if (!colon)
79 return; 102 return;
103 slash = colon;
104 }
80 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1); 105 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
81 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1); 106 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
82 strcpy(tmp + (slash - ctx.repo->readme + 1), path); 107 strcpy(tmp + (slash - ctx.repo->readme + 1), path);
83 } else 108 } else
84 tmp = ctx.repo->readme; 109 tmp = ctx.repo->readme;
110
111 /* Print the calculated readme, either from the git repo or from the
112 * filesystem, while applying the about-filter.
113 */
85 html("<div id='summary'>"); 114 html("<div id='summary'>");
86 if (ctx.repo->about_filter) 115 if (ctx.repo->about_filter)
87 cgit_open_filter(ctx.repo->about_filter); 116 cgit_open_filter(ctx.repo->about_filter);
117 if (ref)
118 cgit_print_file(tmp, ref);
119 else
88 html_include(tmp); 120 html_include(tmp);
89 if (ctx.repo->about_filter) 121 if (ctx.repo->about_filter)
90 cgit_close_filter(ctx.repo->about_filter); 122 cgit_close_filter(ctx.repo->about_filter);
91 html("</div>"); 123 html("</div>");
92} 124}
diff --git a/ui-tag.c b/ui-tag.c
index c2d72af..39e4cb8 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -27,12 +27,20 @@ static void print_tag_content(char *buf)
27 html("<div class='commit-msg'>"); 27 html("<div class='commit-msg'>");
28 html_txt(++p); 28 html_txt(++p);
29 html("</div>"); 29 html("</div>");
30 } 30 }
31} 31}
32 32
33void print_download_links(char *revname)
34{
35 html("<tr><th>download</th><td class='sha1'>");
36 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
37 revname, ctx.repo->snapshots);
38 html("</td></tr>");
39}
40
33void cgit_print_tag(char *revname) 41void cgit_print_tag(char *revname)
34{ 42{
35 unsigned char sha1[20]; 43 unsigned char sha1[20];
36 struct object *obj; 44 struct object *obj;
37 struct tag *tag; 45 struct tag *tag;
38 struct taginfo *info; 46 struct taginfo *info;
@@ -53,40 +61,44 @@ void cgit_print_tag(char *revname)
53 tag = lookup_tag(sha1); 61 tag = lookup_tag(sha1);
54 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { 62 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
55 cgit_print_error(fmt("Bad tag object: %s", revname)); 63 cgit_print_error(fmt("Bad tag object: %s", revname));
56 return; 64 return;
57 } 65 }
58 html("<table class='commit-info'>\n"); 66 html("<table class='commit-info'>\n");
59 htmlf("<tr><td>Tag name</td><td>"); 67 htmlf("<tr><td>tag name</td><td>");
60 html_txt(revname); 68 html_txt(revname);
61 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1)); 69 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1));
62 if (info->tagger_date > 0) { 70 if (info->tagger_date > 0) {
63 html("<tr><td>Tag date</td><td>"); 71 html("<tr><td>tag date</td><td>");
64 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time); 72 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time);
65 html("</td></tr>\n"); 73 html("</td></tr>\n");
66 } 74 }
67 if (info->tagger) { 75 if (info->tagger) {
68 html("<tr><td>Tagged by</td><td>"); 76 html("<tr><td>tagged by</td><td>");
69 html_txt(info->tagger); 77 html_txt(info->tagger);
70 if (info->tagger_email && !ctx.cfg.noplainemail) { 78 if (info->tagger_email && !ctx.cfg.noplainemail) {
71 html(" "); 79 html(" ");
72 html_txt(info->tagger_email); 80 html_txt(info->tagger_email);
73 } 81 }
74 html("</td></tr>\n"); 82 html("</td></tr>\n");
75 } 83 }
76 html("<tr><td>Tagged object</td><td>"); 84 html("<tr><td>tagged object</td><td class='sha1'>");
77 cgit_object_link(tag->tagged); 85 cgit_object_link(tag->tagged);
78 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 if (ctx.repo->snapshots)
88 print_download_links(revname);
79 html("</table>\n"); 89 html("</table>\n");
80 print_tag_content(info->msg); 90 print_tag_content(info->msg);
81 } else { 91 } else {
82 html("<table class='commit-info'>\n"); 92 html("<table class='commit-info'>\n");
83 htmlf("<tr><td>Tag name</td><td>"); 93 htmlf("<tr><td>tag name</td><td>");
84 html_txt(revname); 94 html_txt(revname);
85 html("</td></tr>\n"); 95 html("</td></tr>\n");
86 html("<tr><td>Tagged object</td><td>"); 96 html("<tr><td>Tagged object</td><td class='sha1'>");
87 cgit_object_link(obj); 97 cgit_object_link(obj);
88 html("</td></tr>\n"); 98 html("</td></tr>\n");
99 if (ctx.repo->snapshots)
100 print_download_links(revname);
89 html("</table>\n"); 101 html("</table>\n");
90 } 102 }
91 return; 103 return;
92} 104}
diff --git a/ui-tree.c b/ui-tree.c
index a164767..0b1b531 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -43,13 +43,13 @@ static void print_text_buffer(const char *name, char *buf, unsigned long size)
43 } 43 }
44 44
45 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size); 49 html_raw(buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
52 return; 52 return;
53 } 53 }
54 54
55 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
@@ -64,13 +64,13 @@ static void print_binary_buffer(char *buf, unsigned long size)
64 unsigned long ofs, idx; 64 unsigned long ofs, idx;
65 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
66 66
67 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 htmlf("%*s%02x", 72 htmlf("%*s%02x",
73 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
74 buf[idx] & 0xff); 74 buf[idx] & 0xff);
75 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
@@ -99,16 +99,22 @@ static void print_object(const unsigned char *sha1, char *path, const char *base
99 if (!buf) { 99 if (!buf) {
100 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
101 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
102 return; 102 return;
103 } 103 }
104 104
105 html(" ("); 105 htmlf("blob: %s (", sha1_to_hex(sha1));
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 html(")\n");
109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size);
113 return;
114 }
109 115
110 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
111 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
112 else 118 else
113 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
114} 120}
@@ -166,12 +172,14 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
166 html("<td>"); 172 html("<td>");
167 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
168 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 174 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
169 if (ctx.repo->max_stats) 175 if (ctx.repo->max_stats)
170 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 176 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
171 fullpath); 177 fullpath);
178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev,
179 fullpath);
172 html("</td></tr>\n"); 180 html("</td></tr>\n");
173 free(name); 181 free(name);
174 return 0; 182 return 0;
175} 183}
176 184
177static void ls_head() 185static void ls_head()
@@ -214,23 +222,16 @@ static void ls_tree(const unsigned char *sha1, char *path)
214static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 222static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
215 const char *pathname, unsigned mode, int stage, 223 const char *pathname, unsigned mode, int stage,
216 void *cbdata) 224 void *cbdata)
217{ 225{
218 static int state; 226 static int state;
219 static char buffer[PATH_MAX]; 227 static char buffer[PATH_MAX];
220 char *url;
221 228
222 if (state == 0) { 229 if (state == 0) {
223 memcpy(buffer, base, baselen); 230 memcpy(buffer, base, baselen);
224 strcpy(buffer+baselen, pathname); 231 strcpy(buffer+baselen, pathname);
225 url = cgit_pageurl(ctx.qry.repo, "tree",
226 fmt("h=%s&amp;path=%s", curr_rev, buffer));
227 html("/");
228 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
229 curr_rev, buffer);
230
231 if (strcmp(match_path, buffer)) 232 if (strcmp(match_path, buffer))
232 return READ_TREE_RECURSIVE; 233 return READ_TREE_RECURSIVE;
233 234
234 if (S_ISDIR(mode)) { 235 if (S_ISDIR(mode)) {
235 state = 1; 236 state = 1;
236 ls_head(); 237 ls_head();
@@ -267,16 +268,12 @@ void cgit_print_tree(const char *rev, char *path)
267 commit = lookup_commit_reference(sha1); 268 commit = lookup_commit_reference(sha1);
268 if (!commit || parse_commit(commit)) { 269 if (!commit || parse_commit(commit)) {
269 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 270 cgit_print_error(fmt("Invalid commit reference: %s", rev));
270 return; 271 return;
271 } 272 }
272 273
273 html("path: <a href='");
274 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
275 html("'>root</a>");
276
277 if (path == NULL) { 274 if (path == NULL) {
278 ls_tree(commit->tree->object.sha1, NULL); 275 ls_tree(commit->tree->object.sha1, NULL);
279 return; 276 return;
280 } 277 }
281 278
282 match_path = path; 279 match_path = path;
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 */