summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile18
-rw-r--r--cgit.c23
-rw-r--r--cgit.css104
-rw-r--r--cgit.h9
-rw-r--r--cgitrc.5.txt31
-rw-r--r--cmd.c2
-rwxr-xr-xfilters/syntax-highlighting.sh29
m---------git0
-rw-r--r--html.c70
-rw-r--r--html.h18
-rw-r--r--scan-tree.c2
-rw-r--r--shared.c2
-rw-r--r--ui-atom.c4
-rw-r--r--ui-commit.c21
-rw-r--r--ui-diff.c62
-rw-r--r--ui-log.c4
-rw-r--r--ui-plain.c68
-rw-r--r--ui-refs.c4
-rw-r--r--ui-shared.c43
-rw-r--r--ui-shared.h5
-rw-r--r--ui-snapshot.c14
-rw-r--r--ui-ssdiff.c369
-rw-r--r--ui-ssdiff.h13
-rw-r--r--ui-tag.c24
-rw-r--r--ui-tree.c8
25 files changed, 846 insertions, 101 deletions
diff --git a/Makefile b/Makefile
index 1dc44d5..3a4d974 100644
--- a/Makefile
+++ b/Makefile
@@ -1,25 +1,28 @@
1CGIT_VERSION = v0.8.3.2 1CGIT_VERSION = v0.8.3.2
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
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.6.4.3 8GIT_VER = 1.7.0
9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
10INSTALL = install 10INSTALL = install
11 11
12# Define NO_STRCASESTR if you don't have strcasestr. 12# Define NO_STRCASESTR if you don't have strcasestr.
13# 13#
14# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
15# implementation (slower).
16#
14# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 17# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
15# 18#
16 19
17#-include config.mak 20#-include config.mak
18 21
19# 22#
20# Platform specific tweaks 23# Platform specific tweaks
21# 24#
22 25
23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 26uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 27uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 28uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
@@ -59,46 +62,47 @@ endif
59# Define a pattern rule for automatic dependency building 62# Define a pattern rule for automatic dependency building
60# 63#
61%.d: %.c 64%.d: %.c
62 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 65 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
63 66
64# 67#
65# Define a pattern rule for silent object building 68# Define a pattern rule for silent object building
66# 69#
67%.o: %.c 70%.o: %.c
68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 71 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
69 72
70 73
71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 74EXTLIBS = git/libgit.a git/xdiff/lib.a -lz
72OBJECTS = 75OBJECTS =
73OBJECTS += cache.o 76OBJECTS += cache.o
74OBJECTS += cgit.o 77OBJECTS += cgit.o
75OBJECTS += cmd.o 78OBJECTS += cmd.o
76OBJECTS += configfile.o 79OBJECTS += configfile.o
77OBJECTS += html.o 80OBJECTS += html.o
78OBJECTS += parsing.o 81OBJECTS += parsing.o
79OBJECTS += scan-tree.o 82OBJECTS += scan-tree.o
80OBJECTS += shared.o 83OBJECTS += shared.o
81OBJECTS += ui-atom.o 84OBJECTS += ui-atom.o
82OBJECTS += ui-blob.o 85OBJECTS += ui-blob.o
83OBJECTS += ui-clone.o 86OBJECTS += ui-clone.o
84OBJECTS += ui-commit.o 87OBJECTS += ui-commit.o
85OBJECTS += ui-diff.o 88OBJECTS += ui-diff.o
86OBJECTS += ui-log.o 89OBJECTS += ui-log.o
87OBJECTS += ui-patch.o 90OBJECTS += ui-patch.o
88OBJECTS += ui-plain.o 91OBJECTS += ui-plain.o
89OBJECTS += ui-refs.o 92OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 93OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 94OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 95OBJECTS += ui-snapshot.o
96OBJECTS += ui-ssdiff.o
93OBJECTS += ui-stats.o 97OBJECTS += ui-stats.o
94OBJECTS += ui-summary.o 98OBJECTS += ui-summary.o
95OBJECTS += ui-tag.o 99OBJECTS += ui-tag.o
96OBJECTS += ui-tree.o 100OBJECTS += ui-tree.o
97 101
98ifdef NEEDS_LIBICONV 102ifdef NEEDS_LIBICONV
99 EXTLIBS += -liconv 103 EXTLIBS += -liconv
100endif 104endif
101 105
102 106
103.PHONY: all libgit test install uninstall clean force-version get-git \ 107.PHONY: all libgit test install uninstall clean force-version get-git \
104 doc man-doc html-doc clean-doc 108 doc man-doc html-doc clean-doc
@@ -114,35 +118,41 @@ CFLAGS += -g -Wall -Igit
114CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 118CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
115CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 119CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
116CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 120CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
117CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 121CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
118CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 122CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
119 123
120ifdef NO_ICONV 124ifdef NO_ICONV
121 CFLAGS += -DNO_ICONV 125 CFLAGS += -DNO_ICONV
122endif 126endif
123ifdef NO_STRCASESTR 127ifdef NO_STRCASESTR
124 CFLAGS += -DNO_STRCASESTR 128 CFLAGS += -DNO_STRCASESTR
125endif 129endif
130ifdef NO_OPENSSL
131 CFLAGS += -DNO_OPENSSL
132 GIT_OPTIONS += NO_OPENSSL=1
133else
134 EXTLIBS += -lcrypto
135endif
126 136
127cgit: $(OBJECTS) libgit 137cgit: $(OBJECTS) libgit
128 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 138 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
129 139
130cgit.o: VERSION 140cgit.o: VERSION
131 141
132-include $(OBJECTS:.o=.d) 142-include $(OBJECTS:.o=.d)
133 143
134libgit: 144libgit:
135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 145 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
136 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 146 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
137 147
138test: all 148test: all
139 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 149 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
140 150
141install: all 151install: all
142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 152 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
143 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 153 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
144 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 154 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
145 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 155 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
146 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 156 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
147 157
148uninstall: 158uninstall:
diff --git a/cgit.c b/cgit.c
index 6c7e811..38bc136 100644
--- a/cgit.c
+++ b/cgit.c
@@ -51,24 +51,28 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "enable-remote-branches"))
64 repo->enable_remote_branches = atoi(value);
65 else if (!strcmp(name, "enable-subject-links"))
66 repo->enable_subject_links = atoi(value);
63 else if (!strcmp(name, "max-stats")) 67 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 68 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 70 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 71 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 72 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 73 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/') 74 if (*value == '/')
71 repo->readme = xstrdup(value); 75 repo->readme = xstrdup(value);
72 else 76 else
73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
@@ -128,69 +132,79 @@ void config_cb(const char *name, const char *value)
128 else if (!strcmp(name, "noheader")) 132 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value); 133 ctx.cfg.noheader = atoi(value);
130 else if (!strcmp(name, "snapshots")) 134 else if (!strcmp(name, "snapshots"))
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 136 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 137 ctx.cfg.enable_filter_overrides = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 138 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 139 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 140 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 141 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 142 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 143 ctx.cfg.enable_log_linecount = atoi(value);
144 else if (!strcmp(name, "enable-remote-branches"))
145 ctx.cfg.enable_remote_branches = atoi(value);
146 else if (!strcmp(name, "enable-subject-links"))
147 ctx.cfg.enable_subject_links = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 148 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 149 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 150 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 151 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 152 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 153 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 154 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 155 ctx.cfg.cache_root = xstrdup(value);
148 else if (!strcmp(name, "cache-root-ttl")) 156 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 157 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 158 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 159 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 160 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 161 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 162 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 163 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 164 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 165 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 166 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 167 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 168 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 169 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 170 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 171 ctx.cfg.embedded = atoi(value);
172 else if (!strcmp(name, "max-atom-items"))
173 ctx.cfg.max_atom_items = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 174 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 175 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 176 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 177 ctx.cfg.max_repodesc_len = atoi(value);
178 else if (!strcmp(name, "max-blob-size"))
179 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 180 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 181 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 182 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 183 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path")) 184 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 185 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 186 process_cached_repolist(value);
175 else 187 else
176 scan_tree(value, repo_config); 188 scan_tree(value, repo_config);
177 else if (!strcmp(name, "source-filter")) 189 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 190 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 191 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 192 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 193 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 194 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 195 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 196 ctx.cfg.summary_tags = atoi(value);
197 else if (!strcmp(name, "side-by-side-diffs"))
198 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 199 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 200 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 201 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 202 ctx.cfg.renamelimit = atoi(value);
189 else if (!strcmp(name, "robots")) 203 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 204 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 205 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 206 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 207 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 208 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 209 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 210 add_mimetype(name + 9, value);
@@ -200,24 +214,26 @@ void config_cb(const char *name, const char *value)
200 214
201static void querystring_cb(const char *name, const char *value) 215static void querystring_cb(const char *name, const char *value)
202{ 216{
203 if (!value) 217 if (!value)
204 value = ""; 218 value = "";
205 219
206 if (!strcmp(name,"r")) { 220 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 221 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 222 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 223 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 224 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 225 } else if (!strcmp(name, "url")) {
226 if (*value == '/')
227 value++;
212 ctx.qry.url = xstrdup(value); 228 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 229 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 230 } else if (!strcmp(name, "qt")) {
215 ctx.qry.grep = xstrdup(value); 231 ctx.qry.grep = xstrdup(value);
216 } else if (!strcmp(name, "q")) { 232 } else if (!strcmp(name, "q")) {
217 ctx.qry.search = xstrdup(value); 233 ctx.qry.search = xstrdup(value);
218 } else if (!strcmp(name, "h")) { 234 } else if (!strcmp(name, "h")) {
219 ctx.qry.head = xstrdup(value); 235 ctx.qry.head = xstrdup(value);
220 ctx.qry.has_symref = 1; 236 ctx.qry.has_symref = 1;
221 } else if (!strcmp(name, "id")) { 237 } else if (!strcmp(name, "id")) {
222 ctx.qry.sha1 = xstrdup(value); 238 ctx.qry.sha1 = xstrdup(value);
223 ctx.qry.has_sha1 = 1; 239 ctx.qry.has_sha1 = 1;
@@ -229,24 +245,28 @@ static void querystring_cb(const char *name, const char *value)
229 } else if (!strcmp(name, "path")) { 245 } else if (!strcmp(name, "path")) {
230 ctx.qry.path = trim_end(value, '/'); 246 ctx.qry.path = trim_end(value, '/');
231 } else if (!strcmp(name, "name")) { 247 } else if (!strcmp(name, "name")) {
232 ctx.qry.name = xstrdup(value); 248 ctx.qry.name = xstrdup(value);
233 } else if (!strcmp(name, "mimetype")) { 249 } else if (!strcmp(name, "mimetype")) {
234 ctx.qry.mimetype = xstrdup(value); 250 ctx.qry.mimetype = xstrdup(value);
235 } else if (!strcmp(name, "s")){ 251 } else if (!strcmp(name, "s")){
236 ctx.qry.sort = xstrdup(value); 252 ctx.qry.sort = xstrdup(value);
237 } else if (!strcmp(name, "showmsg")) { 253 } else if (!strcmp(name, "showmsg")) {
238 ctx.qry.showmsg = atoi(value); 254 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 255 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 256 ctx.qry.period = xstrdup(value);
257 } else if (!strcmp(name, "ss")) {
258 ctx.qry.ssdiff = atoi(value);
259 } else if (!strcmp(name, "all")) {
260 ctx.qry.show_all = atoi(value);
241 } 261 }
242} 262}
243 263
244char *xstrdupn(const char *str) 264char *xstrdupn(const char *str)
245{ 265{
246 return (str ? xstrdup(str) : NULL); 266 return (str ? xstrdup(str) : NULL);
247} 267}
248 268
249static void prepare_context(struct cgit_context *ctx) 269static void prepare_context(struct cgit_context *ctx)
250{ 270{
251 memset(ctx, 0, sizeof(*ctx)); 271 memset(ctx, 0, sizeof(*ctx));
252 ctx->cfg.agefile = "info/web/last-modified"; 272 ctx->cfg.agefile = "info/web/last-modified";
@@ -259,35 +279,38 @@ static void prepare_context(struct cgit_context *ctx)
259 ctx->cfg.cache_root_ttl = 5; 279 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15; 280 ctx->cfg.cache_scanrc_ttl = 15;
261 ctx->cfg.cache_static_ttl = -1; 281 ctx->cfg.cache_static_ttl = -1;
262 ctx->cfg.css = "/cgit.css"; 282 ctx->cfg.css = "/cgit.css";
263 ctx->cfg.logo = "/cgit.png"; 283 ctx->cfg.logo = "/cgit.png";
264 ctx->cfg.local_time = 0; 284 ctx->cfg.local_time = 0;
265 ctx->cfg.enable_tree_linenumbers = 1; 285 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 286 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 287 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 288 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 289 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 290 ctx->cfg.max_repodesc_len = 80;
291 ctx->cfg.max_blob_size = 0;
271 ctx->cfg.max_stats = 0; 292 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 293 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
273 ctx->cfg.renamelimit = -1; 294 ctx->cfg.renamelimit = -1;
274 ctx->cfg.robots = "index, nofollow"; 295 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 296 ctx->cfg.root_title = "Git repository browser";
276 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 297 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
277 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 298 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = ""; 299 ctx->cfg.section = "";
279 ctx->cfg.summary_branches = 10; 300 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 301 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 302 ctx->cfg.summary_tags = 10;
303 ctx->cfg.max_atom_items = 10;
304 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 305 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 306 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 307 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 308 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 309 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 310 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
288 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 311 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
289 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 312 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
290 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 313 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
291 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 314 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
292 ctx->page.mimetype = "text/html"; 315 ctx->page.mimetype = "text/html";
293 ctx->page.charset = PAGE_ENCODING; 316 ctx->page.charset = PAGE_ENCODING;
diff --git a/cgit.css b/cgit.css
index c47ebc9..0cb894a 100644
--- a/cgit.css
+++ b/cgit.css
@@ -153,24 +153,29 @@ table.list td.logsubject {
153} 153}
154 154
155table.list td.logmsg { 155table.list td.logmsg {
156 font-family: monospace; 156 font-family: monospace;
157 white-space: pre; 157 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 158 padding: 1em 0.5em 2em 0.5em;
159} 159}
160 160
161table.list td a { 161table.list td a {
162 color: black; 162 color: black;
163} 163}
164 164
165table.list td a.ls-dir {
166 font-weight: bold;
167 color: #00f;
168}
169
165table.list td a:hover { 170table.list td a:hover {
166 color: #00f; 171 color: #00f;
167} 172}
168 173
169img { 174img {
170 border: none; 175 border: none;
171} 176}
172 177
173input#switch-btn { 178input#switch-btn {
174 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
175} 180}
176 181
@@ -592,12 +597,111 @@ table.hgraph th {
592 padding: 1px 0.5em; 597 padding: 1px 0.5em;
593} 598}
594 599
595table.hgraph td { 600table.hgraph td {
596 vertical-align: center; 601 vertical-align: center;
597 padding: 2px 2px; 602 padding: 2px 2px;
598} 603}
599 604
600table.hgraph div.bar { 605table.hgraph div.bar {
601 background-color: #eee; 606 background-color: #eee;
602 height: 1em; 607 height: 1em;
603} 608}
609
610table.ssdiff {
611 width: 100%;
612}
613
614table.ssdiff td {
615 font-size: 75%;
616 font-family: monospace;
617 white-space: pre;
618 padding: 1px 4px 1px 4px;
619 border-left: solid 1px #aaa;
620 border-right: solid 1px #aaa;
621}
622
623table.ssdiff td.add {
624 color: black;
625 background: #cfc;
626 min-width: 50%;
627}
628
629table.ssdiff td.add_dark {
630 color: black;
631 background: #aca;
632 min-width: 50%;
633}
634
635table.ssdiff span.add {
636 background: #cfc;
637 font-weight: bold;
638}
639
640table.ssdiff td.del {
641 color: black;
642 background: #fcc;
643 min-width: 50%;
644}
645
646table.ssdiff td.del_dark {
647 color: black;
648 background: #caa;
649 min-width: 50%;
650}
651
652table.ssdiff span.del {
653 background: #fcc;
654 font-weight: bold;
655}
656
657table.ssdiff td.changed {
658 color: black;
659 background: #ffc;
660 min-width: 50%;
661}
662
663table.ssdiff td.changed_dark {
664 color: black;
665 background: #cca;
666 min-width: 50%;
667}
668
669table.ssdiff td.lineno {
670 color: black;
671 background: #eee;
672 text-align: right;
673 width: 3em;
674 min-width: 3em;
675}
676
677table.ssdiff td.hunk {
678 color: #black;
679 background: #ccf;
680 border-top: solid 1px #aaa;
681 border-bottom: solid 1px #aaa;
682}
683
684table.ssdiff td.head {
685 border-top: solid 1px #aaa;
686 border-bottom: solid 1px #aaa;
687}
688
689table.ssdiff td.head div.head {
690 font-weight: bold;
691 color: black;
692}
693
694table.ssdiff td.foot {
695 border-top: solid 1px #aaa;
696 border-left: none;
697 border-right: none;
698 border-bottom: none;
699}
700
701table.ssdiff td.space {
702 border: none;
703}
704
705table.ssdiff td.space div {
706 min-height: 3em;
707} \ No newline at end of file
diff --git a/cgit.h b/cgit.h
index 6c6c460..8884f9e 100644
--- a/cgit.h
+++ b/cgit.h
@@ -63,24 +63,26 @@ struct cgit_repo {
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int enable_remote_branches;
76 int enable_subject_links;
75 int max_stats; 77 int max_stats;
76 time_t mtime; 78 time_t mtime;
77 struct cgit_filter *about_filter; 79 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 80 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 81 struct cgit_filter *source_filter;
80}; 82};
81 83
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 85 const char *value);
84 86
85struct cgit_repolist { 87struct cgit_repolist {
86 int length; 88 int length;
@@ -134,24 +136,26 @@ struct cgit_query {
134 char *head; 136 char *head;
135 char *sha1; 137 char *sha1;
136 char *sha2; 138 char *sha2;
137 char *path; 139 char *path;
138 char *name; 140 char *name;
139 char *mimetype; 141 char *mimetype;
140 char *url; 142 char *url;
141 char *period; 143 char *period;
142 int ofs; 144 int ofs;
143 int nohead; 145 int nohead;
144 char *sort; 146 char *sort;
145 int showmsg; 147 int showmsg;
148 int ssdiff;
149 int show_all;
146}; 150};
147 151
148struct cgit_config { 152struct cgit_config {
149 char *agefile; 153 char *agefile;
150 char *cache_root; 154 char *cache_root;
151 char *clone_prefix; 155 char *clone_prefix;
152 char *css; 156 char *css;
153 char *favicon; 157 char *favicon;
154 char *footer; 158 char *footer;
155 char *head_include; 159 char *head_include;
156 char *header; 160 char *header;
157 char *index_header; 161 char *index_header;
@@ -169,40 +173,45 @@ struct cgit_config {
169 int cache_size; 173 int cache_size;
170 int cache_dynamic_ttl; 174 int cache_dynamic_ttl;
171 int cache_max_create_time; 175 int cache_max_create_time;
172 int cache_repo_ttl; 176 int cache_repo_ttl;
173 int cache_root_ttl; 177 int cache_root_ttl;
174 int cache_scanrc_ttl; 178 int cache_scanrc_ttl;
175 int cache_static_ttl; 179 int cache_static_ttl;
176 int embedded; 180 int embedded;
177 int enable_filter_overrides; 181 int enable_filter_overrides;
178 int enable_index_links; 182 int enable_index_links;
179 int enable_log_filecount; 183 int enable_log_filecount;
180 int enable_log_linecount; 184 int enable_log_linecount;
185 int enable_remote_branches;
186 int enable_subject_links;
181 int enable_tree_linenumbers; 187 int enable_tree_linenumbers;
182 int local_time; 188 int local_time;
189 int max_atom_items;
183 int max_repo_count; 190 int max_repo_count;
184 int max_commit_count; 191 int max_commit_count;
185 int max_lock_attempts; 192 int max_lock_attempts;
186 int max_msg_len; 193 int max_msg_len;
187 int max_repodesc_len; 194 int max_repodesc_len;
195 int max_blob_size;
188 int max_stats; 196 int max_stats;
189 int nocache; 197 int nocache;
190 int noplainemail; 198 int noplainemail;
191 int noheader; 199 int noheader;
192 int renamelimit; 200 int renamelimit;
193 int snapshots; 201 int snapshots;
194 int summary_branches; 202 int summary_branches;
195 int summary_log; 203 int summary_log;
196 int summary_tags; 204 int summary_tags;
205 int ssdiff;
197 struct string_list mimetypes; 206 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 207 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 208 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 209 struct cgit_filter *source_filter;
201}; 210};
202 211
203struct cgit_page { 212struct cgit_page {
204 time_t modified; 213 time_t modified;
205 time_t expires; 214 time_t expires;
206 size_t size; 215 size_t size;
207 char *mimetype; 216 char *mimetype;
208 char *charset; 217 char *charset;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..5c24381 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -101,24 +101,35 @@ enable-index-links::
101 "commit" and "tree" pages). Default value: "0". 101 "commit" and "tree" pages). Default value: "0".
102 102
103enable-log-filecount:: 103enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 104 Flag which, when set to "1", will make cgit print the number of
105 modified files for each commit on the repository log page. Default 105 modified files for each commit on the repository log page. Default
106 value: "0". 106 value: "0".
107 107
108enable-log-linecount:: 108enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 109 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 110 and removed lines for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches".
117
118enable-subject-links::
119 Flag which, when set to "1", will make cgit use the subject of the
120 parent commit as link text when generating links to parent commits
121 in commit view. Default value: "0". See also:
122 "repo.enable-subject-links".
123
113enable-tree-linenumbers:: 124enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 125 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 126 for plaintext blobs printed in the tree view. Default value: "1".
116 127
117favicon:: 128favicon::
118 Url used as link to a shortcut icon for cgit. If specified, it is 129 Url used as link to a shortcut icon for cgit. If specified, it is
119 suggested to use the value "/favicon.ico" since certain browsers will 130 suggested to use the value "/favicon.ico" since certain browsers will
120 ignore other values. Default value: none. 131 ignore other values. Default value: none.
121 132
122footer:: 133footer::
123 The content of the file specified with this option will be included 134 The content of the file specified with this option will be included
124 verbatim at the bottom of all pages (i.e. it replaces the standard 135 verbatim at the bottom of all pages (i.e. it replaces the standard
@@ -152,40 +163,48 @@ local-time::
152 Flag which, if set to "1", makes cgit print commit and tag times in the 163 Flag which, if set to "1", makes cgit print commit and tag times in the
153 servers timezone. Default value: "0". 164 servers timezone. Default value: "0".
154 165
155logo:: 166logo::
156 Url which specifies the source of an image which will be used as a logo 167 Url which specifies the source of an image which will be used as a logo
157 on all cgit pages. Default value: "/cgit.png". 168 on all cgit pages. Default value: "/cgit.png".
158 169
159logo-link:: 170logo-link::
160 Url loaded when clicking on the cgit logo image. If unspecified the 171 Url loaded when clicking on the cgit logo image. If unspecified the
161 calculated url of the repository index page will be used. Default 172 calculated url of the repository index page will be used. Default
162 value: none. 173 value: none.
163 174
175max-atom-items::
176 Specifies the number of items to display in atom feeds view. Default
177 value: "10".
178
164max-commit-count:: 179max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 180 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 181 value: "50".
167 182
168max-message-length:: 183max-message-length::
169 Specifies the maximum number of commit message characters to display in 184 Specifies the maximum number of commit message characters to display in
170 "log" view. Default value: "80". 185 "log" view. Default value: "80".
171 186
172max-repo-count:: 187max-repo-count::
173 Specifies the number of entries to list per page on therepository 188 Specifies the number of entries to list per page on therepository
174 index page. Default value: "50". 189 index page. Default value: "50".
175 190
176max-repodesc-length:: 191max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 192 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 193 on the repository index page. Default value: "80".
179 194
195max-blob-size::
196 Specifies the maximum size of a blob to display HTML for in KBytes.
197 Default value: "0" (limit disabled).
198
180max-stats:: 199max-stats::
181 Set the default maximum statistics period. Valid values are "week", 200 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 201 "month", "quarter" and "year". If unspecified, statistics are
183 disabled. Default value: none. See also: "repo.max-stats". 202 disabled. Default value: none. See also: "repo.max-stats".
184 203
185mimetype.<ext>:: 204mimetype.<ext>::
186 Set the mimetype for the specified filename extension. This is used 205 Set the mimetype for the specified filename extension. This is used
187 by the `plain` command when returning blob content. 206 by the `plain` command when returning blob content.
188 207
189module-link:: 208module-link::
190 Text which will be used as the formatstring for a hyperlink when a 209 Text which will be used as the formatstring for a hyperlink when a
191 submodule is printed in a directory listing. The arguments for the 210 submodule is printed in a directory listing. The arguments for the
@@ -232,24 +251,28 @@ root-title::
232 "Git Repository Browser". 251 "Git Repository Browser".
233 252
234scan-path:: 253scan-path::
235 A path which will be scanned for repositories. If caching is enabled, 254 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 255 the result will be cached as a cgitrc include-file in the cache
237 directory. Default value: none. See also: cache-scanrc-ttl. 256 directory. Default value: none. See also: cache-scanrc-ttl.
238 257
239section:: 258section::
240 The name of the current repository section - all repositories defined 259 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 260 after this option will inherit the current section name. Default value:
242 none. 261 none.
243 262
263side-by-side-diffs::
264 If set to "1" shows side-by-side diffs instead of unidiffs per
265 default. Default value: "0".
266
244snapshots:: 267snapshots::
245 Text which specifies the default set of snapshot formats generated by 268 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 269 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. 270 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
248 271
249source-filter:: 272source-filter::
250 Specifies a command which will be invoked to format plaintext blobs 273 Specifies a command which will be invoked to format plaintext blobs
251 in the tree view. The command will get the blob content on its STDIN 274 in the tree view. The command will get the blob content on its STDIN
252 and the name of the blob as its only command line argument. The STDOUT 275 and the name of the blob as its only command line argument. The STDOUT
253 from the command will be included verbatim as the blob contents, i.e. 276 from the command will be included verbatim as the blob contents, i.e.
254 this can be used to implement e.g. syntax highlighting. Default value: 277 this can be used to implement e.g. syntax highlighting. Default value:
255 none. 278 none.
@@ -295,24 +318,32 @@ repo.defbranch::
295 318
296repo.desc:: 319repo.desc::
297 The value to show as repository description. Default value: none. 320 The value to show as repository description. Default value: none.
298 321
299repo.enable-log-filecount:: 322repo.enable-log-filecount::
300 A flag which can be used to disable the global setting 323 A flag which can be used to disable the global setting
301 `enable-log-filecount'. Default value: none. 324 `enable-log-filecount'. Default value: none.
302 325
303repo.enable-log-linecount:: 326repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 327 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 328 `enable-log-linecount'. Default value: none.
306 329
330repo.enable-remote-branches::
331 Flag which, when set to "1", will make cgit display remote branches
332 in the summary and refs views. Default value: <enable-remote-branches>.
333
334repo.enable-subject-links::
335 A flag which can be used to override the global setting
336 `enable-subject-links'. Default value: none.
337
307repo.max-stats:: 338repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 339 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 340 to the values specified for the global "max-stats" setting. Default
310 value: none. 341 value: none.
311 342
312repo.name:: 343repo.name::
313 The value to show as repository name. Default value: <repo.url>. 344 The value to show as repository name. Default value: <repo.url>.
314 345
315repo.owner:: 346repo.owner::
316 A value used to identify the owner of the repository. Default value: 347 A value used to identify the owner of the repository. Default value:
317 none. 348 none.
318 349
diff --git a/cmd.c b/cmd.c
index 766f903..ad784fc 100644
--- a/cmd.c
+++ b/cmd.c
@@ -24,25 +24,25 @@
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 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);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
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,39 +1,34 @@
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; }
12# table.blob .str { color:#ff0000; } 16# table.blob .str { color:#ff0000; }
13# table.blob .dstr { color:#818100; } 17# table.blob .dstr { color:#818100; }
14# table.blob .slc { color:#838183; font-style:italic; } 18# table.blob .slc { color:#838183; font-style:italic; }
15# table.blob .com { color:#838183; font-style:italic; } 19# table.blob .com { color:#838183; font-style:italic; }
16# table.blob .dir { color:#008200; } 20# table.blob .dir { color:#008200; }
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 7fb6bcff2dece2ff9fbc5ebfe526d9b2a7e764c Subproject e923eaeb901ff056421b9007adcbbce271caa7b
diff --git a/html.c b/html.c
index 66ba65d..337baeb 100644
--- a/html.c
+++ b/html.c
@@ -4,24 +4,50 @@
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <unistd.h> 9#include <unistd.h>
10#include <stdio.h> 10#include <stdio.h>
11#include <stdlib.h> 11#include <stdlib.h>
12#include <stdarg.h> 12#include <stdarg.h>
13#include <string.h> 13#include <string.h>
14#include <errno.h> 14#include <errno.h>
15 15
16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
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", "%20", 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;
22 int len; 48 int len;
23 va_list args; 49 va_list args;
24 50
25 bufidx++; 51 bufidx++;
26 bufidx &= 7; 52 bufidx &= 7;
27 53
@@ -54,145 +80,147 @@ void htmlf(const char *format, ...)
54 vsnprintf(buf, sizeof(buf), format, args); 80 vsnprintf(buf, sizeof(buf), format, args);
55 va_end(args); 81 va_end(args);
56 html(buf); 82 html(buf);
57} 83}
58 84
59void html_status(int code, const char *msg, int more_headers) 85void 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 write(htmlfd, 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;");
79 txt = t+1; 105 txt = t+1;
80 } 106 }
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 write(htmlfd, 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 write(htmlfd, 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 write(htmlfd, 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;");
123 else if (c=='"') 149 else if (c=='"')
124 html("&quot;"); 150 html("&quot;");
125 txt = t+1; 151 txt = t+1;
126 } 152 }
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];
165 if (e && c!='+' && c!='&' && c!='+') {
139 write(htmlfd, txt, t - txt); 166 write(htmlfd, txt, t - txt);
140 write(htmlfd, fmt("%%%2x", c), 3); 167 write(htmlfd, 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];
182 if (e) {
155 write(htmlfd, txt, t - txt); 183 write(htmlfd, txt, t - txt);
156 write(htmlfd, fmt("%%%2x", c), 3); 184 write(htmlfd, e, 3);
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);
193 } 221 }
194 if (class) { 222 if (class) {
195 html("' class='"); 223 html("' class='");
196 html_attr(class); 224 html_attr(class);
197 } 225 }
198 html("'>"); 226 html("'>");
@@ -248,32 +276,32 @@ char *convert_query_hexchar(char *txt)
248 d1 = hextoint(*(txt+1)); 276 d1 = hextoint(*(txt+1));
249 d2 = hextoint(*(txt+2)); 277 d2 = hextoint(*(txt+2));
250 if (d1<0 || d2<0) { 278 if (d1<0 || d2<0) {
251 strcpy(txt, txt+3); 279 strcpy(txt, txt+3);
252 return txt-1; 280 return txt-1;
253 } else { 281 } else {
254 *txt = d1 * 16 + d2; 282 *txt = d1 * 16 + d2;
255 strcpy(txt+1, txt+3); 283 strcpy(txt+1, txt+3);
256 return txt; 284 return txt;
257 } 285 }
258} 286}
259 287
260int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)) 288int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value))
261{ 289{
262 char *t, *value = NULL, c; 290 char *t, *txt, *value = NULL, c;
263 291
264 if (!txt) 292 if (!txt_)
265 return 0; 293 return 0;
266 294
267 t = txt = strdup(txt); 295 t = txt = strdup(txt_);
268 if (t == NULL) { 296 if (t == NULL) {
269 printf("Out of memory\n"); 297 printf("Out of memory\n");
270 exit(1); 298 exit(1);
271 } 299 }
272 while((c=*t) != '\0') { 300 while((c=*t) != '\0') {
273 if (c=='=') { 301 if (c=='=') {
274 *t = '\0'; 302 *t = '\0';
275 value = t+1; 303 value = t+1;
276 } else if (c=='+') { 304 } else if (c=='+') {
277 *t = ' '; 305 *t = ' ';
278 } else if (c=='%') { 306 } else if (c=='%') {
279 t = convert_query_hexchar(t); 307 t = convert_query_hexchar(t);
diff --git a/html.h b/html.h
index a55d4b2..16d55ec 100644
--- a/html.h
+++ b/html.h
@@ -1,24 +1,24 @@
1#ifndef HTML_H 1#ifndef HTML_H
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);
8extern void htmlf(const char *format,...); 8extern void htmlf(const char *format,...);
9extern void html_status(int code, const char *msg, int more_headers); 9extern void html_status(int code, const char *msg, int more_headers);
10extern void html_txt(char *txt); 10extern void html_txt(const char *txt);
11extern void html_ntxt(int len, char *txt); 11extern void html_ntxt(int len, const char *txt);
12extern void html_attr(char *txt); 12extern void html_attr(const char *txt);
13extern void html_url_path(char *txt); 13extern void html_url_path(const char *txt);
14extern void html_url_arg(char *txt); 14extern void html_url_arg(const char *txt);
15extern void html_hidden(char *name, char *value); 15extern void html_hidden(const char *name, const char *value);
16extern void html_option(char *value, char *text, char *selected_value); 16extern void html_option(const char *value, const char *text, const char *selected_value);
17extern void html_link_open(char *url, char *title, char *class); 17extern void html_link_open(const char *url, const char *title, const char *class);
18extern void html_link_close(void); 18extern void html_link_close(void);
19extern void html_fileperm(unsigned short mode); 19extern void html_fileperm(unsigned short mode);
20extern int html_include(const char *filename); 20extern int html_include(const char *filename);
21 21
22extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)); 22extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
23 23
24#endif /* HTML_H */ 24#endif /* HTML_H */
diff --git a/scan-tree.c b/scan-tree.c
index dbca797..1e18f3c 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -47,24 +47,26 @@ static void repo_config(const char *name, const char *value)
47static void add_repo(const char *base, const char *path, repo_config_fn fn) 47static void add_repo(const char *base, const char *path, repo_config_fn fn)
48{ 48{
49 struct stat st; 49 struct stat st;
50 struct passwd *pwd; 50 struct passwd *pwd;
51 char *p; 51 char *p;
52 size_t size; 52 size_t size;
53 53
54 if (stat(path, &st)) { 54 if (stat(path, &st)) {
55 fprintf(stderr, "Error accessing %s: %s (%d)\n", 55 fprintf(stderr, "Error accessing %s: %s (%d)\n",
56 path, strerror(errno), errno); 56 path, strerror(errno), errno);
57 return; 57 return;
58 } 58 }
59 if (!stat(fmt("%s/noweb", path), &st))
60 return;
59 if ((pwd = getpwuid(st.st_uid)) == NULL) { 61 if ((pwd = getpwuid(st.st_uid)) == NULL) {
60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 62 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
61 path, strerror(errno), errno); 63 path, strerror(errno), errno);
62 return; 64 return;
63 } 65 }
64 if (base == path) 66 if (base == path)
65 p = fmt("%s", path); 67 p = fmt("%s", path);
66 else 68 else
67 p = fmt("%s", path + strlen(base) + 1); 69 p = fmt("%s", path + strlen(base) + 1);
68 70
69 if (!strcmp(p + strlen(p) - 5, "/.git")) 71 if (!strcmp(p + strlen(p) - 5, "/.git"))
70 p[strlen(p) - 5] = '\0'; 72 p[strlen(p) - 5] = '\0';
diff --git a/shared.c b/shared.c
index 6adf2b6..8b3a045 100644
--- a/shared.c
+++ b/shared.c
@@ -50,24 +50,26 @@ struct cgit_repo *cgit_add_repo(const char *url)
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo)); 51 memset(ret, 0, sizeof(struct cgit_repo));
52 ret->url = trim_end(url, '/'); 52 ret->url = trim_end(url, '/');
53 ret->name = ret->url; 53 ret->name = ret->url;
54 ret->path = NULL; 54 ret->path = NULL;
55 ret->desc = "[no description]"; 55 ret->desc = "[no description]";
56 ret->owner = NULL; 56 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 57 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 58 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 59 ret->snapshots = ctx.cfg.snapshots;
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 = NULL;
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}
71 73
72struct cgit_repo *cgit_get_repoinfo(const char *url) 74struct cgit_repo *cgit_get_repoinfo(const char *url)
73{ 75{
diff --git a/ui-atom.c b/ui-atom.c
index 808b2d0..9f049ae 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -76,25 +76,27 @@ void add_entry(struct commit *commit, char *host)
76 cgit_free_commitinfo(info); 76 cgit_free_commitinfo(info);
77} 77}
78 78
79 79
80void cgit_print_atom(char *tip, char *path, int max_count) 80void cgit_print_atom(char *tip, char *path, int max_count)
81{ 81{
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 }
95 97
96 init_revisions(&rev, NULL); 98 init_revisions(&rev, NULL);
97 rev.abbrev = DEFAULT_ABBREV; 99 rev.abbrev = DEFAULT_ABBREV;
98 rev.commit_format = CMIT_FMT_DEFAULT; 100 rev.commit_format = CMIT_FMT_DEFAULT;
99 rev.verbose_header = 1; 101 rev.verbose_header = 1;
100 rev.show_root_diff = 0; 102 rev.show_root_diff = 0;
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..41313b9 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -6,28 +6,28 @@
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-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)
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 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp, *tmp2;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
@@ -49,48 +49,57 @@ void cgit_print_commit(char *hex)
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1);
67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1);
64 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
69 html("</td></tr>\n"); 74 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 75 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 76 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 77 if (!parent) {
73 html("<tr><td colspan='3'>"); 78 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 79 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 80 html("</td></tr>");
76 continue; 81 continue;
77 } 82 }
78 html("<tr><th>parent</th>" 83 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 84 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 85 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 86 if (ctx.repo->enable_subject_links) {
87 parent_info = cgit_parse_commit(parent);
88 tmp2 = parent_info->subject;
89 }
90 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, 0);
82 html(" ("); 91 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 92 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 93 sha1_to_hex(p->item->object.sha1), NULL, 0);
85 html(")</td></tr>"); 94 html(")</td></tr>");
86 parents++; 95 parents++;
87 } 96 }
88 if (ctx.repo->snapshots) { 97 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 98 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 99 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
91 hex, ctx.repo->snapshots); 100 hex, ctx.repo->snapshots);
92 html("</td></tr>"); 101 html("</td></tr>");
93 } 102 }
94 html("</table>\n"); 103 html("</table>\n");
95 html("<div class='commit-subject'>"); 104 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter) 105 if (ctx.repo->commit_filter)
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..a92a768 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,46 +1,48 @@
1/* ui-diff.c: show diff between two blobs 1/* ui-diff.c: show diff between two blobs
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "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;
18static int lines_added, lines_removed; 19static int lines_added, lines_removed;
19 20
20static struct fileinfo { 21static struct fileinfo {
21 char status; 22 char status;
22 unsigned char old_sha1[20]; 23 unsigned char old_sha1[20];
23 unsigned char new_sha1[20]; 24 unsigned char new_sha1[20];
24 unsigned short old_mode; 25 unsigned short old_mode;
25 unsigned short new_mode; 26 unsigned short new_mode;
26 char *old_path; 27 char *old_path;
27 char *new_path; 28 char *new_path;
28 unsigned int added; 29 unsigned int added;
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;
35 37
36static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
37{ 39{
38 char *class; 40 char *class;
39 41
40 switch (info->status) { 42 switch (info->status) {
41 case DIFF_STATUS_ADDED: 43 case DIFF_STATUS_ADDED:
42 class = "add"; 44 class = "add";
43 break; 45 break;
44 case DIFF_STATUS_COPIED: 46 case DIFF_STATUS_COPIED:
45 class = "cpy"; 47 class = "cpy";
46 break; 48 break;
@@ -74,25 +76,25 @@ static void print_fileinfo(struct fileinfo *info)
74 cgit_print_filemode(info->new_mode); 76 cgit_print_filemode(info->new_mode);
75 } 77 }
76 78
77 if (info->old_mode != info->new_mode && 79 if (info->old_mode != info->new_mode &&
78 !is_null_sha1(info->old_sha1) && 80 !is_null_sha1(info->old_sha1) &&
79 !is_null_sha1(info->new_sha1)) { 81 !is_null_sha1(info->new_sha1)) {
80 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
81 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
82 html("]</span>"); 84 html("]</span>");
83 } 85 }
84 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
85 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86 ctx.qry.sha2, info->new_path); 88 ctx.qry.sha2, info->new_path, 0);
87 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
89 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
90 info->old_path); 92 info->old_path);
91 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
92 if (info->binary) { 94 if (info->binary) {
93 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%d -> %d bytes",
94 info->old_size, info->new_size); 96 info->old_size, info->new_size);
95 return; 97 return;
96 } 98 }
97 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
98 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
@@ -149,25 +151,25 @@ static void inspect_filepair(struct diff_filepair *pair)
149 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
150 total_adds += lines_added; 152 total_adds += lines_added;
151 total_rems += lines_removed; 153 total_rems += lines_removed;
152} 154}
153 155
154void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
155 const unsigned char *new_sha1) 157 const unsigned char *new_sha1)
156{ 158{
157 int i; 159 int i;
158 160
159 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
160 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
161 ctx.qry.sha2, NULL); 163 ctx.qry.sha2, NULL, 0);
162 html("</div>"); 164 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 165 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 166 max_changes = 0;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
166 for(i = 0; i<files; i++) 168 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 169 print_fileinfo(&items[i]);
168 html("</table>"); 170 html("</table>");
169 html("<div class='diffstat-summary'>"); 171 html("<div class='diffstat-summary'>");
170 htmlf("%d files changed, %d insertions, %d deletions", 172 htmlf("%d files changed, %d insertions, %d deletions",
171 files, total_adds, total_rems); 173 files, total_adds, total_rems);
172 html("</div>"); 174 html("</div>");
173} 175}
@@ -237,44 +239,72 @@ static void header(unsigned char *sha1, char *path1, int mode1,
237 else 239 else
238 html_txt(path1); 240 html_txt(path1);
239 html("<br/>+++ b/"); 241 html("<br/>+++ b/");
240 if (mode2 != 0) 242 if (mode2 != 0)
241 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
242 sha1_to_hex(new_rev_sha1), path2); 244 sha1_to_hex(new_rev_sha1), path2);
243 else 245 else
244 html_txt(path2); 246 html_txt(path2);
245 } 247 }
246 html("</div>"); 248 html("</div>");
247} 249}
248 250
251static void print_ssdiff_link()
252{
253 if (!strcmp(ctx.qry.page, "diff")) {
254 if (use_ssdiff)
255 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
256 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
257 else
258 cgit_diff_link("Side-by-side diff", NULL, NULL,
259 ctx.qry.head, ctx.qry.sha1,
260 ctx.qry.sha2, ctx.qry.path, 1);
261 }
262}
263
249static void filepair_cb(struct diff_filepair *pair) 264static void filepair_cb(struct diff_filepair *pair)
250{ 265{
251 unsigned long old_size = 0; 266 unsigned long old_size = 0;
252 unsigned long new_size = 0; 267 unsigned long new_size = 0;
253 int binary = 0; 268 int binary = 0;
269 linediff_fn print_line_fn = print_line;
254 270
271 if (use_ssdiff) {
272 cgit_ssdiff_header_begin();
273 print_line_fn = cgit_ssdiff_line_cb;
274 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 275 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 276 pair->two->sha1, pair->two->path, pair->two->mode);
277 if (use_ssdiff)
278 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 280 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 281 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 282 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 283 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
284 if (use_ssdiff)
285 cgit_ssdiff_footer();
262 return; 286 return;
263 } 287 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 288 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 289 &new_size, &binary, print_line_fn))
266 cgit_print_error("Error running diff"); 290 cgit_print_error("Error running diff");
267 if (binary) 291 if (binary) {
268 html("Binary files differ"); 292 if (use_ssdiff)
293 html("<tr><td colspan='4'>Binary files differ</td></tr>");
294 else
295 html("Binary files differ");
296 }
297 if (use_ssdiff)
298 cgit_ssdiff_footer();
269} 299}
270 300
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 301void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
272{ 302{
273 enum object_type type; 303 enum object_type type;
274 unsigned long size; 304 unsigned long size;
275 struct commit *commit, *commit2; 305 struct commit *commit, *commit2;
276 306
277 if (!new_rev) 307 if (!new_rev)
278 new_rev = ctx.qry.head; 308 new_rev = ctx.qry.head;
279 get_sha1(new_rev, new_rev_sha1); 309 get_sha1(new_rev, new_rev_sha1);
280 type = sha1_object_info(new_rev_sha1, &size); 310 type = sha1_object_info(new_rev_sha1, &size);
@@ -294,20 +324,30 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi
294 hashclr(old_rev_sha1); 324 hashclr(old_rev_sha1);
295 325
296 if (!is_null_sha1(old_rev_sha1)) { 326 if (!is_null_sha1(old_rev_sha1)) {
297 type = sha1_object_info(old_rev_sha1, &size); 327 type = sha1_object_info(old_rev_sha1, &size);
298 if (type == OBJ_BAD) { 328 if (type == OBJ_BAD) {
299 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 329 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
300 return; 330 return;
301 } 331 }
302 commit2 = lookup_commit_reference(old_rev_sha1); 332 commit2 = lookup_commit_reference(old_rev_sha1);
303 if (!commit2 || parse_commit(commit2)) 333 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 335 }
336
337 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
338 use_ssdiff = 1;
339
340 print_ssdiff_link();
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 341 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 342
308 html("<table summary='diff' class='diff'>"); 343 if (use_ssdiff) {
309 html("<tr><td>"); 344 html("<table summary='ssdiff' class='ssdiff'>");
345 } else {
346 html("<table summary='diff' class='diff'>");
347 html("<tr><td>");
348 }
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 349 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
311 html("</td></tr>"); 350 if (!use_ssdiff)
351 html("</td></tr>");
312 html("</table>"); 352 html("</table>");
313} 353}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..0947604 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -57,48 +57,48 @@ void show_commit_decorations(struct commit *commit)
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 57 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 59 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 60 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 61 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 62 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 63 sha1_to_hex(commit->object.sha1), NULL,
64 0, NULL, NULL, ctx.qry.showmsg); 64 0, NULL, NULL, ctx.qry.showmsg);
65 } 65 }
66 else { 66 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 67 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 69 sha1_to_hex(commit->object.sha1), 0);
70 } 70 }
71 deco = deco->next; 71 deco = deco->next;
72 } 72 }
73} 73}
74 74
75void print_commit(struct commit *commit) 75void print_commit(struct commit *commit)
76{ 76{
77 struct commitinfo *info; 77 struct commitinfo *info;
78 char *tmp; 78 char *tmp;
79 int cols = 2; 79 int cols = 2;
80 80
81 info = cgit_parse_commit(commit); 81 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 82 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 83 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
86 html_link_open(tmp, NULL, NULL); 86 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 88 html_link_close();
89 htmlf("</td><td%s>", 89 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 90 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 92 sha1_to_hex(commit->object.sha1), 0);
93 show_commit_decorations(commit); 93 show_commit_decorations(commit);
94 html("</td><td>"); 94 html("</td><td>");
95 html_txt(info->author); 95 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 96 if (ctx.repo->enable_log_filecount) {
97 files = 0; 97 files = 0;
98 add_lines = 0; 98 add_lines = 0;
99 rem_lines = 0; 99 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 100 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 101 html("</td><td>");
102 htmlf("%d", files); 102 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 103 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 104 html("</td><td>");
diff --git a/ui-plain.c b/ui-plain.c
index 66cb19c..da76406 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -1,26 +1,25 @@
1/* ui-plain.c: functions for output of plain blobs by path 1/* ui-plain.c: functions for output of plain blobs by path
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 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;
21 unsigned long size; 20 unsigned long size;
22 struct string_list_item *mime; 21 struct string_list_item *mime;
23 22
24 type = sha1_object_info(sha1, &size); 23 type = sha1_object_info(sha1, &size);
25 if (type == OBJ_BAD) { 24 if (type == OBJ_BAD) {
26 html_status(404, "Not found", 0); 25 html_status(404, "Not found", 0);
@@ -44,51 +43,104 @@ static void print_object(const unsigned char *sha1, const char *path)
44 ctx.page.mimetype = "application/octet-stream"; 43 ctx.page.mimetype = "application/octet-stream";
45 else 44 else
46 ctx.page.mimetype = "text/plain"; 45 ctx.page.mimetype = "text/plain";
47 } 46 }
48 ctx.page.filename = fmt("%s", path); 47 ctx.page.filename = fmt("%s", path);
49 ctx.page.size = size; 48 ctx.page.size = size;
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;
73 unsigned char sha1[20]; 118 unsigned char sha1[20];
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 d3b4f6e..98738db 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -65,25 +65,25 @@ static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
@@ -178,24 +178,26 @@ void cgit_print_branches(int maxcount)
178{ 178{
179 struct reflist list; 179 struct reflist list;
180 int i; 180 int i;
181 181
182 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
183 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 if (ctx.repo->enable_remote_branches)
191 for_each_remote_ref(cgit_refs_cb, &list);
190 192
191 if (maxcount == 0 || maxcount > list.count) 193 if (maxcount == 0 || maxcount > list.count)
192 maxcount = list.count; 194 maxcount = list.count;
193 195
194 if (maxcount < list.count) { 196 if (maxcount < list.count) {
195 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
196 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
197 } 199 }
198 200
199 for(i=0; i<maxcount; i++) 201 for(i=0; i<maxcount; i++)
200 print_branch(list.refs[i]); 202 print_branch(list.refs[i]);
201 203
diff --git a/ui-shared.c b/ui-shared.c
index 8a7cc32..8827fff 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -308,63 +308,85 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
308 delim = "&"; 308 delim = "&";
309 } 309 }
310 if (showmsg) { 310 if (showmsg) {
311 html(delim); 311 html(delim);
312 html("showmsg=1"); 312 html("showmsg=1");
313 } 313 }
314 html("'>"); 314 html("'>");
315 html_txt(name); 315 html_txt(name);
316 html("</a>"); 316 html("</a>");
317} 317}
318 318
319void cgit_commit_link(char *name, char *title, char *class, char *head, 319void cgit_commit_link(char *name, char *title, char *class, char *head,
320 char *rev) 320 char *rev, int toggle_ssdiff)
321{ 321{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 323 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 324 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 325 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 326 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 327 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 328
329 char *delim;
330
331 delim = repolink(title, class, "commit", head, NULL);
332 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim);
334 html("id=");
335 html_url_arg(rev);
336 delim = "&amp;";
337 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim);
340 html("ss=1");
341 }
342 html("'>");
343 html_txt(name);
344 html("</a>");
329} 345}
330 346
331void cgit_refs_link(char *name, char *title, char *class, char *head, 347void cgit_refs_link(char *name, char *title, char *class, char *head,
332 char *rev, char *path) 348 char *rev, char *path)
333{ 349{
334 reporevlink("refs", name, title, class, head, rev, path); 350 reporevlink("refs", name, title, class, head, rev, path);
335} 351}
336 352
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 353void cgit_snapshot_link(char *name, char *title, char *class, char *head,
338 char *rev, char *archivename) 354 char *rev, char *archivename)
339{ 355{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 356 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 357}
342 358
343void cgit_diff_link(char *name, char *title, char *class, char *head, 359void cgit_diff_link(char *name, char *title, char *class, char *head,
344 char *new_rev, char *old_rev, char *path) 360 char *new_rev, char *old_rev, char *path,
361 int toggle_ssdiff)
345{ 362{
346 char *delim; 363 char *delim;
347 364
348 delim = repolink(title, class, "diff", head, path); 365 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 366 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 367 html(delim);
351 html("id="); 368 html("id=");
352 html_url_arg(new_rev); 369 html_url_arg(new_rev);
353 delim = "&amp;"; 370 delim = "&amp;";
354 } 371 }
355 if (old_rev) { 372 if (old_rev) {
356 html(delim); 373 html(delim);
357 html("id2="); 374 html("id2=");
358 html_url_arg(old_rev); 375 html_url_arg(old_rev);
376 delim = "&amp;";
377 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim);
380 html("ss=1");
359 } 381 }
360 html("'>"); 382 html("'>");
361 html_txt(name); 383 html_txt(name);
362 html("</a>"); 384 html("</a>");
363} 385}
364 386
365void cgit_patch_link(char *name, char *title, char *class, char *head, 387void cgit_patch_link(char *name, char *title, char *class, char *head,
366 char *rev) 388 char *rev)
367{ 389{
368 reporevlink("patch", name, title, class, head, rev, NULL); 390 reporevlink("patch", name, title, class, head, rev, NULL);
369} 391}
370 392
@@ -374,25 +396,25 @@ void cgit_stats_link(char *name, char *title, char *class, char *head,
374 reporevlink("stats", name, title, class, head, NULL, path); 396 reporevlink("stats", name, title, class, head, NULL, path);
375} 397}
376 398
377void cgit_object_link(struct object *obj) 399void cgit_object_link(struct object *obj)
378{ 400{
379 char *page, *shortrev, *fullrev, *name; 401 char *page, *shortrev, *fullrev, *name;
380 402
381 fullrev = sha1_to_hex(obj->sha1); 403 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 404 shortrev = xstrdup(fullrev);
383 shortrev[10] = '\0'; 405 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 406 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 408 ctx.qry.head, fullrev, 0);
387 return; 409 return;
388 } else if (obj->type == OBJ_TREE) 410 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 411 page = "tree";
390 else if (obj->type == OBJ_TAG) 412 else if (obj->type == OBJ_TAG)
391 page = "tag"; 413 page = "tag";
392 else 414 else
393 page = "blob"; 415 page = "blob";
394 name = fmt("%s %s...", typename(obj->type), shortrev); 416 name = fmt("%s %s...", typename(obj->type), shortrev);
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 418}
397 419
398void cgit_print_date(time_t secs, char *format, int local_time) 420void cgit_print_date(time_t secs, char *format, int local_time)
@@ -686,27 +708,27 @@ void cgit_print_pageheader(struct cgit_context *ctx)
686 708
687 html("<table class='tabs'><tr><td>\n"); 709 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 710 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 711 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
690 ctx->qry.head); 712 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 714 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
696 ctx->qry.sha1, NULL); 718 ctx->qry.sha1, NULL);
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 719 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
698 ctx->qry.head, ctx->qry.sha1); 720 ctx->qry.head, ctx->qry.sha1, 0);
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0);
701 if (ctx->repo->max_stats) 723 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 724 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
703 ctx->qry.head, NULL); 725 ctx->qry.head, NULL);
704 if (ctx->repo->readme) 726 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 727 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 728 hc(cmd, "about"), ctx->qry.head, NULL,
707 NULL); 729 NULL);
708 html("</td><td class='form'>"); 730 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 731 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 732 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 733 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 734 ctx->qry.path, NULL));
@@ -751,23 +773,28 @@ void cgit_print_filemode(unsigned short mode)
751 html("m"); 773 html("m");
752 else 774 else
753 html("-"); 775 html("-");
754 html_fileperm(mode >> 6); 776 html_fileperm(mode >> 6);
755 html_fileperm(mode >> 3); 777 html_fileperm(mode >> 3);
756 html_fileperm(mode); 778 html_fileperm(mode);
757} 779}
758 780
759void cgit_print_snapshot_links(const char *repo, const char *head, 781void cgit_print_snapshot_links(const char *repo, const char *head,
760 const char *hex, int snapshots) 782 const char *hex, int snapshots)
761{ 783{
762 const struct cgit_snapshot_format* f; 784 const struct cgit_snapshot_format* f;
785 char *prefix;
763 char *filename; 786 char *filename;
787 unsigned char sha1[20];
764 788
789 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
790 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
791 hex++;
792 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
765 for (f = cgit_snapshot_formats; f->suffix; f++) { 793 for (f = cgit_snapshot_formats; f->suffix; f++) {
766 if (!(snapshots & f->bit)) 794 if (!(snapshots & f->bit))
767 continue; 795 continue;
768 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 796 filename = fmt("%s%s", prefix, f->suffix);
769 f->suffix);
770 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 797 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
771 html("<br/>"); 798 html("<br/>");
772 } 799 }
773} 800}
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..9ebc1f9 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -14,33 +14,34 @@ extern void cgit_index_link(char *name, char *title, char *class,
14 char *pattern, int ofs); 14 char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(char *name, char *title, char *class, char *head);
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16extern void cgit_tag_link(char *name, char *title, char *class, char *head,
17 char *rev); 17 char *rev);
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18extern void cgit_tree_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 19 char *rev, char *path);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_plain_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path); 21 char *rev, char *path);
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22extern void cgit_log_link(char *name, char *title, char *class, char *head,
23 char *rev, char *path, int ofs, char *grep, 23 char *rev, char *path, int ofs, char *grep,
24 char *pattern, int showmsg); 24 char *pattern, int showmsg);
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25extern void cgit_commit_link(char *name, char *title, char *class, char *head,
26 char *rev); 26 char *rev, int toggle_ssdiff);
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27extern void cgit_patch_link(char *name, char *title, char *class, char *head,
28 char *rev); 28 char *rev);
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29extern void cgit_refs_link(char *name, char *title, char *class, char *head,
30 char *rev, char *path); 30 char *rev, char *path);
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31extern void cgit_snapshot_link(char *name, char *title, char *class,
32 char *head, char *rev, char *archivename); 32 char *head, char *rev, char *archivename);
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33extern void cgit_diff_link(char *name, char *title, char *class, char *head,
34 char *new_rev, char *old_rev, char *path); 34 char *new_rev, char *old_rev, char *path,
35 int toggle_ssdiff);
35extern void cgit_stats_link(char *name, char *title, char *class, char *head, 36extern void cgit_stats_link(char *name, char *title, char *class, char *head,
36 char *path); 37 char *path);
37extern void cgit_object_link(struct object *obj); 38extern void cgit_object_link(struct object *obj);
38 39
39extern void cgit_print_error(char *msg); 40extern void cgit_print_error(char *msg);
40extern void cgit_print_date(time_t secs, char *format, int local_time); 41extern void cgit_print_date(time_t secs, char *format, int local_time);
41extern void cgit_print_age(time_t t, time_t max_relative, char *format); 42extern void cgit_print_age(time_t t, time_t max_relative, char *format);
42extern void cgit_print_http_headers(struct cgit_context *ctx); 43extern void cgit_print_http_headers(struct cgit_context *ctx);
43extern void cgit_print_docstart(struct cgit_context *ctx); 44extern void cgit_print_docstart(struct cgit_context *ctx);
44extern void cgit_print_docend(); 45extern void cgit_print_docend();
45extern void cgit_print_pageheader(struct cgit_context *ctx); 46extern void cgit_print_pageheader(struct cgit_context *ctx);
46extern void cgit_print_filemode(unsigned short mode); 47extern void cgit_print_filemode(unsigned short mode);
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 4136b3e..1b25dca 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -26,29 +26,35 @@ static int write_compressed_tar_archive(struct archiver_args *args,const char *f
26} 26}
27 27
28static int write_tar_gzip_archive(struct archiver_args *args) 28static int write_tar_gzip_archive(struct archiver_args *args)
29{ 29{
30 return write_compressed_tar_archive(args,"gzip"); 30 return write_compressed_tar_archive(args,"gzip");
31} 31}
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;
49 int fl, sl; 55 int fl, sl;
50 56
51 fl = strlen(filename); 57 fl = strlen(filename);
52 for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { 58 for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
53 sl = strlen(fmt->suffix); 59 sl = strlen(fmt->suffix);
54 if (sl >= fl) 60 if (sl >= fl)
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
new file mode 100644
index 0000000..408e620
--- a/dev/null
+++ b/ui-ssdiff.c
@@ -0,0 +1,369 @@
1#include "cgit.h"
2#include "html.h"
3#include "ui-shared.h"
4
5extern int use_ssdiff;
6
7static int current_old_line, current_new_line;
8
9struct deferred_lines {
10 int line_no;
11 char *line;
12 struct deferred_lines *next;
13};
14
15static struct deferred_lines *deferred_old, *deferred_old_last;
16static struct deferred_lines *deferred_new, *deferred_new_last;
17
18static char *longest_common_subsequence(char *A, char *B)
19{
20 int i, j, ri;
21 int m = strlen(A);
22 int n = strlen(B);
23 int L[m + 1][n + 1];
24 int tmp1, tmp2;
25 int lcs_length;
26 char *result;
27
28 for (i = m; i >= 0; i--) {
29 for (j = n; j >= 0; j--) {
30 if (A[i] == '\0' || B[j] == '\0') {
31 L[i][j] = 0;
32 } else if (A[i] == B[j]) {
33 L[i][j] = 1 + L[i + 1][j + 1];
34 } else {
35 tmp1 = L[i + 1][j];
36 tmp2 = L[i][j + 1];
37 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
38 }
39 }
40 }
41
42 lcs_length = L[0][0];
43 result = xmalloc(lcs_length + 2);
44 memset(result, 0, sizeof(*result) * (lcs_length + 2));
45
46 ri = 0;
47 i = 0;
48 j = 0;
49 while (i < m && j < n) {
50 if (A[i] == B[j]) {
51 result[ri] = A[i];
52 ri += 1;
53 i += 1;
54 j += 1;
55 } else if (L[i + 1][j] >= L[i][j + 1]) {
56 i += 1;
57 } else {
58 j += 1;
59 }
60 }
61 return result;
62}
63
64static int line_from_hunk(char *line, char type)
65{
66 char *buf1, *buf2;
67 int len;
68
69 buf1 = strchr(line, type);
70 if (buf1 == NULL)
71 return 0;
72 buf1 += 1;
73 buf2 = strchr(buf1, ',');
74 if (buf2 == NULL)
75 return 0;
76 len = buf2 - buf1;
77 buf2 = xmalloc(len + 1);
78 strncpy(buf2, buf1, len);
79 buf2[len] = '\0';
80 int res = atoi(buf2);
81 free(buf2);
82 return res;
83}
84
85static char *replace_tabs(char *line)
86{
87 char *prev_buf = line;
88 char *cur_buf;
89 int linelen = strlen(line);
90 int n_tabs = 0;
91 int i;
92 char *result;
93 char *spaces = " ";
94
95 if (linelen == 0) {
96 result = xmalloc(1);
97 result[0] = '\0';
98 return result;
99 }
100
101 for (i = 0; i < linelen; i++)
102 if (line[i] == '\t')
103 n_tabs += 1;
104 result = xmalloc(linelen + n_tabs * 8 + 1);
105 result[0] = '\0';
106
107 while (1) {
108 cur_buf = strchr(prev_buf, '\t');
109 if (!cur_buf) {
110 strcat(result, prev_buf);
111 break;
112 } else {
113 strcat(result, " ");
114 strncat(result, spaces, 8 - (strlen(result) % 8));
115 strncat(result, prev_buf, cur_buf - prev_buf);
116 }
117 prev_buf = cur_buf + 1;
118 }
119 return result;
120}
121
122static int calc_deferred_lines(struct deferred_lines *start)
123{
124 struct deferred_lines *item = start;
125 int result = 0;
126 while (item) {
127 result += 1;
128 item = item->next;
129 }
130 return result;
131}
132
133static void deferred_old_add(char *line, int line_no)
134{
135 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
136 item->line = xstrdup(line);
137 item->line_no = line_no;
138 item->next = NULL;
139 if (deferred_old) {
140 deferred_old_last->next = item;
141 deferred_old_last = item;
142 } else {
143 deferred_old = deferred_old_last = item;
144 }
145}
146
147static void deferred_new_add(char *line, int line_no)
148{
149 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
150 item->line = xstrdup(line);
151 item->line_no = line_no;
152 item->next = NULL;
153 if (deferred_new) {
154 deferred_new_last->next = item;
155 deferred_new_last = item;
156 } else {
157 deferred_new = deferred_new_last = item;
158 }
159}
160
161static void print_part_with_lcs(char *class, char *line, char *lcs)
162{
163 int line_len = strlen(line);
164 int i, j;
165 char c[2] = " ";
166 int same = 1;
167
168 j = 0;
169 for (i = 0; i < line_len; i++) {
170 c[0] = line[i];
171 if (same) {
172 if (line[i] == lcs[j])
173 j += 1;
174 else {
175 same = 0;
176 htmlf("<span class='%s'>", class);
177 }
178 } else if (line[i] == lcs[j]) {
179 same = 1;
180 htmlf("</span>");
181 j += 1;
182 }
183 html_txt(c);
184 }
185}
186
187static void print_ssdiff_line(char *class,
188 int old_line_no,
189 char *old_line,
190 int new_line_no,
191 char *new_line, int individual_chars)
192{
193 char *lcs = NULL;
194 if (old_line)
195 old_line = replace_tabs(old_line + 1);
196 if (new_line)
197 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line);
200 html("<tr>");
201 if (old_line_no > 0)
202 htmlf("<td class='lineno'>%d</td><td class='%s'>",
203 old_line_no, class);
204 else if (old_line)
205 htmlf("<td class='lineno'></td><td class='%s'>", class);
206 else
207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
208 if (old_line) {
209 if (lcs)
210 print_part_with_lcs("del", old_line, lcs);
211 else
212 html_txt(old_line);
213 }
214
215 html("</td>");
216 if (new_line_no > 0)
217 htmlf("<td class='lineno'>%d</td><td class='%s'>",
218 new_line_no, class);
219 else if (new_line)
220 htmlf("<td class='lineno'></td><td class='%s'>", class);
221 else
222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
223 if (new_line) {
224 if (lcs)
225 print_part_with_lcs("add", new_line, lcs);
226 else
227 html_txt(new_line);
228 }
229
230 html("</td></tr>");
231 if (lcs)
232 free(lcs);
233 if (new_line)
234 free(new_line);
235 if (old_line)
236 free(old_line);
237}
238
239static void print_deferred_old_lines()
240{
241 struct deferred_lines *iter_old, *tmp;
242 iter_old = deferred_old;
243 while (iter_old) {
244 print_ssdiff_line("del", iter_old->line_no,
245 iter_old->line, -1, NULL, 0);
246 tmp = iter_old->next;
247 free(iter_old);
248 iter_old = tmp;
249 }
250}
251
252static void print_deferred_new_lines()
253{
254 struct deferred_lines *iter_new, *tmp;
255 iter_new = deferred_new;
256 while (iter_new) {
257 print_ssdiff_line("add", -1, NULL,
258 iter_new->line_no, iter_new->line, 0);
259 tmp = iter_new->next;
260 free(iter_new);
261 iter_new = tmp;
262 }
263}
264
265static void print_deferred_changed_lines()
266{
267 struct deferred_lines *iter_old, *iter_new, *tmp;
268 int n_old_lines = calc_deferred_lines(deferred_old);
269 int n_new_lines = calc_deferred_lines(deferred_new);
270 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
271
272 iter_old = deferred_old;
273 iter_new = deferred_new;
274 while (iter_old || iter_new) {
275 if (iter_old && iter_new)
276 print_ssdiff_line("changed", iter_old->line_no,
277 iter_old->line,
278 iter_new->line_no, iter_new->line,
279 individual_chars);
280 else if (iter_old)
281 print_ssdiff_line("changed", iter_old->line_no,
282 iter_old->line, -1, NULL, 0);
283 else if (iter_new)
284 print_ssdiff_line("changed", -1, NULL,
285 iter_new->line_no, iter_new->line, 0);
286 if (iter_old) {
287 tmp = iter_old->next;
288 free(iter_old);
289 iter_old = tmp;
290 }
291
292 if (iter_new) {
293 tmp = iter_new->next;
294 free(iter_new);
295 iter_new = tmp;
296 }
297 }
298}
299
300void cgit_ssdiff_print_deferred_lines()
301{
302 if (!deferred_old && !deferred_new)
303 return;
304 if (deferred_old && !deferred_new)
305 print_deferred_old_lines();
306 else if (!deferred_old && deferred_new)
307 print_deferred_new_lines();
308 else
309 print_deferred_changed_lines();
310 deferred_old = deferred_old_last = NULL;
311 deferred_new = deferred_new_last = NULL;
312}
313
314/*
315 * print a single line returned from xdiff
316 */
317void cgit_ssdiff_line_cb(char *line, int len)
318{
319 char c = line[len - 1];
320 line[len - 1] = '\0';
321 if (line[0] == '@') {
322 current_old_line = line_from_hunk(line, '-');
323 current_new_line = line_from_hunk(line, '+');
324 }
325
326 if (line[0] == ' ') {
327 if (deferred_old || deferred_new)
328 cgit_ssdiff_print_deferred_lines();
329 print_ssdiff_line("ctx", current_old_line, line,
330 current_new_line, line, 0);
331 current_old_line += 1;
332 current_new_line += 1;
333 } else if (line[0] == '+') {
334 deferred_new_add(line, current_new_line);
335 current_new_line += 1;
336 } else if (line[0] == '-') {
337 deferred_old_add(line, current_old_line);
338 current_old_line += 1;
339 } else if (line[0] == '@') {
340 html("<tr><td colspan='4' class='hunk'>");
341 html_txt(line);
342 html("</td></tr>");
343 } else {
344 html("<tr><td colspan='4' class='ctx'>");
345 html_txt(line);
346 html("</td></tr>");
347 }
348 line[len - 1] = c;
349}
350
351void cgit_ssdiff_header_begin()
352{
353 current_old_line = -1;
354 current_new_line = -1;
355 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
356 html("<tr><td class='head' colspan='4'>");
357}
358
359void cgit_ssdiff_header_end()
360{
361 html("</td><tr>");
362}
363
364void cgit_ssdiff_footer()
365{
366 if (deferred_old || deferred_new)
367 cgit_ssdiff_print_deferred_lines();
368 html("<tr><td class='foot' colspan='4'></td></tr>");
369}
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-tag.c b/ui-tag.c
index c2d72af..39e4cb8 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -21,24 +21,32 @@ static void print_tag_content(char *buf)
21 p = strchr(buf, '\n'); 21 p = strchr(buf, '\n');
22 if (p) 22 if (p)
23 *p = '\0'; 23 *p = '\0';
24 html_txt(buf); 24 html_txt(buf);
25 html("</div>"); 25 html("</div>");
26 if (p) { 26 if (p) {
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;
39 47
40 if (!revname) 48 if (!revname)
41 revname = ctx.qry.head; 49 revname = ctx.qry.head;
42 50
43 if (get_sha1(fmt("refs/tags/%s", revname), sha1)) { 51 if (get_sha1(fmt("refs/tags/%s", revname), sha1)) {
44 cgit_print_error(fmt("Bad tag reference: %s", revname)); 52 cgit_print_error(fmt("Bad tag reference: %s", revname));
@@ -47,46 +55,50 @@ void cgit_print_tag(char *revname)
47 obj = parse_object(sha1); 55 obj = parse_object(sha1);
48 if (!obj) { 56 if (!obj) {
49 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1))); 57 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1)));
50 return; 58 return;
51 } 59 }
52 if (obj->type == OBJ_TAG) { 60 if (obj->type == OBJ_TAG) {
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..0ee38f2 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -98,24 +98,30 @@ static void print_object(const unsigned char *sha1, char *path, const char *base
98 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
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 html(" (");
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 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size);
113 return;
114 }
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}
115 121
116 122
117static int ls_item(const unsigned char *sha1, const char *base, int baselen, 123static int ls_item(const unsigned char *sha1, const char *base, int baselen,
118 const char *pathname, unsigned int mode, int stage, 124 const char *pathname, unsigned int mode, int stage,
119 void *cbdata) 125 void *cbdata)
120{ 126{
121 char *name; 127 char *name;
@@ -160,24 +166,26 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
160 class = "ls-blob"; 166 class = "ls-blob";
161 cgit_tree_link(name, NULL, class, ctx.qry.head, 167 cgit_tree_link(name, NULL, class, ctx.qry.head,
162 curr_rev, fullpath); 168 curr_rev, fullpath);
163 } 169 }
164 htmlf("</td><td class='ls-size'>%li</td>", size); 170 htmlf("</td><td class='ls-size'>%li</td>", size);
165 171
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()
178{ 186{
179 html("<table summary='tree listing' class='list'>\n"); 187 html("<table summary='tree listing' class='list'>\n");
180 html("<tr class='nohover'>"); 188 html("<tr class='nohover'>");
181 html("<th class='left'>Mode</th>"); 189 html("<th class='left'>Mode</th>");
182 html("<th class='left'>Name</th>"); 190 html("<th class='left'>Name</th>");
183 html("<th class='right'>Size</th>"); 191 html("<th class='right'>Size</th>");