summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--Makefile28
-rw-r--r--cache.h1
-rw-r--r--cgit.c82
-rw-r--r--cgit.css148
-rw-r--r--cgit.h26
-rw-r--r--cgitrc.5.txt68
-rw-r--r--cmd.c48
-rw-r--r--cmd.h3
-rwxr-xr-xfilters/commit-links.sh16
-rwxr-xr-xfilters/syntax-highlighting.sh29
m---------git0
-rw-r--r--html.c84
-rw-r--r--html.h21
-rw-r--r--scan-tree.c115
-rw-r--r--scan-tree.h3
-rw-r--r--shared.c90
-rw-r--r--ui-atom.c4
-rw-r--r--ui-blob.c37
-rw-r--r--ui-blob.h1
-rw-r--r--ui-commit.c46
-rw-r--r--ui-commit.h2
-rw-r--r--ui-diff.c84
-rw-r--r--ui-log.c54
-rw-r--r--ui-patch.c8
-rw-r--r--ui-patch.h2
-rw-r--r--ui-plain.c70
-rw-r--r--ui-refs.c4
-rw-r--r--ui-repolist.c6
-rw-r--r--ui-shared.c270
-rw-r--r--ui-shared.h71
-rw-r--r--ui-snapshot.c14
-rw-r--r--ui-ssdiff.c369
-rw-r--r--ui-ssdiff.h13
-rw-r--r--ui-stats.c26
-rw-r--r--ui-summary.c38
-rw-r--r--ui-tag.c24
-rw-r--r--ui-tree.c27
37 files changed, 1604 insertions, 328 deletions
diff --git a/Makefile b/Makefile
index 5162020..6a47ed2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,39 +1,47 @@
1CGIT_VERSION = v0.8.3.3 1CGIT_VERSION = v0.8.3.3
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.3
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#
19# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
20# do not support the 'size specifiers' introduced by C99, namely ll, hh,
21# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
22# some C compilers supported these specifiers prior to C99 as an extension.
23#
16 24
17#-include config.mak 25#-include config.mak
18 26
19# 27#
20# Platform specific tweaks 28# Platform specific tweaks
21# 29#
22 30
23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 31uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 32uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 33uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
26 34
27ifeq ($(uname_O),Cygwin) 35ifeq ($(uname_O),Cygwin)
28 NO_STRCASESTR = YesPlease 36 NO_STRCASESTR = YesPlease
29 NEEDS_LIBICONV = YesPlease 37 NEEDS_LIBICONV = YesPlease
30endif 38endif
31 39
32# 40#
33# Let the user override the above settings. 41# Let the user override the above settings.
34# 42#
35-include cgit.conf 43-include cgit.conf
36 44
37# 45#
38# Define a way to invoke make in subdirs quietly, shamelessly ripped 46# Define a way to invoke make in subdirs quietly, shamelessly ripped
39# from git.git 47# from git.git
@@ -47,114 +55,126 @@ else # "make -w"
47NO_SUBDIR = : 55NO_SUBDIR = :
48endif 56endif
49 57
50ifndef V 58ifndef V
51 QUIET_CC = @echo ' ' CC $@; 59 QUIET_CC = @echo ' ' CC $@;
52 QUIET_MM = @echo ' ' MM $@; 60 QUIET_MM = @echo ' ' MM $@;
53 QUIET_SUBDIR0 = +@subdir= 61 QUIET_SUBDIR0 = +@subdir=
54 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 62 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
55 $(MAKE) $(PRINT_DIR) -C $$subdir 63 $(MAKE) $(PRINT_DIR) -C $$subdir
56endif 64endif
57 65
58# 66#
59# Define a pattern rule for automatic dependency building 67# Define a pattern rule for automatic dependency building
60# 68#
61%.d: %.c 69%.d: %.c
62 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 70 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
63 71
64# 72#
65# Define a pattern rule for silent object building 73# Define a pattern rule for silent object building
66# 74#
67%.o: %.c 75%.o: %.c
68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 76 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
69 77
70 78
71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 79EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread
72OBJECTS = 80OBJECTS =
73OBJECTS += cache.o 81OBJECTS += cache.o
74OBJECTS += cgit.o 82OBJECTS += cgit.o
75OBJECTS += cmd.o 83OBJECTS += cmd.o
76OBJECTS += configfile.o 84OBJECTS += configfile.o
77OBJECTS += html.o 85OBJECTS += html.o
78OBJECTS += parsing.o 86OBJECTS += parsing.o
79OBJECTS += scan-tree.o 87OBJECTS += scan-tree.o
80OBJECTS += shared.o 88OBJECTS += shared.o
81OBJECTS += ui-atom.o 89OBJECTS += ui-atom.o
82OBJECTS += ui-blob.o 90OBJECTS += ui-blob.o
83OBJECTS += ui-clone.o 91OBJECTS += ui-clone.o
84OBJECTS += ui-commit.o 92OBJECTS += ui-commit.o
85OBJECTS += ui-diff.o 93OBJECTS += ui-diff.o
86OBJECTS += ui-log.o 94OBJECTS += ui-log.o
87OBJECTS += ui-patch.o 95OBJECTS += ui-patch.o
88OBJECTS += ui-plain.o 96OBJECTS += ui-plain.o
89OBJECTS += ui-refs.o 97OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 98OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 99OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 100OBJECTS += ui-snapshot.o
101OBJECTS += ui-ssdiff.o
93OBJECTS += ui-stats.o 102OBJECTS += ui-stats.o
94OBJECTS += ui-summary.o 103OBJECTS += ui-summary.o
95OBJECTS += ui-tag.o 104OBJECTS += ui-tag.o
96OBJECTS += ui-tree.o 105OBJECTS += ui-tree.o
97 106
98ifdef NEEDS_LIBICONV 107ifdef NEEDS_LIBICONV
99 EXTLIBS += -liconv 108 EXTLIBS += -liconv
100endif 109endif
101 110
102 111
103.PHONY: all libgit test install uninstall clean force-version get-git \ 112.PHONY: all libgit test install uninstall clean force-version get-git \
104 doc man-doc html-doc clean-doc 113 doc man-doc html-doc clean-doc
105 114
106all: cgit 115all: cgit
107 116
108VERSION: force-version 117VERSION: force-version
109 @./gen-version.sh "$(CGIT_VERSION)" 118 @./gen-version.sh "$(CGIT_VERSION)"
110-include VERSION 119-include VERSION
111 120
112 121
113CFLAGS += -g -Wall -Igit 122CFLAGS += -g -Wall -Igit
114CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 123CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
115CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 124CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
116CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 125CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
117CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 126CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
118CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 127CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
119 128
120ifdef NO_ICONV 129ifdef NO_ICONV
121 CFLAGS += -DNO_ICONV 130 CFLAGS += -DNO_ICONV
122endif 131endif
123ifdef NO_STRCASESTR 132ifdef NO_STRCASESTR
124 CFLAGS += -DNO_STRCASESTR 133 CFLAGS += -DNO_STRCASESTR
125endif 134endif
135ifdef NO_C99_FORMAT
136 CFLAGS += -DNO_C99_FORMAT
137endif
138ifdef NO_OPENSSL
139 CFLAGS += -DNO_OPENSSL
140 GIT_OPTIONS += NO_OPENSSL=1
141else
142 EXTLIBS += -lcrypto
143endif
126 144
127cgit: $(OBJECTS) libgit 145cgit: $(OBJECTS) libgit
128 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 146 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
129 147
130cgit.o: VERSION 148cgit.o: VERSION
131 149
150ifneq "$(MAKECMDGOALS)" "clean"
132-include $(OBJECTS:.o=.d) 151-include $(OBJECTS:.o=.d)
152endif
133 153
134libgit: 154libgit:
135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 155 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
136 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 156 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
137 157
138test: all 158test: all
139 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 159 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
140 160
141install: all 161install: all
142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 162 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
143 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 163 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
144 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 164 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
145 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 165 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
146 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 166 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
147 167
148uninstall: 168uninstall:
149 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 169 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
150 rm -f $(CGIT_DATA_PATH)/cgit.css 170 rm -f $(CGIT_DATA_PATH)/cgit.css
151 rm -f $(CGIT_DATA_PATH)/cgit.png 171 rm -f $(CGIT_DATA_PATH)/cgit.png
152 172
153doc: man-doc html-doc pdf-doc 173doc: man-doc html-doc pdf-doc
154 174
155man-doc: cgitrc.5.txt 175man-doc: cgitrc.5.txt
156 a2x -f manpage cgitrc.5.txt 176 a2x -f manpage cgitrc.5.txt
157 177
158html-doc: cgitrc.5.txt 178html-doc: cgitrc.5.txt
159 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt 179 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
160 180
diff --git a/cache.h b/cache.h
index ac9276b..5cfdb4f 100644
--- a/cache.h
+++ b/cache.h
@@ -9,29 +9,30 @@
9typedef void (*cache_fill_fn)(void *cbdata); 9typedef void (*cache_fill_fn)(void *cbdata);
10 10
11 11
12/* Print cached content to stdout, generate the content if necessary. 12/* Print cached content to stdout, generate the content if necessary.
13 * 13 *
14 * Parameters 14 * Parameters
15 * size max number of cache files 15 * size max number of cache files
16 * path directory used to store cache files 16 * path directory used to store cache files
17 * key the key used to lookup cache files 17 * key the key used to lookup cache files
18 * ttl max cache time in seconds for this key 18 * ttl max cache time in seconds for this key
19 * fn content generator function for this key 19 * fn content generator function for this key
20 * cbdata user-supplied data to the content generator function 20 * cbdata user-supplied data to the content generator function
21 * 21 *
22 * Return value 22 * Return value
23 * 0 indicates success, everyting else is an error 23 * 0 indicates success, everyting else is an error
24 */ 24 */
25extern int cache_process(int size, const char *path, const char *key, int ttl, 25extern int cache_process(int size, const char *path, const char *key, int ttl,
26 cache_fill_fn fn, void *cbdata); 26 cache_fill_fn fn, void *cbdata);
27 27
28 28
29/* List info about all cache entries on stdout */ 29/* List info about all cache entries on stdout */
30extern int cache_ls(const char *path); 30extern int cache_ls(const char *path);
31 31
32/* Print a message to stdout */ 32/* Print a message to stdout */
33__attribute__((format (printf,1,2)))
33extern void cache_log(const char *format, ...); 34extern void cache_log(const char *format, ...);
34 35
35extern unsigned long hash_str(const char *str); 36extern unsigned long hash_str(const char *str);
36 37
37#endif /* CGIT_CACHE_H */ 38#endif /* CGIT_CACHE_H */
diff --git a/cgit.c b/cgit.c
index 6c7e811..96900bb 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,305 +1,346 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
7 */ 8 */
8 9
9#include "cgit.h" 10#include "cgit.h"
10#include "cache.h" 11#include "cache.h"
11#include "cmd.h" 12#include "cmd.h"
12#include "configfile.h" 13#include "configfile.h"
13#include "html.h" 14#include "html.h"
14#include "ui-shared.h" 15#include "ui-shared.h"
15#include "ui-stats.h" 16#include "ui-stats.h"
16#include "scan-tree.h" 17#include "scan-tree.h"
17 18
18const char *cgit_version = CGIT_VERSION; 19const char *cgit_version = CGIT_VERSION;
19 20
20void add_mimetype(const char *name, const char *value) 21void add_mimetype(const char *name, const char *value)
21{ 22{
22 struct string_list_item *item; 23 struct string_list_item *item;
23 24
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
25 item->util = xstrdup(value); 26 item->util = xstrdup(value);
26} 27}
27 28
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 29struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 30{
30 struct cgit_filter *f; 31 struct cgit_filter *f;
31 32
32 if (!cmd || !cmd[0]) 33 if (!cmd || !cmd[0])
33 return NULL; 34 return NULL;
34 35
35 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 40 f->argv[1] = NULL;
40 return f; 41 return f;
41} 42}
42 43
43static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
44 45
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 47{
47 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 60 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 62 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
64 else if (!strcmp(name, "enable-remote-branches"))
65 repo->enable_remote_branches = atoi(value);
66 else if (!strcmp(name, "enable-subject-links"))
67 repo->enable_subject_links = atoi(value);
63 else if (!strcmp(name, "max-stats")) 68 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 69 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 70 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 71 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 72 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 73 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 74 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/')
71 repo->readme = xstrdup(value); 75 repo->readme = xstrdup(value);
72 else
73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) { 76 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter")) 77 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0); 78 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter")) 79 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0); 80 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter")) 81 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1); 82 repo->source_filter = new_filter(value, 1);
81 } 83 }
82} 84}
83 85
84void config_cb(const char *name, const char *value) 86void config_cb(const char *name, const char *value)
85{ 87{
86 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 88 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value); 89 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url")) 90 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value); 91 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path")) 92 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/'); 93 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo.")) 94 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value); 95 repo_config(ctx.repo, name + 5, value);
96 else if (!strcmp(name, "readme"))
97 ctx.cfg.readme = xstrdup(value);
94 else if (!strcmp(name, "root-title")) 98 else if (!strcmp(name, "root-title"))
95 ctx.cfg.root_title = xstrdup(value); 99 ctx.cfg.root_title = xstrdup(value);
96 else if (!strcmp(name, "root-desc")) 100 else if (!strcmp(name, "root-desc"))
97 ctx.cfg.root_desc = xstrdup(value); 101 ctx.cfg.root_desc = xstrdup(value);
98 else if (!strcmp(name, "root-readme")) 102 else if (!strcmp(name, "root-readme"))
99 ctx.cfg.root_readme = xstrdup(value); 103 ctx.cfg.root_readme = xstrdup(value);
100 else if (!strcmp(name, "css")) 104 else if (!strcmp(name, "css"))
101 ctx.cfg.css = xstrdup(value); 105 ctx.cfg.css = xstrdup(value);
102 else if (!strcmp(name, "favicon")) 106 else if (!strcmp(name, "favicon"))
103 ctx.cfg.favicon = xstrdup(value); 107 ctx.cfg.favicon = xstrdup(value);
104 else if (!strcmp(name, "footer")) 108 else if (!strcmp(name, "footer"))
105 ctx.cfg.footer = xstrdup(value); 109 ctx.cfg.footer = xstrdup(value);
106 else if (!strcmp(name, "head-include")) 110 else if (!strcmp(name, "head-include"))
107 ctx.cfg.head_include = xstrdup(value); 111 ctx.cfg.head_include = xstrdup(value);
108 else if (!strcmp(name, "header")) 112 else if (!strcmp(name, "header"))
109 ctx.cfg.header = xstrdup(value); 113 ctx.cfg.header = xstrdup(value);
110 else if (!strcmp(name, "logo")) 114 else if (!strcmp(name, "logo"))
111 ctx.cfg.logo = xstrdup(value); 115 ctx.cfg.logo = xstrdup(value);
112 else if (!strcmp(name, "index-header")) 116 else if (!strcmp(name, "index-header"))
113 ctx.cfg.index_header = xstrdup(value); 117 ctx.cfg.index_header = xstrdup(value);
114 else if (!strcmp(name, "index-info")) 118 else if (!strcmp(name, "index-info"))
115 ctx.cfg.index_info = xstrdup(value); 119 ctx.cfg.index_info = xstrdup(value);
116 else if (!strcmp(name, "logo-link")) 120 else if (!strcmp(name, "logo-link"))
117 ctx.cfg.logo_link = xstrdup(value); 121 ctx.cfg.logo_link = xstrdup(value);
118 else if (!strcmp(name, "module-link")) 122 else if (!strcmp(name, "module-link"))
119 ctx.cfg.module_link = xstrdup(value); 123 ctx.cfg.module_link = xstrdup(value);
120 else if (!strcmp(name, "virtual-root")) { 124 else if (!strcmp(name, "virtual-root")) {
121 ctx.cfg.virtual_root = trim_end(value, '/'); 125 ctx.cfg.virtual_root = trim_end(value, '/');
122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
123 ctx.cfg.virtual_root = ""; 127 ctx.cfg.virtual_root = "";
124 } else if (!strcmp(name, "nocache")) 128 } else if (!strcmp(name, "nocache"))
125 ctx.cfg.nocache = atoi(value); 129 ctx.cfg.nocache = atoi(value);
126 else if (!strcmp(name, "noplainemail")) 130 else if (!strcmp(name, "noplainemail"))
127 ctx.cfg.noplainemail = atoi(value); 131 ctx.cfg.noplainemail = atoi(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);
138 else if (!strcmp(name, "enable-gitweb-owner"))
139 ctx.cfg.enable_gitweb_owner = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 140 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 141 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 142 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 143 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 144 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 145 ctx.cfg.enable_log_linecount = atoi(value);
146 else if (!strcmp(name, "enable-remote-branches"))
147 ctx.cfg.enable_remote_branches = atoi(value);
148 else if (!strcmp(name, "enable-subject-links"))
149 ctx.cfg.enable_subject_links = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 150 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 151 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 152 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 153 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 154 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 155 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 156 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 157 ctx.cfg.cache_root = xstrdup(expand_macros(value));
148 else if (!strcmp(name, "cache-root-ttl")) 158 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 159 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 160 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 161 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 162 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 163 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 164 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 165 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 166 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 167 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 168 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 169 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 170 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 171 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 172 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 173 ctx.cfg.embedded = atoi(value);
174 else if (!strcmp(name, "max-atom-items"))
175 ctx.cfg.max_atom_items = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 176 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 177 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 178 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 179 ctx.cfg.max_repodesc_len = atoi(value);
180 else if (!strcmp(name, "max-blob-size"))
181 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 182 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 183 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 184 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 185 ctx.cfg.max_commit_count = atoi(value);
186 else if (!strcmp(name, "project-list"))
187 ctx.cfg.project_list = xstrdup(expand_macros(value));
172 else if (!strcmp(name, "scan-path")) 188 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 189 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 190 process_cached_repolist(expand_macros(value));
191 else if (ctx.cfg.project_list)
192 scan_projects(expand_macros(value),
193 ctx.cfg.project_list, repo_config);
175 else 194 else
176 scan_tree(value, repo_config); 195 scan_tree(expand_macros(value), repo_config);
196 else if (!strcmp(name, "section-from-path"))
197 ctx.cfg.section_from_path = atoi(value);
177 else if (!strcmp(name, "source-filter")) 198 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 199 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 200 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 201 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 202 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 203 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 204 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 205 ctx.cfg.summary_tags = atoi(value);
206 else if (!strcmp(name, "side-by-side-diffs"))
207 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 208 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 209 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 210 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 211 ctx.cfg.renamelimit = atoi(value);
212 else if (!strcmp(name, "remove-suffix"))
213 ctx.cfg.remove_suffix = atoi(value);
189 else if (!strcmp(name, "robots")) 214 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 215 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 216 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 217 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 218 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 219 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 220 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 221 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 222 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 223 parse_configfile(expand_macros(value), config_cb);
199} 224}
200 225
201static void querystring_cb(const char *name, const char *value) 226static void querystring_cb(const char *name, const char *value)
202{ 227{
203 if (!value) 228 if (!value)
204 value = ""; 229 value = "";
205 230
206 if (!strcmp(name,"r")) { 231 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 232 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 233 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 234 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 235 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 236 } else if (!strcmp(name, "url")) {
237 if (*value == '/')
238 value++;
212 ctx.qry.url = xstrdup(value); 239 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 240 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 241 } else if (!strcmp(name, "qt")) {
215 ctx.qry.grep = xstrdup(value); 242 ctx.qry.grep = xstrdup(value);
216 } else if (!strcmp(name, "q")) { 243 } else if (!strcmp(name, "q")) {
217 ctx.qry.search = xstrdup(value); 244 ctx.qry.search = xstrdup(value);
218 } else if (!strcmp(name, "h")) { 245 } else if (!strcmp(name, "h")) {
219 ctx.qry.head = xstrdup(value); 246 ctx.qry.head = xstrdup(value);
220 ctx.qry.has_symref = 1; 247 ctx.qry.has_symref = 1;
221 } else if (!strcmp(name, "id")) { 248 } else if (!strcmp(name, "id")) {
222 ctx.qry.sha1 = xstrdup(value); 249 ctx.qry.sha1 = xstrdup(value);
223 ctx.qry.has_sha1 = 1; 250 ctx.qry.has_sha1 = 1;
224 } else if (!strcmp(name, "id2")) { 251 } else if (!strcmp(name, "id2")) {
225 ctx.qry.sha2 = xstrdup(value); 252 ctx.qry.sha2 = xstrdup(value);
226 ctx.qry.has_sha1 = 1; 253 ctx.qry.has_sha1 = 1;
227 } else if (!strcmp(name, "ofs")) { 254 } else if (!strcmp(name, "ofs")) {
228 ctx.qry.ofs = atoi(value); 255 ctx.qry.ofs = atoi(value);
229 } else if (!strcmp(name, "path")) { 256 } else if (!strcmp(name, "path")) {
230 ctx.qry.path = trim_end(value, '/'); 257 ctx.qry.path = trim_end(value, '/');
231 } else if (!strcmp(name, "name")) { 258 } else if (!strcmp(name, "name")) {
232 ctx.qry.name = xstrdup(value); 259 ctx.qry.name = xstrdup(value);
233 } else if (!strcmp(name, "mimetype")) { 260 } else if (!strcmp(name, "mimetype")) {
234 ctx.qry.mimetype = xstrdup(value); 261 ctx.qry.mimetype = xstrdup(value);
235 } else if (!strcmp(name, "s")){ 262 } else if (!strcmp(name, "s")){
236 ctx.qry.sort = xstrdup(value); 263 ctx.qry.sort = xstrdup(value);
237 } else if (!strcmp(name, "showmsg")) { 264 } else if (!strcmp(name, "showmsg")) {
238 ctx.qry.showmsg = atoi(value); 265 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 266 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 267 ctx.qry.period = xstrdup(value);
268 } else if (!strcmp(name, "ss")) {
269 ctx.qry.ssdiff = atoi(value);
270 } else if (!strcmp(name, "all")) {
271 ctx.qry.show_all = atoi(value);
272 } else if (!strcmp(name, "context")) {
273 ctx.qry.context = atoi(value);
274 } else if (!strcmp(name, "ignorews")) {
275 ctx.qry.ignorews = atoi(value);
241 } 276 }
242} 277}
243 278
244char *xstrdupn(const char *str) 279char *xstrdupn(const char *str)
245{ 280{
246 return (str ? xstrdup(str) : NULL); 281 return (str ? xstrdup(str) : NULL);
247} 282}
248 283
249static void prepare_context(struct cgit_context *ctx) 284static void prepare_context(struct cgit_context *ctx)
250{ 285{
251 memset(ctx, 0, sizeof(*ctx)); 286 memset(ctx, 0, sizeof(*ctx));
252 ctx->cfg.agefile = "info/web/last-modified"; 287 ctx->cfg.agefile = "info/web/last-modified";
253 ctx->cfg.nocache = 0; 288 ctx->cfg.nocache = 0;
254 ctx->cfg.cache_size = 0; 289 ctx->cfg.cache_size = 0;
255 ctx->cfg.cache_dynamic_ttl = 5; 290 ctx->cfg.cache_dynamic_ttl = 5;
256 ctx->cfg.cache_max_create_time = 5; 291 ctx->cfg.cache_max_create_time = 5;
257 ctx->cfg.cache_repo_ttl = 5; 292 ctx->cfg.cache_repo_ttl = 5;
258 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 293 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
259 ctx->cfg.cache_root_ttl = 5; 294 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15; 295 ctx->cfg.cache_scanrc_ttl = 15;
261 ctx->cfg.cache_static_ttl = -1; 296 ctx->cfg.cache_static_ttl = -1;
262 ctx->cfg.css = "/cgit.css"; 297 ctx->cfg.css = "/cgit.css";
263 ctx->cfg.logo = "/cgit.png"; 298 ctx->cfg.logo = "/cgit.png";
264 ctx->cfg.local_time = 0; 299 ctx->cfg.local_time = 0;
300 ctx->cfg.enable_gitweb_owner = 1;
265 ctx->cfg.enable_tree_linenumbers = 1; 301 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 302 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 303 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 304 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 305 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 306 ctx->cfg.max_repodesc_len = 80;
307 ctx->cfg.max_blob_size = 0;
271 ctx->cfg.max_stats = 0; 308 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 309 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
310 ctx->cfg.project_list = NULL;
273 ctx->cfg.renamelimit = -1; 311 ctx->cfg.renamelimit = -1;
312 ctx->cfg.remove_suffix = 0;
274 ctx->cfg.robots = "index, nofollow"; 313 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 314 ctx->cfg.root_title = "Git repository browser";
276 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 315 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
277 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 316 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = ""; 317 ctx->cfg.section = "";
279 ctx->cfg.summary_branches = 10; 318 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 319 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 320 ctx->cfg.summary_tags = 10;
321 ctx->cfg.max_atom_items = 10;
322 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 323 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 324 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 325 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 326 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 327 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 328 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
288 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 329 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
289 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 330 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
290 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 331 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
291 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 332 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
292 ctx->page.mimetype = "text/html"; 333 ctx->page.mimetype = "text/html";
293 ctx->page.charset = PAGE_ENCODING; 334 ctx->page.charset = PAGE_ENCODING;
294 ctx->page.filename = NULL; 335 ctx->page.filename = NULL;
295 ctx->page.size = 0; 336 ctx->page.size = 0;
296 ctx->page.modified = time(NULL); 337 ctx->page.modified = time(NULL);
297 ctx->page.expires = ctx->page.modified; 338 ctx->page.expires = ctx->page.modified;
298 ctx->page.etag = NULL; 339 ctx->page.etag = NULL;
299 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 340 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
300 if (ctx->env.script_name) 341 if (ctx->env.script_name)
301 ctx->cfg.script_name = ctx->env.script_name; 342 ctx->cfg.script_name = ctx->env.script_name;
302 if (ctx->env.query_string) 343 if (ctx->env.query_string)
303 ctx->qry.raw = ctx->env.query_string; 344 ctx->qry.raw = ctx->env.query_string;
304 if (!ctx->env.cgit_config) 345 if (!ctx->env.cgit_config)
305 ctx->env.cgit_config = CGIT_CONFIG; 346 ctx->env.cgit_config = CGIT_CONFIG;
@@ -389,48 +430,54 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
389 cgit_print_pageheader(ctx); 430 cgit_print_pageheader(ctx);
390 cgit_print_error(fmt("Invalid branch: %s", tmp)); 431 cgit_print_error(fmt("Invalid branch: %s", tmp));
391 cgit_print_docend(); 432 cgit_print_docend();
392 return 1; 433 return 1;
393 } 434 }
394 return 0; 435 return 0;
395} 436}
396 437
397static void process_request(void *cbdata) 438static void process_request(void *cbdata)
398{ 439{
399 struct cgit_context *ctx = cbdata; 440 struct cgit_context *ctx = cbdata;
400 struct cgit_cmd *cmd; 441 struct cgit_cmd *cmd;
401 442
402 cmd = cgit_get_cmd(ctx); 443 cmd = cgit_get_cmd(ctx);
403 if (!cmd) { 444 if (!cmd) {
404 ctx->page.title = "cgit error"; 445 ctx->page.title = "cgit error";
405 cgit_print_http_headers(ctx); 446 cgit_print_http_headers(ctx);
406 cgit_print_docstart(ctx); 447 cgit_print_docstart(ctx);
407 cgit_print_pageheader(ctx); 448 cgit_print_pageheader(ctx);
408 cgit_print_error("Invalid request"); 449 cgit_print_error("Invalid request");
409 cgit_print_docend(); 450 cgit_print_docend();
410 return; 451 return;
411 } 452 }
412 453
454 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
455 * in-project path limit to be made available at ctx->qry.vpath.
456 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
457 */
458 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
459
413 if (cmd->want_repo && !ctx->repo) { 460 if (cmd->want_repo && !ctx->repo) {
414 cgit_print_http_headers(ctx); 461 cgit_print_http_headers(ctx);
415 cgit_print_docstart(ctx); 462 cgit_print_docstart(ctx);
416 cgit_print_pageheader(ctx); 463 cgit_print_pageheader(ctx);
417 cgit_print_error(fmt("No repository selected")); 464 cgit_print_error(fmt("No repository selected"));
418 cgit_print_docend(); 465 cgit_print_docend();
419 return; 466 return;
420 } 467 }
421 468
422 if (ctx->repo && prepare_repo_cmd(ctx)) 469 if (ctx->repo && prepare_repo_cmd(ctx))
423 return; 470 return;
424 471
425 if (cmd->want_layout) { 472 if (cmd->want_layout) {
426 cgit_print_http_headers(ctx); 473 cgit_print_http_headers(ctx);
427 cgit_print_docstart(ctx); 474 cgit_print_docstart(ctx);
428 cgit_print_pageheader(ctx); 475 cgit_print_pageheader(ctx);
429 } 476 }
430 477
431 cmd->fn(ctx); 478 cmd->fn(ctx);
432 479
433 if (cmd->want_layout) 480 if (cmd->want_layout)
434 cgit_print_docend(); 481 cgit_print_docend();
435} 482}
436 483
@@ -520,73 +567,84 @@ void print_repolist(FILE *f, struct cgit_repolist *list, int start)
520 print_repo(f, &list->repos[i]); 567 print_repo(f, &list->repos[i]);
521} 568}
522 569
523/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 570/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
524 * and return 0 on success. 571 * and return 0 on success.
525 */ 572 */
526static int generate_cached_repolist(const char *path, const char *cached_rc) 573static int generate_cached_repolist(const char *path, const char *cached_rc)
527{ 574{
528 char *locked_rc; 575 char *locked_rc;
529 int idx; 576 int idx;
530 FILE *f; 577 FILE *f;
531 578
532 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 579 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
533 f = fopen(locked_rc, "wx"); 580 f = fopen(locked_rc, "wx");
534 if (!f) { 581 if (!f) {
535 /* Inform about the error unless the lockfile already existed, 582 /* Inform about the error unless the lockfile already existed,
536 * since that only means we've got concurrent requests. 583 * since that only means we've got concurrent requests.
537 */ 584 */
538 if (errno != EEXIST) 585 if (errno != EEXIST)
539 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 586 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
540 locked_rc, strerror(errno), errno); 587 locked_rc, strerror(errno), errno);
541 return errno; 588 return errno;
542 } 589 }
543 idx = cgit_repolist.count; 590 idx = cgit_repolist.count;
591 if (ctx.cfg.project_list)
592 scan_projects(path, ctx.cfg.project_list, repo_config);
593 else
544 scan_tree(path, repo_config); 594 scan_tree(path, repo_config);
545 print_repolist(f, &cgit_repolist, idx); 595 print_repolist(f, &cgit_repolist, idx);
546 if (rename(locked_rc, cached_rc)) 596 if (rename(locked_rc, cached_rc))
547 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 597 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
548 locked_rc, cached_rc, strerror(errno), errno); 598 locked_rc, cached_rc, strerror(errno), errno);
549 fclose(f); 599 fclose(f);
550 return 0; 600 return 0;
551} 601}
552 602
553static void process_cached_repolist(const char *path) 603static void process_cached_repolist(const char *path)
554{ 604{
555 struct stat st; 605 struct stat st;
556 char *cached_rc; 606 char *cached_rc;
557 time_t age; 607 time_t age;
608 unsigned long hash;
558 609
559 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 610 hash = hash_str(path);
560 hash_str(path))); 611 if (ctx.cfg.project_list)
612 hash += hash_str(ctx.cfg.project_list);
613 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
561 614
562 if (stat(cached_rc, &st)) { 615 if (stat(cached_rc, &st)) {
563 /* Nothing is cached, we need to scan without forking. And 616 /* Nothing is cached, we need to scan without forking. And
564 * if we fail to generate a cached repolist, we need to 617 * if we fail to generate a cached repolist, we need to
565 * invoke scan_tree manually. 618 * invoke scan_tree manually.
566 */ 619 */
567 if (generate_cached_repolist(path, cached_rc)) 620 if (generate_cached_repolist(path, cached_rc)) {
621 if (ctx.cfg.project_list)
622 scan_projects(path, ctx.cfg.project_list,
623 repo_config);
624 else
568 scan_tree(path, repo_config); 625 scan_tree(path, repo_config);
626 }
569 return; 627 return;
570 } 628 }
571 629
572 parse_configfile(cached_rc, config_cb); 630 parse_configfile(cached_rc, config_cb);
573 631
574 /* If the cached configfile hasn't expired, lets exit now */ 632 /* If the cached configfile hasn't expired, lets exit now */
575 age = time(NULL) - st.st_mtime; 633 age = time(NULL) - st.st_mtime;
576 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 634 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
577 return; 635 return;
578 636
579 /* The cached repolist has been parsed, but it was old. So lets 637 /* The cached repolist has been parsed, but it was old. So lets
580 * rescan the specified path and generate a new cached repolist 638 * rescan the specified path and generate a new cached repolist
581 * in a child-process to avoid latency for the current request. 639 * in a child-process to avoid latency for the current request.
582 */ 640 */
583 if (fork()) 641 if (fork())
584 return; 642 return;
585 643
586 exit(generate_cached_repolist(path, cached_rc)); 644 exit(generate_cached_repolist(path, cached_rc));
587} 645}
588 646
589static void cgit_parse_args(int argc, const char **argv) 647static void cgit_parse_args(int argc, const char **argv)
590{ 648{
591 int i; 649 int i;
592 int scan = 0; 650 int scan = 0;
@@ -653,49 +711,49 @@ static int calc_ttl()
653 if (!ctx.qry.page) 711 if (!ctx.qry.page)
654 return ctx.cfg.cache_repo_ttl; 712 return ctx.cfg.cache_repo_ttl;
655 713
656 if (ctx.qry.has_symref) 714 if (ctx.qry.has_symref)
657 return ctx.cfg.cache_dynamic_ttl; 715 return ctx.cfg.cache_dynamic_ttl;
658 716
659 if (ctx.qry.has_sha1) 717 if (ctx.qry.has_sha1)
660 return ctx.cfg.cache_static_ttl; 718 return ctx.cfg.cache_static_ttl;
661 719
662 return ctx.cfg.cache_repo_ttl; 720 return ctx.cfg.cache_repo_ttl;
663} 721}
664 722
665int main(int argc, const char **argv) 723int main(int argc, const char **argv)
666{ 724{
667 const char *path; 725 const char *path;
668 char *qry; 726 char *qry;
669 int err, ttl; 727 int err, ttl;
670 728
671 prepare_context(&ctx); 729 prepare_context(&ctx);
672 cgit_repolist.length = 0; 730 cgit_repolist.length = 0;
673 cgit_repolist.count = 0; 731 cgit_repolist.count = 0;
674 cgit_repolist.repos = NULL; 732 cgit_repolist.repos = NULL;
675 733
676 cgit_parse_args(argc, argv); 734 cgit_parse_args(argc, argv);
677 parse_configfile(ctx.env.cgit_config, config_cb); 735 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
678 ctx.repo = NULL; 736 ctx.repo = NULL;
679 http_parse_querystring(ctx.qry.raw, querystring_cb); 737 http_parse_querystring(ctx.qry.raw, querystring_cb);
680 738
681 /* If virtual-root isn't specified in cgitrc, lets pretend 739 /* If virtual-root isn't specified in cgitrc, lets pretend
682 * that virtual-root equals SCRIPT_NAME. 740 * that virtual-root equals SCRIPT_NAME.
683 */ 741 */
684 if (!ctx.cfg.virtual_root) 742 if (!ctx.cfg.virtual_root)
685 ctx.cfg.virtual_root = ctx.cfg.script_name; 743 ctx.cfg.virtual_root = ctx.cfg.script_name;
686 744
687 /* If no url parameter is specified on the querystring, lets 745 /* If no url parameter is specified on the querystring, lets
688 * use PATH_INFO as url. This allows cgit to work with virtual 746 * use PATH_INFO as url. This allows cgit to work with virtual
689 * urls without the need for rewriterules in the webserver (as 747 * urls without the need for rewriterules in the webserver (as
690 * long as PATH_INFO is included in the cache lookup key). 748 * long as PATH_INFO is included in the cache lookup key).
691 */ 749 */
692 path = ctx.env.path_info; 750 path = ctx.env.path_info;
693 if (!ctx.qry.url && path) { 751 if (!ctx.qry.url && path) {
694 if (path[0] == '/') 752 if (path[0] == '/')
695 path++; 753 path++;
696 ctx.qry.url = xstrdup(path); 754 ctx.qry.url = xstrdup(path);
697 if (ctx.qry.raw) { 755 if (ctx.qry.raw) {
698 qry = ctx.qry.raw; 756 qry = ctx.qry.raw;
699 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 757 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
700 free(qry); 758 free(qry);
701 } else 759 } else
diff --git a/cgit.css b/cgit.css
index c47ebc9..0c88b65 100644
--- a/cgit.css
+++ b/cgit.css
@@ -43,90 +43,96 @@ table#header td.main a {
43 color: #000; 43 color: #000;
44} 44}
45 45
46table#header td.form { 46table#header td.form {
47 text-align: right; 47 text-align: right;
48 vertical-align: bottom; 48 vertical-align: bottom;
49 padding-right: 1em; 49 padding-right: 1em;
50 padding-bottom: 2px; 50 padding-bottom: 2px;
51 white-space: nowrap; 51 white-space: nowrap;
52} 52}
53 53
54table#header td.form form, 54table#header td.form form,
55table#header td.form input, 55table#header td.form input,
56table#header td.form select { 56table#header td.form select {
57 font-size: 90%; 57 font-size: 90%;
58} 58}
59 59
60table#header td.sub { 60table#header td.sub {
61 color: #777; 61 color: #777;
62 border-top: solid 1px #ccc; 62 border-top: solid 1px #ccc;
63 padding-left: 10px; 63 padding-left: 10px;
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 border-bottom: solid 3px #ccc;
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
71 width: 100%; 71 width: 100%;
72} 72}
73 73
74table.tabs td { 74table.tabs td {
75 padding: 0px 1em; 75 padding: 0px 1em;
76 vertical-align: bottom; 76 vertical-align: bottom;
77} 77}
78 78
79table.tabs td a { 79table.tabs td a {
80 padding: 2px 0.75em; 80 padding: 2px 0.75em;
81 color: #777; 81 color: #777;
82 font-size: 110%; 82 font-size: 110%;
83} 83}
84 84
85table.tabs td a.active { 85table.tabs td a.active {
86 color: #000; 86 color: #000;
87 background-color: #ccc; 87 background-color: #ccc;
88} 88}
89 89
90table.tabs td.form { 90table.tabs td.form {
91 text-align: right; 91 text-align: right;
92} 92}
93 93
94table.tabs td.form form { 94table.tabs td.form form {
95 padding-bottom: 2px; 95 padding-bottom: 2px;
96 font-size: 90%; 96 font-size: 90%;
97 white-space: nowrap; 97 white-space: nowrap;
98} 98}
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.path {
106 margin: 0px;
107 padding: 5px 2em 2px 2em;
108 color: #000;
109 background-color: #eee;
110}
111
105div.content { 112div.content {
106 margin: 0px; 113 margin: 0px;
107 padding: 2em; 114 padding: 2em;
108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
110} 116}
111 117
112 118
113table.list { 119table.list {
114 width: 100%; 120 width: 100%;
115 border: none; 121 border: none;
116 border-collapse: collapse; 122 border-collapse: collapse;
117} 123}
118 124
119table.list tr { 125table.list tr {
120 background: white; 126 background: white;
121} 127}
122 128
123table.list tr.logheader { 129table.list tr.logheader {
124 background: #eee; 130 background: #eee;
125} 131}
126 132
127table.list tr:hover { 133table.list tr:hover {
128 background: #eee; 134 background: #eee;
129} 135}
130 136
131table.list tr.nohover:hover { 137table.list tr.nohover:hover {
132 background: white; 138 background: white;
@@ -137,52 +143,68 @@ table.list th {
137 /* color: #888; 143 /* color: #888;
138 border-top: dashed 1px #888; 144 border-top: dashed 1px #888;
139 border-bottom: dashed 1px #888; 145 border-bottom: dashed 1px #888;
140 */ 146 */
141 padding: 0.1em 0.5em 0.05em 0.5em; 147 padding: 0.1em 0.5em 0.05em 0.5em;
142 vertical-align: baseline; 148 vertical-align: baseline;
143} 149}
144 150
145table.list td { 151table.list td {
146 border: none; 152 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
148} 154}
149 155
150table.list td.logsubject { 156table.list td.logsubject {
151 font-family: monospace; 157 font-family: monospace;
152 font-weight: bold; 158 font-weight: bold;
153} 159}
154 160
155table.list td.logmsg { 161table.list td.logmsg {
156 font-family: monospace; 162 font-family: monospace;
157 white-space: pre; 163 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 164 padding: 1em 0.5em 2em 0.5em;
159} 165}
160 166
167table.list td.lognotes-label {
168 text-align:right;
169 vertical-align:top;
170}
171
172table.list td.lognotes {
173 font-family: monospace;
174 white-space: pre;
175 padding: 0em 0.5em 2em 0.5em;
176}
177
161table.list td a { 178table.list td a {
162 color: black; 179 color: black;
163} 180}
164 181
182table.list td a.ls-dir {
183 font-weight: bold;
184 color: #00f;
185}
186
165table.list td a:hover { 187table.list td a:hover {
166 color: #00f; 188 color: #00f;
167} 189}
168 190
169img { 191img {
170 border: none; 192 border: none;
171} 193}
172 194
173input#switch-btn { 195input#switch-btn {
174 margin: 2px 0px 0px 0px; 196 margin: 2px 0px 0px 0px;
175} 197}
176 198
177td#sidebar input.txt { 199td#sidebar input.txt {
178 width: 100%; 200 width: 100%;
179 margin: 2px 0px 0px 0px; 201 margin: 2px 0px 0px 0px;
180} 202}
181 203
182table#grid { 204table#grid {
183 margin: 0px; 205 margin: 0px;
184} 206}
185 207
186td#content { 208td#content {
187 vertical-align: top; 209 vertical-align: top;
188 padding: 1em 2em 1em 1em; 210 padding: 1em 2em 1em 1em;
@@ -294,48 +316,66 @@ table.commit-info {
294table.commit-info th { 316table.commit-info th {
295 text-align: left; 317 text-align: left;
296 font-weight: normal; 318 font-weight: normal;
297 padding: 0.1em 1em 0.1em 0.1em; 319 padding: 0.1em 1em 0.1em 0.1em;
298 vertical-align: top; 320 vertical-align: top;
299} 321}
300 322
301table.commit-info td { 323table.commit-info td {
302 font-weight: normal; 324 font-weight: normal;
303 padding: 0.1em 1em 0.1em 0.1em; 325 padding: 0.1em 1em 0.1em 0.1em;
304} 326}
305 327
306div.commit-subject { 328div.commit-subject {
307 font-weight: bold; 329 font-weight: bold;
308 font-size: 125%; 330 font-size: 125%;
309 margin: 1.5em 0em 0.5em 0em; 331 margin: 1.5em 0em 0.5em 0em;
310 padding: 0em; 332 padding: 0em;
311} 333}
312 334
313div.commit-msg { 335div.commit-msg {
314 white-space: pre; 336 white-space: pre;
315 font-family: monospace; 337 font-family: monospace;
316} 338}
317 339
340div.notes-header {
341 font-weight: bold;
342 padding-top: 1.5em;
343}
344
345div.notes {
346 white-space: pre;
347 font-family: monospace;
348 border: solid 1px #ee9;
349 background-color: #ffd;
350 padding: 0.3em 2em 0.3em 1em;
351 float: left;
352}
353
354div.notes-footer {
355 clear: left;
356}
357
318div.diffstat-header { 358div.diffstat-header {
319 font-weight: bold; 359 font-weight: bold;
320 padding-top: 1.5em; 360 padding-top: 1.5em;
321} 361}
322 362
323table.diffstat { 363table.diffstat {
324 border-collapse: collapse; 364 border-collapse: collapse;
325 border: solid 1px #aaa; 365 border: solid 1px #aaa;
326 background-color: #eee; 366 background-color: #eee;
327} 367}
328 368
329table.diffstat th { 369table.diffstat th {
330 font-weight: normal; 370 font-weight: normal;
331 text-align: left; 371 text-align: left;
332 text-decoration: underline; 372 text-decoration: underline;
333 padding: 0.1em 1em 0.1em 0.1em; 373 padding: 0.1em 1em 0.1em 0.1em;
334 font-size: 100%; 374 font-size: 100%;
335} 375}
336 376
337table.diffstat td { 377table.diffstat td {
338 padding: 0.2em 0.2em 0.1em 0.1em; 378 padding: 0.2em 0.2em 0.1em 0.1em;
339 font-size: 100%; 379 font-size: 100%;
340 border: none; 380 border: none;
341} 381}
@@ -499,49 +539,52 @@ a.branch-deco {
499 margin: 0px 0.5em; 539 margin: 0px 0.5em;
500 padding: 0px 0.25em; 540 padding: 0px 0.25em;
501 background-color: #88ff88; 541 background-color: #88ff88;
502 border: solid 1px #007700; 542 border: solid 1px #007700;
503} 543}
504a.tag-deco { 544a.tag-deco {
505 margin: 0px 0.5em; 545 margin: 0px 0.5em;
506 padding: 0px 0.25em; 546 padding: 0px 0.25em;
507 background-color: #ffff88; 547 background-color: #ffff88;
508 border: solid 1px #777700; 548 border: solid 1px #777700;
509} 549}
510a.remote-deco { 550a.remote-deco {
511 margin: 0px 0.5em; 551 margin: 0px 0.5em;
512 padding: 0px 0.25em; 552 padding: 0px 0.25em;
513 background-color: #ccccff; 553 background-color: #ccccff;
514 border: solid 1px #000077; 554 border: solid 1px #000077;
515} 555}
516a.deco { 556a.deco {
517 margin: 0px 0.5em; 557 margin: 0px 0.5em;
518 padding: 0px 0.25em; 558 padding: 0px 0.25em;
519 background-color: #ff8888; 559 background-color: #ff8888;
520 border: solid 1px #770000; 560 border: solid 1px #770000;
521} 561}
522 562
523div.commit-subject a { 563div.commit-subject a.branch-deco,
564div.commit-subject a.tag-deco,
565div.commit-subject a.remote-deco,
566div.commit-subject a.deco {
524 margin-left: 1em; 567 margin-left: 1em;
525 font-size: 75%; 568 font-size: 75%;
526} 569}
527 570
528table.stats { 571table.stats {
529 border: solid 1px black; 572 border: solid 1px black;
530 border-collapse: collapse; 573 border-collapse: collapse;
531} 574}
532 575
533table.stats th { 576table.stats th {
534 text-align: left; 577 text-align: left;
535 padding: 1px 0.5em; 578 padding: 1px 0.5em;
536 background-color: #eee; 579 background-color: #eee;
537 border: solid 1px black; 580 border: solid 1px black;
538} 581}
539 582
540table.stats td { 583table.stats td {
541 text-align: right; 584 text-align: right;
542 padding: 1px 0.5em; 585 padding: 1px 0.5em;
543 border: solid 1px black; 586 border: solid 1px black;
544} 587}
545 588
546table.stats td.total { 589table.stats td.total {
547 font-weight: bold; 590 font-weight: bold;
@@ -580,24 +623,123 @@ table.vgraph div.bar {
580 background-color: #eee; 623 background-color: #eee;
581} 624}
582 625
583table.hgraph { 626table.hgraph {
584 border: solid 1px black; 627 border: solid 1px black;
585 width: 800px; 628 width: 800px;
586} 629}
587 630
588table.hgraph th { 631table.hgraph th {
589 background-color: #eee; 632 background-color: #eee;
590 font-weight: bold; 633 font-weight: bold;
591 border: solid 1px black; 634 border: solid 1px black;
592 padding: 1px 0.5em; 635 padding: 1px 0.5em;
593} 636}
594 637
595table.hgraph td { 638table.hgraph td {
596 vertical-align: center; 639 vertical-align: center;
597 padding: 2px 2px; 640 padding: 2px 2px;
598} 641}
599 642
600table.hgraph div.bar { 643table.hgraph div.bar {
601 background-color: #eee; 644 background-color: #eee;
602 height: 1em; 645 height: 1em;
603} 646}
647
648table.ssdiff {
649 width: 100%;
650}
651
652table.ssdiff td {
653 font-size: 75%;
654 font-family: monospace;
655 white-space: pre;
656 padding: 1px 4px 1px 4px;
657 border-left: solid 1px #aaa;
658 border-right: solid 1px #aaa;
659}
660
661table.ssdiff td.add {
662 color: black;
663 background: #cfc;
664 min-width: 50%;
665}
666
667table.ssdiff td.add_dark {
668 color: black;
669 background: #aca;
670 min-width: 50%;
671}
672
673table.ssdiff span.add {
674 background: #cfc;
675 font-weight: bold;
676}
677
678table.ssdiff td.del {
679 color: black;
680 background: #fcc;
681 min-width: 50%;
682}
683
684table.ssdiff td.del_dark {
685 color: black;
686 background: #caa;
687 min-width: 50%;
688}
689
690table.ssdiff span.del {
691 background: #fcc;
692 font-weight: bold;
693}
694
695table.ssdiff td.changed {
696 color: black;
697 background: #ffc;
698 min-width: 50%;
699}
700
701table.ssdiff td.changed_dark {
702 color: black;
703 background: #cca;
704 min-width: 50%;
705}
706
707table.ssdiff td.lineno {
708 color: black;
709 background: #eee;
710 text-align: right;
711 width: 3em;
712 min-width: 3em;
713}
714
715table.ssdiff td.hunk {
716 color: #black;
717 background: #ccf;
718 border-top: solid 1px #aaa;
719 border-bottom: solid 1px #aaa;
720}
721
722table.ssdiff td.head {
723 border-top: solid 1px #aaa;
724 border-bottom: solid 1px #aaa;
725}
726
727table.ssdiff td.head div.head {
728 font-weight: bold;
729 color: black;
730}
731
732table.ssdiff td.foot {
733 border-top: solid 1px #aaa;
734 border-left: none;
735 border-right: none;
736 border-bottom: none;
737}
738
739table.ssdiff td.space {
740 border: none;
741}
742
743table.ssdiff td.space div {
744 min-height: 3em;
745} \ No newline at end of file
diff --git a/cgit.h b/cgit.h
index 6c6c460..8f5dd2a 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,45 +1,46 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22#include <notes.h>
22 23
23 24
24/* 25/*
25 * Dateformats used on misc. pages 26 * Dateformats used on misc. pages
26 */ 27 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 28#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 29#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 30#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 31
31 32
32/* 33/*
33 * Limits used for relative dates 34 * Limits used for relative dates
34 */ 35 */
35#define TM_MIN 60 36#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 37#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 38#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 39#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 40#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 41#define TM_MONTH (TM_YEAR / 12.0)
41 42
42 43
43/* 44/*
44 * Default encoding 45 * Default encoding
45 */ 46 */
@@ -51,48 +52,50 @@ typedef void (*linediff_fn)(char *line, int len);
51 52
52struct cgit_filter { 53struct cgit_filter {
53 char *cmd; 54 char *cmd;
54 char **argv; 55 char **argv;
55 int old_stdout; 56 int old_stdout;
56 int pipe_fh[2]; 57 int pipe_fh[2];
57 int pid; 58 int pid;
58 int exitstatus; 59 int exitstatus;
59}; 60};
60 61
61struct cgit_repo { 62struct cgit_repo {
62 char *url; 63 char *url;
63 char *name; 64 char *name;
64 char *path; 65 char *path;
65 char *desc; 66 char *desc;
66 char *owner; 67 char *owner;
67 char *defbranch; 68 char *defbranch;
68 char *module_link; 69 char *module_link;
69 char *readme; 70 char *readme;
70 char *section; 71 char *section;
71 char *clone_url; 72 char *clone_url;
72 int snapshots; 73 int snapshots;
73 int enable_log_filecount; 74 int enable_log_filecount;
74 int enable_log_linecount; 75 int enable_log_linecount;
76 int enable_remote_branches;
77 int enable_subject_links;
75 int max_stats; 78 int max_stats;
76 time_t mtime; 79 time_t mtime;
77 struct cgit_filter *about_filter; 80 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 81 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 82 struct cgit_filter *source_filter;
80}; 83};
81 84
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 85typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 86 const char *value);
84 87
85struct cgit_repolist { 88struct cgit_repolist {
86 int length; 89 int length;
87 int count; 90 int count;
88 struct cgit_repo *repos; 91 struct cgit_repo *repos;
89}; 92};
90 93
91struct commitinfo { 94struct commitinfo {
92 struct commit *commit; 95 struct commit *commit;
93 char *author; 96 char *author;
94 char *author_email; 97 char *author_email;
95 unsigned long author_date; 98 unsigned long author_date;
96 char *committer; 99 char *committer;
97 char *committer_email; 100 char *committer_email;
98 unsigned long committer_date; 101 unsigned long committer_date;
@@ -122,99 +125,114 @@ struct reflist {
122 int alloc; 125 int alloc;
123 int count; 126 int count;
124}; 127};
125 128
126struct cgit_query { 129struct cgit_query {
127 int has_symref; 130 int has_symref;
128 int has_sha1; 131 int has_sha1;
129 char *raw; 132 char *raw;
130 char *repo; 133 char *repo;
131 char *page; 134 char *page;
132 char *search; 135 char *search;
133 char *grep; 136 char *grep;
134 char *head; 137 char *head;
135 char *sha1; 138 char *sha1;
136 char *sha2; 139 char *sha2;
137 char *path; 140 char *path;
138 char *name; 141 char *name;
139 char *mimetype; 142 char *mimetype;
140 char *url; 143 char *url;
141 char *period; 144 char *period;
142 int ofs; 145 int ofs;
143 int nohead; 146 int nohead;
144 char *sort; 147 char *sort;
145 int showmsg; 148 int showmsg;
149 int ssdiff;
150 int show_all;
151 int context;
152 int ignorews;
153 char *vpath;
146}; 154};
147 155
148struct cgit_config { 156struct cgit_config {
149 char *agefile; 157 char *agefile;
150 char *cache_root; 158 char *cache_root;
151 char *clone_prefix; 159 char *clone_prefix;
152 char *css; 160 char *css;
153 char *favicon; 161 char *favicon;
154 char *footer; 162 char *footer;
155 char *head_include; 163 char *head_include;
156 char *header; 164 char *header;
157 char *index_header; 165 char *index_header;
158 char *index_info; 166 char *index_info;
159 char *logo; 167 char *logo;
160 char *logo_link; 168 char *logo_link;
161 char *module_link; 169 char *module_link;
170 char *project_list;
171 char *readme;
162 char *robots; 172 char *robots;
163 char *root_title; 173 char *root_title;
164 char *root_desc; 174 char *root_desc;
165 char *root_readme; 175 char *root_readme;
166 char *script_name; 176 char *script_name;
167 char *section; 177 char *section;
168 char *virtual_root; 178 char *virtual_root;
169 int cache_size; 179 int cache_size;
170 int cache_dynamic_ttl; 180 int cache_dynamic_ttl;
171 int cache_max_create_time; 181 int cache_max_create_time;
172 int cache_repo_ttl; 182 int cache_repo_ttl;
173 int cache_root_ttl; 183 int cache_root_ttl;
174 int cache_scanrc_ttl; 184 int cache_scanrc_ttl;
175 int cache_static_ttl; 185 int cache_static_ttl;
176 int embedded; 186 int embedded;
177 int enable_filter_overrides; 187 int enable_filter_overrides;
188 int enable_gitweb_owner;
178 int enable_index_links; 189 int enable_index_links;
179 int enable_log_filecount; 190 int enable_log_filecount;
180 int enable_log_linecount; 191 int enable_log_linecount;
192 int enable_remote_branches;
193 int enable_subject_links;
181 int enable_tree_linenumbers; 194 int enable_tree_linenumbers;
182 int local_time; 195 int local_time;
196 int max_atom_items;
183 int max_repo_count; 197 int max_repo_count;
184 int max_commit_count; 198 int max_commit_count;
185 int max_lock_attempts; 199 int max_lock_attempts;
186 int max_msg_len; 200 int max_msg_len;
187 int max_repodesc_len; 201 int max_repodesc_len;
202 int max_blob_size;
188 int max_stats; 203 int max_stats;
189 int nocache; 204 int nocache;
190 int noplainemail; 205 int noplainemail;
191 int noheader; 206 int noheader;
192 int renamelimit; 207 int renamelimit;
208 int remove_suffix;
209 int section_from_path;
193 int snapshots; 210 int snapshots;
194 int summary_branches; 211 int summary_branches;
195 int summary_log; 212 int summary_log;
196 int summary_tags; 213 int summary_tags;
214 int ssdiff;
197 struct string_list mimetypes; 215 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 216 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 217 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 218 struct cgit_filter *source_filter;
201}; 219};
202 220
203struct cgit_page { 221struct cgit_page {
204 time_t modified; 222 time_t modified;
205 time_t expires; 223 time_t expires;
206 size_t size; 224 size_t size;
207 char *mimetype; 225 char *mimetype;
208 char *charset; 226 char *charset;
209 char *filename; 227 char *filename;
210 char *etag; 228 char *etag;
211 char *title; 229 char *title;
212 int status; 230 int status;
213 char *statusmsg; 231 char *statusmsg;
214}; 232};
215 233
216struct cgit_environment { 234struct cgit_environment {
217 char *cgit_config; 235 char *cgit_config;
218 char *http_host; 236 char *http_host;
219 char *https; 237 char *https;
220 char *no_http; 238 char *no_http;
@@ -247,48 +265,52 @@ extern struct cgit_repolist cgit_repolist;
247extern struct cgit_context ctx; 265extern struct cgit_context ctx;
248extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 266extern const struct cgit_snapshot_format cgit_snapshot_formats[];
249 267
250extern struct cgit_repo *cgit_add_repo(const char *url); 268extern struct cgit_repo *cgit_add_repo(const char *url);
251extern struct cgit_repo *cgit_get_repoinfo(const char *url); 269extern struct cgit_repo *cgit_get_repoinfo(const char *url);
252extern void cgit_repo_config_cb(const char *name, const char *value); 270extern void cgit_repo_config_cb(const char *name, const char *value);
253 271
254extern int chk_zero(int result, char *msg); 272extern int chk_zero(int result, char *msg);
255extern int chk_positive(int result, char *msg); 273extern int chk_positive(int result, char *msg);
256extern int chk_non_negative(int result, char *msg); 274extern int chk_non_negative(int result, char *msg);
257 275
258extern char *trim_end(const char *str, char c); 276extern char *trim_end(const char *str, char c);
259extern char *strlpart(char *txt, int maxlen); 277extern char *strlpart(char *txt, int maxlen);
260extern char *strrpart(char *txt, int maxlen); 278extern char *strrpart(char *txt, int maxlen);
261 279
262extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 280extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
263extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 281extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
264 int flags, void *cb_data); 282 int flags, void *cb_data);
265 283
266extern void *cgit_free_commitinfo(struct commitinfo *info); 284extern void *cgit_free_commitinfo(struct commitinfo *info);
267 285
268extern int cgit_diff_files(const unsigned char *old_sha1, 286extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 287 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 288 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 289 int *binary, int context, int ignorews,
290 linediff_fn fn);
272 291
273extern void cgit_diff_tree(const unsigned char *old_sha1, 292extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 293 const unsigned char *new_sha1,
275 filepair_fn fn, const char *prefix); 294 filepair_fn fn, const char *prefix, int ignorews);
276 295
277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 296extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
278 297
298__attribute__((format (printf,1,2)))
279extern char *fmt(const char *format,...); 299extern char *fmt(const char *format,...);
280 300
281extern struct commitinfo *cgit_parse_commit(struct commit *commit); 301extern struct commitinfo *cgit_parse_commit(struct commit *commit);
282extern struct taginfo *cgit_parse_tag(struct tag *tag); 302extern struct taginfo *cgit_parse_tag(struct tag *tag);
283extern void cgit_parse_url(const char *url); 303extern void cgit_parse_url(const char *url);
284 304
285extern const char *cgit_repobasename(const char *reponame); 305extern const char *cgit_repobasename(const char *reponame);
286 306
287extern int cgit_parse_snapshots_mask(const char *str); 307extern int cgit_parse_snapshots_mask(const char *str);
288 308
289extern int cgit_open_filter(struct cgit_filter *filter); 309extern int cgit_open_filter(struct cgit_filter *filter);
290extern int cgit_close_filter(struct cgit_filter *filter); 310extern int cgit_close_filter(struct cgit_filter *filter);
291 311
292extern int readfile(const char *path, char **buf, size_t *size); 312extern int readfile(const char *path, char **buf, size_t *size);
293 313
314extern char *expand_macros(const char *txt);
315
294#endif /* CGIT_H */ 316#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..ce78d41 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -74,63 +74,79 @@ clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-filter-overrides:: 94enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path.
102
98enable-index-links:: 103enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 104 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 105 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 106 "commit" and "tree" pages). Default value: "0".
102 107
103enable-log-filecount:: 108enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 109 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 110 modified files for each commit on the repository log page. Default
106 value: "0". 111 value: "0".
107 112
108enable-log-linecount:: 113enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 114 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 115 and removed lines for each commit on the repository log page. Default
111 value: "0". 116 value: "0".
112 117
118enable-remote-branches::
119 Flag which, when set to "1", will make cgit display remote branches
120 in the summary and refs views. Default value: "0". See also:
121 "repo.enable-remote-branches".
122
123enable-subject-links::
124 Flag which, when set to "1", will make cgit use the subject of the
125 parent commit as link text when generating links to parent commits
126 in commit view. Default value: "0". See also:
127 "repo.enable-subject-links".
128
113enable-tree-linenumbers:: 129enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 130 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 131 for plaintext blobs printed in the tree view. Default value: "1".
116 132
117favicon:: 133favicon::
118 Url used as link to a shortcut icon for cgit. If specified, it is 134 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 135 suggested to use the value "/favicon.ico" since certain browsers will
120 ignore other values. Default value: none. 136 ignore other values. Default value: none.
121 137
122footer:: 138footer::
123 The content of the file specified with this option will be included 139 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 140 verbatim at the bottom of all pages (i.e. it replaces the standard
125 "generated by..." message. Default value: none. 141 "generated by..." message. Default value: none.
126 142
127head-include:: 143head-include::
128 The content of the file specified with this option will be included 144 The content of the file specified with this option will be included
129 verbatim in the html HEAD section on all pages. Default value: none. 145 verbatim in the html HEAD section on all pages. Default value: none.
130 146
131header:: 147header::
132 The content of the file specified with this option will be included 148 The content of the file specified with this option will be included
133 verbatim at the top of all pages. Default value: none. 149 verbatim at the top of all pages. Default value: none.
134 150
135include:: 151include::
136 Name of a configfile to include before the rest of the current config- 152 Name of a configfile to include before the rest of the current config-
@@ -140,128 +156,163 @@ index-header::
140 The content of the file specified with this option will be included 156 The content of the file specified with this option will be included
141 verbatim above the repository index. This setting is deprecated, and 157 verbatim above the repository index. This setting is deprecated, and
142 will not be supported by cgit-1.0 (use root-readme instead). Default 158 will not be supported by cgit-1.0 (use root-readme instead). Default
143 value: none. 159 value: none.
144 160
145index-info:: 161index-info::
146 The content of the file specified with this option will be included 162 The content of the file specified with this option will be included
147 verbatim below the heading on the repository index page. This setting 163 verbatim below the heading on the repository index page. This setting
148 is deprecated, and will not be supported by cgit-1.0 (use root-desc 164 is deprecated, and will not be supported by cgit-1.0 (use root-desc
149 instead). Default value: none. 165 instead). Default value: none.
150 166
151local-time:: 167local-time::
152 Flag which, if set to "1", makes cgit print commit and tag times in the 168 Flag which, if set to "1", makes cgit print commit and tag times in the
153 servers timezone. Default value: "0". 169 servers timezone. Default value: "0".
154 170
155logo:: 171logo::
156 Url which specifies the source of an image which will be used as a logo 172 Url which specifies the source of an image which will be used as a logo
157 on all cgit pages. Default value: "/cgit.png". 173 on all cgit pages. Default value: "/cgit.png".
158 174
159logo-link:: 175logo-link::
160 Url loaded when clicking on the cgit logo image. If unspecified the 176 Url loaded when clicking on the cgit logo image. If unspecified the
161 calculated url of the repository index page will be used. Default 177 calculated url of the repository index page will be used. Default
162 value: none. 178 value: none.
163 179
180max-atom-items::
181 Specifies the number of items to display in atom feeds view. Default
182 value: "10".
183
164max-commit-count:: 184max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 185 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 186 value: "50".
167 187
168max-message-length:: 188max-message-length::
169 Specifies the maximum number of commit message characters to display in 189 Specifies the maximum number of commit message characters to display in
170 "log" view. Default value: "80". 190 "log" view. Default value: "80".
171 191
172max-repo-count:: 192max-repo-count::
173 Specifies the number of entries to list per page on therepository 193 Specifies the number of entries to list per page on therepository
174 index page. Default value: "50". 194 index page. Default value: "50".
175 195
176max-repodesc-length:: 196max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 197 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 198 on the repository index page. Default value: "80".
179 199
200max-blob-size::
201 Specifies the maximum size of a blob to display HTML for in KBytes.
202 Default value: "0" (limit disabled).
203
180max-stats:: 204max-stats::
181 Set the default maximum statistics period. Valid values are "week", 205 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 206 "month", "quarter" and "year". If unspecified, statistics are
183 disabled. Default value: none. See also: "repo.max-stats". 207 disabled. Default value: none. See also: "repo.max-stats".
184 208
185mimetype.<ext>:: 209mimetype.<ext>::
186 Set the mimetype for the specified filename extension. This is used 210 Set the mimetype for the specified filename extension. This is used
187 by the `plain` command when returning blob content. 211 by the `plain` command when returning blob content.
188 212
189module-link:: 213module-link::
190 Text which will be used as the formatstring for a hyperlink when a 214 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 215 submodule is printed in a directory listing. The arguments for the
192 formatstring are the path and SHA1 of the submodule commit. Default 216 formatstring are the path and SHA1 of the submodule commit. Default
193 value: "./?repo=%s&page=commit&id=%s" 217 value: "./?repo=%s&page=commit&id=%s"
194 218
195nocache:: 219nocache::
196 If set to the value "1" caching will be disabled. This settings is 220 If set to the value "1" caching will be disabled. This settings is
197 deprecated, and will not be honored starting with cgit-1.0. Default 221 deprecated, and will not be honored starting with cgit-1.0. Default
198 value: "0". 222 value: "0".
199 223
200noplainemail:: 224noplainemail::
201 If set to "1" showing full author email adresses will be disabled. 225 If set to "1" showing full author email adresses will be disabled.
202 Default value: "0". 226 Default value: "0".
203 227
204noheader:: 228noheader::
205 Flag which, when set to "1", will make cgit omit the standard header 229 Flag which, when set to "1", will make cgit omit the standard header
206 on all pages. Default value: none. See also: "embedded". 230 on all pages. Default value: none. See also: "embedded".
207 231
232project-list::
233 A list of subdirectories inside of scan-path, relative to it, that
234 should loaded as git repositories. This must be defined prior to
235 scan-path. Default value: none. See also: scan-path.
236
237readme::
238 Text which will be used as default value for "repo.readme". Default
239 value: none.
240
241remove-suffix::
242 If set to "1" and scan-path is enabled, if any repositories are found
243 with a suffix of ".git", this suffix will be removed for the url and
244 name. Default value: "0". See also: scan-path.
245
208renamelimit:: 246renamelimit::
209 Maximum number of files to consider when detecting renames. The value 247 Maximum number of files to consider when detecting renames. The value
210 "-1" uses the compiletime value in git (for further info, look at 248 "-1" uses the compiletime value in git (for further info, look at
211 `man git-diff`). Default value: "-1". 249 `man git-diff`). Default value: "-1".
212 250
213repo.group:: 251repo.group::
214 Legacy alias for "section". This option is deprecated and will not be 252 Legacy alias for "section". This option is deprecated and will not be
215 supported in cgit-1.0. 253 supported in cgit-1.0.
216 254
217robots:: 255robots::
218 Text used as content for the "robots" meta-tag. Default value: 256 Text used as content for the "robots" meta-tag. Default value:
219 "index, nofollow". 257 "index, nofollow".
220 258
221root-desc:: 259root-desc::
222 Text printed below the heading on the repository index page. Default 260 Text printed below the heading on the repository index page. Default
223 value: "a fast webinterface for the git dscm". 261 value: "a fast webinterface for the git dscm".
224 262
225root-readme:: 263root-readme::
226 The content of the file specified with this option will be included 264 The content of the file specified with this option will be included
227 verbatim below the "about" link on the repository index page. Default 265 verbatim below the "about" link on the repository index page. Default
228 value: none. 266 value: none.
229 267
230root-title:: 268root-title::
231 Text printed as heading on the repository index page. Default value: 269 Text printed as heading on the repository index page. Default value:
232 "Git Repository Browser". 270 "Git Repository Browser".
233 271
234scan-path:: 272scan-path::
235 A path which will be scanned for repositories. If caching is enabled, 273 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 274 the result will be cached as a cgitrc include-file in the cache
237 directory. Default value: none. See also: cache-scanrc-ttl. 275 directory. If project-list has been defined prior to scan-path,
276 scan-path loads only the directories listed in the file pointed to by
277 project-list. Default value: none. See also: cache-scanrc-ttl,
278 project-list.
238 279
239section:: 280section::
240 The name of the current repository section - all repositories defined 281 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 282 after this option will inherit the current section name. Default value:
242 none. 283 none.
243 284
285section-from-path::
286 A number which, if specified before scan-path, specifies how many
287 path elements from each repo path to use as a default section name.
288 If negative, cgit will discard the specified number of path elements
289 above the repo directory. Default value: 0.
290
291side-by-side-diffs::
292 If set to "1" shows side-by-side diffs instead of unidiffs per
293 default. Default value: "0".
294
244snapshots:: 295snapshots::
245 Text which specifies the default set of snapshot formats generated by 296 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 297 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. 298 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
248 299
249source-filter:: 300source-filter::
250 Specifies a command which will be invoked to format plaintext blobs 301 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 302 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 303 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. 304 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: 305 this can be used to implement e.g. syntax highlighting. Default value:
255 none. 306 none.
256 307
257summary-branches:: 308summary-branches::
258 Specifies the number of branches to display in the repository "summary" 309 Specifies the number of branches to display in the repository "summary"
259 view. Default value: "10". 310 view. Default value: "10".
260 311
261summary-log:: 312summary-log::
262 Specifies the number of log entries to display in the repository 313 Specifies the number of log entries to display in the repository
263 "summary" view. Default value: "10". 314 "summary" view. Default value: "10".
264 315
265summary-tags:: 316summary-tags::
266 Specifies the number of tags to display in the repository "summary" 317 Specifies the number of tags to display in the repository "summary"
267 view. Default value: "10". 318 view. Default value: "10".
@@ -283,67 +334,77 @@ repo.about-filter::
283repo.clone-url:: 334repo.clone-url::
284 A list of space-separated urls which can be used to clone this repo. 335 A list of space-separated urls which can be used to clone this repo.
285 Default value: none. 336 Default value: none.
286 337
287repo.commit-filter:: 338repo.commit-filter::
288 Override the default commit-filter. Default value: none. See also: 339 Override the default commit-filter. Default value: none. See also:
289 "enable-filter-overrides". 340 "enable-filter-overrides".
290 341
291repo.defbranch:: 342repo.defbranch::
292 The name of the default branch for this repository. If no such branch 343 The name of the default branch for this repository. If no such branch
293 exists in the repository, the first branch name (when sorted) is used 344 exists in the repository, the first branch name (when sorted) is used
294 as default instead. Default value: "master". 345 as default instead. Default value: "master".
295 346
296repo.desc:: 347repo.desc::
297 The value to show as repository description. Default value: none. 348 The value to show as repository description. Default value: none.
298 349
299repo.enable-log-filecount:: 350repo.enable-log-filecount::
300 A flag which can be used to disable the global setting 351 A flag which can be used to disable the global setting
301 `enable-log-filecount'. Default value: none. 352 `enable-log-filecount'. Default value: none.
302 353
303repo.enable-log-linecount:: 354repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 355 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 356 `enable-log-linecount'. Default value: none.
306 357
358repo.enable-remote-branches::
359 Flag which, when set to "1", will make cgit display remote branches
360 in the summary and refs views. Default value: <enable-remote-branches>.
361
362repo.enable-subject-links::
363 A flag which can be used to override the global setting
364 `enable-subject-links'. Default value: none.
365
307repo.max-stats:: 366repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 367 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 368 to the values specified for the global "max-stats" setting. Default
310 value: none. 369 value: none.
311 370
312repo.name:: 371repo.name::
313 The value to show as repository name. Default value: <repo.url>. 372 The value to show as repository name. Default value: <repo.url>.
314 373
315repo.owner:: 374repo.owner::
316 A value used to identify the owner of the repository. Default value: 375 A value used to identify the owner of the repository. Default value:
317 none. 376 none.
318 377
319repo.path:: 378repo.path::
320 An absolute path to the repository directory. For non-bare repositories 379 An absolute path to the repository directory. For non-bare repositories
321 this is the .git-directory. Default value: none. 380 this is the .git-directory. Default value: none.
322 381
323repo.readme:: 382repo.readme::
324 A path (relative to <repo.path>) which specifies a file to include 383 A path (relative to <repo.path>) which specifies a file to include
325 verbatim as the "About" page for this repo. Default value: none. 384 verbatim as the "About" page for this repo. You may also specify a
385 git refspec by head or by hash by prepending the refspec followed by
386 a colon. For example, "master:docs/readme.mkd" Default value: <readme>.
326 387
327repo.snapshots:: 388repo.snapshots::
328 A mask of allowed snapshot-formats for this repo, restricted by the 389 A mask of allowed snapshot-formats for this repo, restricted by the
329 "snapshots" global setting. Default value: <snapshots>. 390 "snapshots" global setting. Default value: <snapshots>.
330 391
331repo.section:: 392repo.section::
332 Override the current section name for this repository. Default value: 393 Override the current section name for this repository. Default value:
333 none. 394 none.
334 395
335repo.source-filter:: 396repo.source-filter::
336 Override the default source-filter. Default value: none. See also: 397 Override the default source-filter. Default value: none. See also:
337 "enable-filter-overrides". 398 "enable-filter-overrides".
338 399
339repo.url:: 400repo.url::
340 The relative url used to access the repository. This must be the first 401 The relative url used to access the repository. This must be the first
341 setting specified for each repo. Default value: none. 402 setting specified for each repo. Default value: none.
342 403
343 404
344REPOSITORY-SPECIFIC CGITRC FILE 405REPOSITORY-SPECIFIC CGITRC FILE
345------------------------------- 406-------------------------------
346When the option "scan-path" is used to auto-discover git repositories, cgit 407When the option "scan-path" is used to auto-discover git repositories, cgit
347will try to parse the file "cgitrc" within any found repository. Such a 408will try to parse the file "cgitrc" within any found repository. Such a
348repo-specific config file may contain any of the repo-specific options 409repo-specific config file may contain any of the repo-specific options
349described above, except "repo.url" and "repo.path". Additionally, the "filter" 410described above, except "repo.url" and "repo.path". Additionally, the "filter"
@@ -392,49 +453,49 @@ logo=/img/mylogo.png
392# Enable statistics per week, month and quarter 453# Enable statistics per week, month and quarter
393max-stats=quarter 454max-stats=quarter
394 455
395 456
396# Set the title and heading of the repository index page 457# Set the title and heading of the repository index page
397root-title=foobar.com git repositories 458root-title=foobar.com git repositories
398 459
399 460
400# Set a subheading for the repository index page 461# Set a subheading for the repository index page
401root-desc=tracking the foobar development 462root-desc=tracking the foobar development
402 463
403 464
404# Include some more info about foobar.com on the index page 465# Include some more info about foobar.com on the index page
405root-readme=/var/www/htdocs/about.html 466root-readme=/var/www/htdocs/about.html
406 467
407 468
408# Allow download of tar.gz, tar.bz2 and zip-files 469# Allow download of tar.gz, tar.bz2 and zip-files
409snapshots=tar.gz tar.bz2 zip 470snapshots=tar.gz tar.bz2 zip
410 471
411 472
412## 473##
413## List of common mimetypes 474## List of common mimetypes
414## 475##
415 476
416mimetype.git=image/git 477mimetype.gif=image/gif
417mimetype.html=text/html 478mimetype.html=text/html
418mimetype.jpg=image/jpeg 479mimetype.jpg=image/jpeg
419mimetype.jpeg=image/jpeg 480mimetype.jpeg=image/jpeg
420mimetype.pdf=application/pdf 481mimetype.pdf=application/pdf
421mimetype.png=image/png 482mimetype.png=image/png
422mimetype.svg=image/svg+xml 483mimetype.svg=image/svg+xml
423 484
424 485
425## 486##
426## List of repositories. 487## List of repositories.
427## PS: Any repositories listed when section is unset will not be 488## PS: Any repositories listed when section is unset will not be
428## displayed under a section heading 489## displayed under a section heading
429## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 490## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
430## and included like this: 491## and included like this:
431## include=/etc/cgitrepos 492## include=/etc/cgitrepos
432## 493##
433 494
434 495
435repo.url=foo 496repo.url=foo
436repo.path=/pub/git/foo.git 497repo.path=/pub/git/foo.git
437repo.desc=the master foo repository 498repo.desc=the master foo repository
438repo.owner=fooman@foobar.com 499repo.owner=fooman@foobar.com
439repo.readme=info/web/about.html 500repo.readme=info/web/about.html
440 501
@@ -478,24 +539,25 @@ repo.snapshots=0
478# Disable line-counts for this repo 539# Disable line-counts for this repo
479repo.enable-log-linecount=0 540repo.enable-log-linecount=0
480 541
481# Restrict the max statistics period for this repo 542# Restrict the max statistics period for this repo
482repo.max-stats=month 543repo.max-stats=month
483.... 544....
484 545
485 546
486BUGS 547BUGS
487---- 548----
488Comments currently cannot appear on the same line as a setting; the comment 549Comments currently cannot appear on the same line as a setting; the comment
489will be included as part of the value. E.g. this line: 550will be included as part of the value. E.g. this line:
490 551
491 robots=index # allow indexing 552 robots=index # allow indexing
492 553
493will generate the following html element: 554will generate the following html element:
494 555
495 <meta name='robots' content='index # allow indexing'/> 556 <meta name='robots' content='index # allow indexing'/>
496 557
497 558
498 559
499AUTHOR 560AUTHOR
500------ 561------
501Lars Hjemli <hjemli@gmail.com> 562Lars Hjemli <hjemli@gmail.com>
563Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/cmd.c b/cmd.c
index 766f903..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -12,160 +12,160 @@
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "ui-atom.h" 13#include "ui-atom.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15#include "ui-clone.h" 15#include "ui-clone.h"
16#include "ui-commit.h" 16#include "ui-commit.h"
17#include "ui-diff.h" 17#include "ui-diff.h"
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 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{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
67static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
68{ 68{
69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
71} 71}
72 72
73static void ls_cache_fn(struct cgit_context *ctx) 73static void ls_cache_fn(struct cgit_context *ctx)
74{ 74{
75 ctx->page.mimetype = "text/plain"; 75 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 76 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 77 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 78 cache_ls(ctx->cfg.cache_root);
79} 79}
80 80
81static void objects_fn(struct cgit_context *ctx) 81static void objects_fn(struct cgit_context *ctx)
82{ 82{
83 cgit_clone_objects(ctx); 83 cgit_clone_objects(ctx);
84} 84}
85 85
86static void repolist_fn(struct cgit_context *ctx) 86static void repolist_fn(struct cgit_context *ctx)
87{ 87{
88 cgit_print_repolist(); 88 cgit_print_repolist();
89} 89}
90 90
91static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
92{ 92{
93 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 94}
95 95
96static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
97{ 97{
98 cgit_print_plain(ctx); 98 cgit_print_plain(ctx);
99} 99}
100 100
101static void refs_fn(struct cgit_context *ctx) 101static void refs_fn(struct cgit_context *ctx)
102{ 102{
103 cgit_print_refs(); 103 cgit_print_refs();
104} 104}
105 105
106static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
107{ 107{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 109 ctx->repo->snapshots, ctx->qry.nohead);
110} 110}
111 111
112static void stats_fn(struct cgit_context *ctx) 112static void stats_fn(struct cgit_context *ctx)
113{ 113{
114 cgit_show_stats(ctx); 114 cgit_show_stats(ctx);
115} 115}
116 116
117static void summary_fn(struct cgit_context *ctx) 117static void summary_fn(struct cgit_context *ctx)
118{ 118{
119 cgit_print_summary(); 119 cgit_print_summary();
120} 120}
121 121
122static void tag_fn(struct cgit_context *ctx) 122static void tag_fn(struct cgit_context *ctx)
123{ 123{
124 cgit_print_tag(ctx->qry.sha1); 124 cgit_print_tag(ctx->qry.sha1);
125} 125}
126 126
127static void tree_fn(struct cgit_context *ctx) 127static void tree_fn(struct cgit_context *ctx)
128{ 128{
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 130}
131 131
132#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 134
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 136{
137 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1), 153 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1, 1),
157 }; 157 };
158 int i; 158 int i;
159 159
160 if (ctx->qry.page == NULL) { 160 if (ctx->qry.page == NULL) {
161 if (ctx->repo) 161 if (ctx->repo)
162 ctx->qry.page = "summary"; 162 ctx->qry.page = "summary";
163 else 163 else
164 ctx->qry.page = "repolist"; 164 ctx->qry.page = "repolist";
165 } 165 }
166 166
167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
168 if (!strcmp(ctx->qry.page, cmds[i].name)) 168 if (!strcmp(ctx->qry.page, cmds[i].name))
169 return &cmds[i]; 169 return &cmds[i];
170 return NULL; 170 return NULL;
171} 171}
diff --git a/cmd.h b/cmd.h
index ec9e691..8dc01bd 100644
--- a/cmd.h
+++ b/cmd.h
@@ -1,15 +1,16 @@
1#ifndef CMD_H 1#ifndef CMD_H
2#define CMD_H 2#define CMD_H
3 3
4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx); 4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx);
5 5
6struct cgit_cmd { 6struct cgit_cmd {
7 const char *name; 7 const char *name;
8 cgit_cmd_fn fn; 8 cgit_cmd_fn fn;
9 unsigned int want_repo:1, 9 unsigned int want_repo:1,
10 want_layout:1; 10 want_layout:1,
11 want_vpath:1;
11}; 12};
12 13
13extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); 14extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx);
14 15
15#endif /* CMD_H */ 16#endif /* CMD_H */
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
index 165a533..110c609 100755
--- a/filters/commit-links.sh
+++ b/filters/commit-links.sh
@@ -1,12 +1,14 @@
1#!/bin/sh 1#!/bin/sh
2# This script can be used to generate links in commit messages - the first 2# This script can be used to generate links in commit messages.
3# sed expression generates links to commits referenced by their SHA1, while
4# the second expression generates links to a fictional bugtracker.
5# 3#
6# To use this script, refer to this file with either the commit-filter or the 4# To use this script, refer to this file with either the commit-filter or the
7# repo.commit-filter options in cgitrc. 5# repo.commit-filter options in cgitrc.
8 6
9sed -re ' 7# This expression generates links to commits referenced by their SHA1.
10s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g 8regex=$regex'
11s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g 9s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g'
12' 10# This expression generates links to a fictional bugtracker.
11regex=$regex'
12s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g'
13
14sed -re "$regex"
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -1,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 87b50542a08ac6caa083ddc376e674424e37940
diff --git a/html.c b/html.c
index d86b2c1..1305910 100644
--- a/html.c
+++ b/html.c
@@ -1,39 +1,65 @@
1/* html.c: helper functions for html output 1/* html.c: helper functions for html output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <unistd.h> 9#include <unistd.h>
10#include <stdio.h> 10#include <stdio.h>
11#include <stdlib.h> 11#include <stdlib.h>
12#include <stdarg.h> 12#include <stdarg.h>
13#include <string.h> 13#include <string.h>
14#include <errno.h> 14#include <errno.h>
15 15
16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
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
28 va_start(args, format); 54 va_start(args, format);
29 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); 55 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
30 va_end(args); 56 va_end(args);
31 if (len>sizeof(buf[bufidx])) { 57 if (len>sizeof(buf[bufidx])) {
32 fprintf(stderr, "[html.c] string truncated: %s\n", format); 58 fprintf(stderr, "[html.c] string truncated: %s\n", format);
33 exit(1); 59 exit(1);
34 } 60 }
35 return buf[bufidx]; 61 return buf[bufidx];
36} 62}
37 63
38void html_raw(const char *data, size_t size) 64void html_raw(const char *data, size_t size)
39{ 65{
@@ -42,251 +68,253 @@ void html_raw(const char *data, size_t size)
42 68
43void html(const char *txt) 69void html(const char *txt)
44{ 70{
45 write(htmlfd, txt, strlen(txt)); 71 write(htmlfd, txt, strlen(txt));
46} 72}
47 73
48void htmlf(const char *format, ...) 74void htmlf(const char *format, ...)
49{ 75{
50 static char buf[65536]; 76 static char buf[65536];
51 va_list args; 77 va_list args;
52 78
53 va_start(args, format); 79 va_start(args, 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 html_raw(txt, t - txt);
73 if (c=='>') 99 if (c=='>')
74 html("&gt;"); 100 html("&gt;");
75 else if (c=='<') 101 else if (c=='<')
76 html("&lt;"); 102 html("&lt;");
77 else if (c=='&') 103 else if (c=='&')
78 html("&amp;"); 104 html("&amp;");
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 html_raw(txt, t - txt);
94 if (c=='>') 120 if (c=='>')
95 html("&gt;"); 121 html("&gt;");
96 else if (c=='<') 122 else if (c=='<')
97 html("&lt;"); 123 html("&lt;");
98 else if (c=='&') 124 else if (c=='&')
99 html("&amp;"); 125 html("&amp;");
100 txt = t+1; 126 txt = t+1;
101 } 127 }
102 t++; 128 t++;
103 } 129 }
104 if (t!=txt) 130 if (t!=txt)
105 write(htmlfd, txt, t - txt); 131 html_raw(txt, t - txt);
106 if (len<0) 132 if (len<0)
107 html("..."); 133 html("...");
108} 134}
109 135
110void html_attr(char *txt) 136void html_attr(const char *txt)
111{ 137{
112 char *t = txt; 138 const char *t = txt;
113 while(t && *t){ 139 while(t && *t){
114 int c = *t; 140 int c = *t;
115 if (c=='<' || c=='>' || c=='\'' || c=='\"') { 141 if (c=='<' || c=='>' || c=='\'' || c=='\"') {
116 write(htmlfd, txt, t - txt); 142 html_raw(txt, t - txt);
117 if (c=='>') 143 if (c=='>')
118 html("&gt;"); 144 html("&gt;");
119 else if (c=='<') 145 else if (c=='<')
120 html("&lt;"); 146 html("&lt;");
121 else if (c=='\'') 147 else if (c=='\'')
122 html("&#x27;"); 148 html("&#x27;");
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];
139 write(htmlfd, txt, t - txt); 165 if (e && c!='+' && c!='&' && c!='+') {
140 write(htmlfd, fmt("%%%2x", c), 3); 166 html_raw(txt, t - txt);
167 html_raw(e, 3);
141 txt = t+1; 168 txt = t+1;
142 } 169 }
143 t++; 170 t++;
144 } 171 }
145 if (t!=txt) 172 if (t!=txt)
146 html(txt); 173 html(txt);
147} 174}
148 175
149void html_url_arg(char *txt) 176void html_url_arg(const char *txt)
150{ 177{
151 char *t = txt; 178 const char *t = txt;
152 while(t && *t){ 179 while(t && *t){
153 int c = *t; 180 int c = *t;
154 if (c=='"' || c=='#' || c=='%' || c=='&' || c=='\'' || c=='+' || c=='?') { 181 const char *e = url_escape_table[c];
155 write(htmlfd, txt, t - txt); 182 if (e) {
156 write(htmlfd, fmt("%%%2x", c), 3); 183 html_raw(txt, t - txt);
184 html_raw(e, 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("'>");
199} 227}
200 228
201void html_link_close(void) 229void html_link_close(void)
202{ 230{
203 html("</a>"); 231 html("</a>");
204} 232}
205 233
206void html_fileperm(unsigned short mode) 234void html_fileperm(unsigned short mode)
207{ 235{
208 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), 236 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
209 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); 237 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
210} 238}
211 239
212int html_include(const char *filename) 240int html_include(const char *filename)
213{ 241{
214 FILE *f; 242 FILE *f;
215 char buf[4096]; 243 char buf[4096];
216 size_t len; 244 size_t len;
217 245
218 if (!(f = fopen(filename, "r"))) { 246 if (!(f = fopen(filename, "r"))) {
219 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n", 247 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
220 filename, strerror(errno), errno); 248 filename, strerror(errno), errno);
221 return -1; 249 return -1;
222 } 250 }
223 while((len = fread(buf, 1, 4096, f)) > 0) 251 while((len = fread(buf, 1, 4096, f)) > 0)
224 write(htmlfd, buf, len); 252 html_raw(buf, len);
225 fclose(f); 253 fclose(f);
226 return 0; 254 return 0;
227} 255}
228 256
229int hextoint(char c) 257int hextoint(char c)
230{ 258{
231 if (c >= 'a' && c <= 'f') 259 if (c >= 'a' && c <= 'f')
232 return 10 + c - 'a'; 260 return 10 + c - 'a';
233 else if (c >= 'A' && c <= 'F') 261 else if (c >= 'A' && c <= 'F')
234 return 10 + c - 'A'; 262 return 10 + c - 'A';
235 else if (c >= '0' && c <= '9') 263 else if (c >= '0' && c <= '9')
236 return c - '0'; 264 return c - '0';
237 else 265 else
238 return -1; 266 return -1;
239} 267}
240 268
241char *convert_query_hexchar(char *txt) 269char *convert_query_hexchar(char *txt)
242{ 270{
243 int d1, d2, n; 271 int d1, d2, n;
244 n = strlen(txt); 272 n = strlen(txt);
245 if (n < 3) { 273 if (n < 3) {
246 *txt = '\0'; 274 *txt = '\0';
247 return txt-1; 275 return txt-1;
248 } 276 }
249 d1 = hextoint(*(txt+1)); 277 d1 = hextoint(*(txt+1));
250 d2 = hextoint(*(txt+2)); 278 d2 = hextoint(*(txt+2));
251 if (d1<0 || d2<0) { 279 if (d1<0 || d2<0) {
252 memmove(txt, txt+3, n-3); 280 memmove(txt, txt+3, n-3);
253 return txt-1; 281 return txt-1;
254 } else { 282 } else {
255 *txt = d1 * 16 + d2; 283 *txt = d1 * 16 + d2;
256 memmove(txt+1, txt+3, n-2); 284 memmove(txt+1, txt+3, n-2);
257 return txt; 285 return txt;
258 } 286 }
259} 287}
260 288
261int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)) 289int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value))
262{ 290{
263 char *t, *value = NULL, c; 291 char *t, *txt, *value = NULL, c;
264 292
265 if (!txt) 293 if (!txt_)
266 return 0; 294 return 0;
267 295
268 t = txt = strdup(txt); 296 t = txt = strdup(txt_);
269 if (t == NULL) { 297 if (t == NULL) {
270 printf("Out of memory\n"); 298 printf("Out of memory\n");
271 exit(1); 299 exit(1);
272 } 300 }
273 while((c=*t) != '\0') { 301 while((c=*t) != '\0') {
274 if (c=='=') { 302 if (c=='=') {
275 *t = '\0'; 303 *t = '\0';
276 value = t+1; 304 value = t+1;
277 } else if (c=='+') { 305 } else if (c=='+') {
278 *t = ' '; 306 *t = ' ';
279 } else if (c=='%') { 307 } else if (c=='%') {
280 t = convert_query_hexchar(t); 308 t = convert_query_hexchar(t);
281 } else if (c=='&') { 309 } else if (c=='&') {
282 *t = '\0'; 310 *t = '\0';
283 (*fn)(txt, value); 311 (*fn)(txt, value);
284 txt = t+1; 312 txt = t+1;
285 value = NULL; 313 value = NULL;
286 } 314 }
287 t++; 315 t++;
288 } 316 }
289 if (t!=txt) 317 if (t!=txt)
290 (*fn)(txt, value); 318 (*fn)(txt, value);
291 return 0; 319 return 0;
292} 320}
diff --git a/html.h b/html.h
index a55d4b2..1135fb8 100644
--- a/html.h
+++ b/html.h
@@ -1,24 +1,27 @@
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);
8
9__attribute__((format (printf,1,2)))
8extern void htmlf(const char *format,...); 10extern void htmlf(const char *format,...);
11
9extern void html_status(int code, const char *msg, int more_headers); 12extern void html_status(int code, const char *msg, int more_headers);
10extern void html_txt(char *txt); 13extern void html_txt(const char *txt);
11extern void html_ntxt(int len, char *txt); 14extern void html_ntxt(int len, const char *txt);
12extern void html_attr(char *txt); 15extern void html_attr(const char *txt);
13extern void html_url_path(char *txt); 16extern void html_url_path(const char *txt);
14extern void html_url_arg(char *txt); 17extern void html_url_arg(const char *txt);
15extern void html_hidden(char *name, char *value); 18extern void html_hidden(const char *name, const char *value);
16extern void html_option(char *value, char *text, char *selected_value); 19extern void html_option(const char *value, const char *text, const char *selected_value);
17extern void html_link_open(char *url, char *title, char *class); 20extern void html_link_open(const char *url, const char *title, const char *class);
18extern void html_link_close(void); 21extern void html_link_close(void);
19extern void html_fileperm(unsigned short mode); 22extern void html_fileperm(unsigned short mode);
20extern int html_include(const char *filename); 23extern int html_include(const char *filename);
21 24
22extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)); 25extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
23 26
24#endif /* HTML_H */ 27#endif /* HTML_H */
diff --git a/scan-tree.c b/scan-tree.c
index dbca797..b5b50f3 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,110 +1,171 @@
1/* scan-tree.c
2 *
3 * Copyright (C) 2008-2009 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 *
6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text)
8 */
9
1#include "cgit.h" 10#include "cgit.h"
2#include "configfile.h" 11#include "configfile.h"
3#include "html.h" 12#include "html.h"
4 13
5#define MAX_PATH 4096 14#define MAX_PATH 4096
6 15
7/* return 1 if path contains a objects/ directory and a HEAD file */ 16/* return 1 if path contains a objects/ directory and a HEAD file */
8static int is_git_dir(const char *path) 17static int is_git_dir(const char *path)
9{ 18{
10 struct stat st; 19 struct stat st;
11 static char buf[MAX_PATH]; 20 static char buf[MAX_PATH];
12 21
13 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) { 22 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) {
14 fprintf(stderr, "Insanely long path: %s\n", path); 23 fprintf(stderr, "Insanely long path: %s\n", path);
15 return 0; 24 return 0;
16 } 25 }
17 if (stat(buf, &st)) { 26 if (stat(buf, &st)) {
18 if (errno != ENOENT) 27 if (errno != ENOENT)
19 fprintf(stderr, "Error checking path %s: %s (%d)\n", 28 fprintf(stderr, "Error checking path %s: %s (%d)\n",
20 path, strerror(errno), errno); 29 path, strerror(errno), errno);
21 return 0; 30 return 0;
22 } 31 }
23 if (!S_ISDIR(st.st_mode)) 32 if (!S_ISDIR(st.st_mode))
24 return 0; 33 return 0;
25 34
26 sprintf(buf, "%s/HEAD", path); 35 sprintf(buf, "%s/HEAD", path);
27 if (stat(buf, &st)) { 36 if (stat(buf, &st)) {
28 if (errno != ENOENT) 37 if (errno != ENOENT)
29 fprintf(stderr, "Error checking path %s: %s (%d)\n", 38 fprintf(stderr, "Error checking path %s: %s (%d)\n",
30 path, strerror(errno), errno); 39 path, strerror(errno), errno);
31 return 0; 40 return 0;
32 } 41 }
33 if (!S_ISREG(st.st_mode)) 42 if (!S_ISREG(st.st_mode))
34 return 0; 43 return 0;
35 44
36 return 1; 45 return 1;
37} 46}
38 47
39struct cgit_repo *repo; 48struct cgit_repo *repo;
40repo_config_fn config_fn; 49repo_config_fn config_fn;
50char *owner;
41 51
42static void repo_config(const char *name, const char *value) 52static void repo_config(const char *name, const char *value)
43{ 53{
44 config_fn(repo, name, value); 54 config_fn(repo, name, value);
45} 55}
46 56
57static int git_owner_config(const char *key, const char *value, void *cb)
58{
59 if (!strcmp(key, "gitweb.owner"))
60 owner = xstrdup(value);
61 return 0;
62}
63
64static char *xstrrchr(char *s, char *from, int c)
65{
66 while (from >= s && *from != c)
67 from--;
68 return from < s ? NULL : from;
69}
70
47static void add_repo(const char *base, const char *path, repo_config_fn fn) 71static void add_repo(const char *base, const char *path, repo_config_fn fn)
48{ 72{
49 struct stat st; 73 struct stat st;
50 struct passwd *pwd; 74 struct passwd *pwd;
51 char *p; 75 char *rel, *p, *slash;
76 int n;
52 size_t size; 77 size_t size;
53 78
54 if (stat(path, &st)) { 79 if (stat(path, &st)) {
55 fprintf(stderr, "Error accessing %s: %s (%d)\n", 80 fprintf(stderr, "Error accessing %s: %s (%d)\n",
56 path, strerror(errno), errno); 81 path, strerror(errno), errno);
57 return; 82 return;
58 } 83 }
59 if ((pwd = getpwuid(st.st_uid)) == NULL) { 84 if (!stat(fmt("%s/noweb", path), &st))
60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
61 path, strerror(errno), errno);
62 return; 85 return;
63 } 86
87 owner = NULL;
88 if (ctx.cfg.enable_gitweb_owner)
89 git_config_from_file(git_owner_config, fmt("%s/config", path), NULL);
64 if (base == path) 90 if (base == path)
65 p = fmt("%s", path); 91 rel = xstrdup(fmt("%s", path));
66 else 92 else
67 p = fmt("%s", path + strlen(base) + 1); 93 rel = xstrdup(fmt("%s", path + strlen(base) + 1));
68 94
69 if (!strcmp(p + strlen(p) - 5, "/.git")) 95 if (!strcmp(rel + strlen(rel) - 5, "/.git"))
70 p[strlen(p) - 5] = '\0'; 96 rel[strlen(rel) - 5] = '\0';
71 97
72 repo = cgit_add_repo(xstrdup(p)); 98 repo = cgit_add_repo(rel);
99 if (ctx.cfg.remove_suffix)
100 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git"))
101 *p = '\0';
73 repo->name = repo->url; 102 repo->name = repo->url;
74 repo->path = xstrdup(path); 103 repo->path = xstrdup(path);
75 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 104 while (!owner) {
76 if (p) 105 if ((pwd = getpwuid(st.st_uid)) == NULL) {
106 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
107 path, strerror(errno), errno);
108 break;
109 }
110 if (pwd->pw_gecos)
111 if ((p = strchr(pwd->pw_gecos, ',')))
77 *p = '\0'; 112 *p = '\0';
78 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 113 owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
114 }
115 repo->owner = owner;
79 116
80 p = fmt("%s/description", path); 117 p = fmt("%s/description", path);
81 if (!stat(p, &st)) 118 if (!stat(p, &st))
82 readfile(p, &repo->desc, &size); 119 readfile(p, &repo->desc, &size);
83 120
121 if (!repo->readme) {
84 p = fmt("%s/README.html", path); 122 p = fmt("%s/README.html", path);
85 if (!stat(p, &st)) 123 if (!stat(p, &st))
86 repo->readme = "README.html"; 124 repo->readme = "README.html";
125 }
126 if (ctx.cfg.section_from_path) {
127 n = ctx.cfg.section_from_path;
128 if (n > 0) {
129 slash = rel;
130 while (slash && n && (slash = strchr(slash, '/')))
131 n--;
132 } else {
133 slash = rel + strlen(rel);
134 while (slash && n && (slash = xstrrchr(rel, slash, '/')))
135 n++;
136 }
137 if (slash && !n) {
138 *slash = '\0';
139 repo->section = xstrdup(rel);
140 *slash = '/';
141 if (!prefixcmp(repo->name, repo->section)) {
142 repo->name += strlen(repo->section);
143 if (*repo->name == '/')
144 repo->name++;
145 }
146 }
147 }
87 148
88 p = fmt("%s/cgitrc", path); 149 p = fmt("%s/cgitrc", path);
89 if (!stat(p, &st)) { 150 if (!stat(p, &st)) {
90 config_fn = fn; 151 config_fn = fn;
91 parse_configfile(xstrdup(p), &repo_config); 152 parse_configfile(xstrdup(p), &repo_config);
92 } 153 }
93} 154}
94 155
95static void scan_path(const char *base, const char *path, repo_config_fn fn) 156static void scan_path(const char *base, const char *path, repo_config_fn fn)
96{ 157{
97 DIR *dir; 158 DIR *dir;
98 struct dirent *ent; 159 struct dirent *ent;
99 char *buf; 160 char *buf;
100 struct stat st; 161 struct stat st;
101 162
102 if (is_git_dir(path)) { 163 if (is_git_dir(path)) {
103 add_repo(base, path, fn); 164 add_repo(base, path, fn);
104 return; 165 return;
105 } 166 }
106 if (is_git_dir(fmt("%s/.git", path))) { 167 if (is_git_dir(fmt("%s/.git", path))) {
107 add_repo(base, fmt("%s/.git", path), fn); 168 add_repo(base, fmt("%s/.git", path), fn);
108 return; 169 return;
109 } 170 }
110 dir = opendir(path); 171 dir = opendir(path);
@@ -119,28 +180,56 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
119 continue; 180 continue;
120 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 181 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
121 continue; 182 continue;
122 } 183 }
123 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 184 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
124 if (!buf) { 185 if (!buf) {
125 fprintf(stderr, "Alloc error on %s: %s (%d)\n", 186 fprintf(stderr, "Alloc error on %s: %s (%d)\n",
126 path, strerror(errno), errno); 187 path, strerror(errno), errno);
127 exit(1); 188 exit(1);
128 } 189 }
129 sprintf(buf, "%s/%s", path, ent->d_name); 190 sprintf(buf, "%s/%s", path, ent->d_name);
130 if (stat(buf, &st)) { 191 if (stat(buf, &st)) {
131 fprintf(stderr, "Error checking path %s: %s (%d)\n", 192 fprintf(stderr, "Error checking path %s: %s (%d)\n",
132 buf, strerror(errno), errno); 193 buf, strerror(errno), errno);
133 free(buf); 194 free(buf);
134 continue; 195 continue;
135 } 196 }
136 if (S_ISDIR(st.st_mode)) 197 if (S_ISDIR(st.st_mode))
137 scan_path(base, buf, fn); 198 scan_path(base, buf, fn);
138 free(buf); 199 free(buf);
139 } 200 }
140 closedir(dir); 201 closedir(dir);
141} 202}
142 203
204#define lastc(s) s[strlen(s) - 1]
205
206void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
207{
208 char line[MAX_PATH * 2], *z;
209 FILE *projects;
210 int err;
211
212 projects = fopen(projectsfile, "r");
213 if (!projects) {
214 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
215 projectsfile, strerror(errno), errno);
216 }
217 while (fgets(line, sizeof(line), projects) != NULL) {
218 for (z = &lastc(line);
219 strlen(line) && strchr("\n\r", *z);
220 z = &lastc(line))
221 *z = '\0';
222 if (strlen(line))
223 scan_path(path, fmt("%s/%s", path, line), fn);
224 }
225 if ((err = ferror(projects))) {
226 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
227 projectsfile, strerror(err), err);
228 }
229 fclose(projects);
230}
231
143void scan_tree(const char *path, repo_config_fn fn) 232void scan_tree(const char *path, repo_config_fn fn)
144{ 233{
145 scan_path(path, path, fn); 234 scan_path(path, path, fn);
146} 235}
diff --git a/scan-tree.h b/scan-tree.h
index 11539f4..1afbd4b 100644
--- a/scan-tree.h
+++ b/scan-tree.h
@@ -1,3 +1,2 @@
1 1extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
2
3extern void scan_tree(const char *path, repo_config_fn fn); 2extern void scan_tree(const char *path, repo_config_fn fn);
diff --git a/shared.c b/shared.c
index 6adf2b6..72ac140 100644
--- a/shared.c
+++ b/shared.c
@@ -1,88 +1,89 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback functions
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 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd;
14 13
15int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
16{ 15{
17 if (result != 0) 16 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
19 return result; 18 return result;
20} 19}
21 20
22int chk_positive(int result, char *msg) 21int chk_positive(int result, char *msg)
23{ 22{
24 if (result <= 0) 23 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 24 die("%s: %s", msg, strerror(errno));
26 return result; 25 return result;
27} 26}
28 27
29int chk_non_negative(int result, char *msg) 28int chk_non_negative(int result, char *msg)
30{ 29{
31 if (result < 0) 30 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 31 die("%s: %s",msg, strerror(errno));
33 return result; 32 return result;
34} 33}
35 34
36struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
37{ 36{
38 struct cgit_repo *ret; 37 struct cgit_repo *ret;
39 38
40 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
43 else 42 else
44 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 45 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
48 } 47 }
49 48
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
52 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
53 ret->name = ret->url; 52 ret->name = ret->url;
54 ret->path = NULL; 53 ret->path = NULL;
55 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
56 ret->owner = NULL; 55 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 57 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links;
62 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 65 ret->readme = ctx.cfg.readme;
65 ret->mtime = -1; 66 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter; 67 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter; 68 ret->commit_filter = ctx.cfg.commit_filter;
68 ret->source_filter = ctx.cfg.source_filter; 69 ret->source_filter = ctx.cfg.source_filter;
69 return ret; 70 return ret;
70} 71}
71 72
72struct cgit_repo *cgit_get_repoinfo(const char *url) 73struct cgit_repo *cgit_get_repoinfo(const char *url)
73{ 74{
74 int i; 75 int i;
75 struct cgit_repo *repo; 76 struct cgit_repo *repo;
76 77
77 for (i=0; i<cgit_repolist.count; i++) { 78 for (i=0; i<cgit_repolist.count; i++) {
78 repo = &cgit_repolist.repos[i]; 79 repo = &cgit_repolist.repos[i];
79 if (!strcmp(repo->url, url)) 80 if (!strcmp(repo->url, url))
80 return repo; 81 return repo;
81 } 82 }
82 return NULL; 83 return NULL;
83} 84}
84 85
85void *cgit_free_commitinfo(struct commitinfo *info) 86void *cgit_free_commitinfo(struct commitinfo *info)
86{ 87{
87 free(info->author); 88 free(info->author);
88 free(info->author_email); 89 free(info->author_email);
@@ -241,125 +242,131 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
241 242
242 /* we have a complete line */ 243 /* we have a complete line */
243 if (!diffbuf) { 244 if (!diffbuf) {
244 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 245 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
245 continue; 246 continue;
246 } 247 }
247 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 248 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
248 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 249 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
249 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 250 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
250 free(diffbuf); 251 free(diffbuf);
251 diffbuf = NULL; 252 diffbuf = NULL;
252 buflen = 0; 253 buflen = 0;
253 } 254 }
254 if (diffbuf) { 255 if (diffbuf) {
255 ((linediff_fn)priv)(diffbuf, buflen); 256 ((linediff_fn)priv)(diffbuf, buflen);
256 free(diffbuf); 257 free(diffbuf);
257 diffbuf = NULL; 258 diffbuf = NULL;
258 buflen = 0; 259 buflen = 0;
259 } 260 }
260 return 0; 261 return 0;
261} 262}
262 263
263int cgit_diff_files(const unsigned char *old_sha1, 264int cgit_diff_files(const unsigned char *old_sha1,
264 const unsigned char *new_sha1, unsigned long *old_size, 265 const unsigned char *new_sha1, unsigned long *old_size,
265 unsigned long *new_size, int *binary, linediff_fn fn) 266 unsigned long *new_size, int *binary, int context,
267 int ignorews, linediff_fn fn)
266{ 268{
267 mmfile_t file1, file2; 269 mmfile_t file1, file2;
268 xpparam_t diff_params; 270 xpparam_t diff_params;
269 xdemitconf_t emit_params; 271 xdemitconf_t emit_params;
270 xdemitcb_t emit_cb; 272 xdemitcb_t emit_cb;
271 273
272 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 274 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
273 return 1; 275 return 1;
274 276
275 *old_size = file1.size; 277 *old_size = file1.size;
276 *new_size = file2.size; 278 *new_size = file2.size;
277 279
278 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 280 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
279 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 281 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
280 *binary = 1; 282 *binary = 1;
281 if (file1.size) 283 if (file1.size)
282 free(file1.ptr); 284 free(file1.ptr);
283 if (file2.size) 285 if (file2.size)
284 free(file2.ptr); 286 free(file2.ptr);
285 return 0; 287 return 0;
286 } 288 }
287 289
288 memset(&diff_params, 0, sizeof(diff_params)); 290 memset(&diff_params, 0, sizeof(diff_params));
289 memset(&emit_params, 0, sizeof(emit_params)); 291 memset(&emit_params, 0, sizeof(emit_params));
290 memset(&emit_cb, 0, sizeof(emit_cb)); 292 memset(&emit_cb, 0, sizeof(emit_cb));
291 diff_params.flags = XDF_NEED_MINIMAL; 293 diff_params.flags = XDF_NEED_MINIMAL;
292 emit_params.ctxlen = 3; 294 if (ignorews)
295 diff_params.flags |= XDF_IGNORE_WHITESPACE;
296 emit_params.ctxlen = context > 0 ? context : 3;
293 emit_params.flags = XDL_EMIT_FUNCNAMES; 297 emit_params.flags = XDL_EMIT_FUNCNAMES;
294 emit_cb.outf = filediff_cb; 298 emit_cb.outf = filediff_cb;
295 emit_cb.priv = fn; 299 emit_cb.priv = fn;
296 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 300 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
297 if (file1.size) 301 if (file1.size)
298 free(file1.ptr); 302 free(file1.ptr);
299 if (file2.size) 303 if (file2.size)
300 free(file2.ptr); 304 free(file2.ptr);
301 return 0; 305 return 0;
302} 306}
303 307
304void cgit_diff_tree(const unsigned char *old_sha1, 308void cgit_diff_tree(const unsigned char *old_sha1,
305 const unsigned char *new_sha1, 309 const unsigned char *new_sha1,
306 filepair_fn fn, const char *prefix) 310 filepair_fn fn, const char *prefix, int ignorews)
307{ 311{
308 struct diff_options opt; 312 struct diff_options opt;
309 int ret; 313 int ret;
310 int prefixlen; 314 int prefixlen;
311 315
312 diff_setup(&opt); 316 diff_setup(&opt);
313 opt.output_format = DIFF_FORMAT_CALLBACK; 317 opt.output_format = DIFF_FORMAT_CALLBACK;
314 opt.detect_rename = 1; 318 opt.detect_rename = 1;
315 opt.rename_limit = ctx.cfg.renamelimit; 319 opt.rename_limit = ctx.cfg.renamelimit;
316 DIFF_OPT_SET(&opt, RECURSIVE); 320 DIFF_OPT_SET(&opt, RECURSIVE);
321 if (ignorews)
322 DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
317 opt.format_callback = cgit_diff_tree_cb; 323 opt.format_callback = cgit_diff_tree_cb;
318 opt.format_callback_data = fn; 324 opt.format_callback_data = fn;
319 if (prefix) { 325 if (prefix) {
320 opt.nr_paths = 1; 326 opt.nr_paths = 1;
321 opt.paths = &prefix; 327 opt.paths = &prefix;
322 prefixlen = strlen(prefix); 328 prefixlen = strlen(prefix);
323 opt.pathlens = &prefixlen; 329 opt.pathlens = &prefixlen;
324 } 330 }
325 diff_setup_done(&opt); 331 diff_setup_done(&opt);
326 332
327 if (old_sha1 && !is_null_sha1(old_sha1)) 333 if (old_sha1 && !is_null_sha1(old_sha1))
328 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 334 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
329 else 335 else
330 ret = diff_root_tree_sha1(new_sha1, "", &opt); 336 ret = diff_root_tree_sha1(new_sha1, "", &opt);
331 diffcore_std(&opt); 337 diffcore_std(&opt);
332 diff_flush(&opt); 338 diff_flush(&opt);
333} 339}
334 340
335void cgit_diff_commit(struct commit *commit, filepair_fn fn) 341void cgit_diff_commit(struct commit *commit, filepair_fn fn)
336{ 342{
337 unsigned char *old_sha1 = NULL; 343 unsigned char *old_sha1 = NULL;
338 344
339 if (commit->parents) 345 if (commit->parents)
340 old_sha1 = commit->parents->item->object.sha1; 346 old_sha1 = commit->parents->item->object.sha1;
341 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 347 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL,
348 ctx.qry.ignorews);
342} 349}
343 350
344int cgit_parse_snapshots_mask(const char *str) 351int cgit_parse_snapshots_mask(const char *str)
345{ 352{
346 const struct cgit_snapshot_format *f; 353 const struct cgit_snapshot_format *f;
347 static const char *delim = " \t,:/|;"; 354 static const char *delim = " \t,:/|;";
348 int tl, sl, rv = 0; 355 int tl, sl, rv = 0;
349 356
350 /* favor legacy setting */ 357 /* favor legacy setting */
351 if(atoi(str)) 358 if(atoi(str))
352 return 1; 359 return 1;
353 for(;;) { 360 for(;;) {
354 str += strspn(str,delim); 361 str += strspn(str,delim);
355 tl = strcspn(str,delim); 362 tl = strcspn(str,delim);
356 if (!tl) 363 if (!tl)
357 break; 364 break;
358 for (f = cgit_snapshot_formats; f->suffix; f++) { 365 for (f = cgit_snapshot_formats; f->suffix; f++) {
359 sl = strlen(f->suffix); 366 sl = strlen(f->suffix);
360 if((tl == sl && !strncmp(f->suffix, str, tl)) || 367 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
361 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 368 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
362 rv |= f->bit; 369 rv |= f->bit;
363 break; 370 break;
364 } 371 }
365 } 372 }
@@ -409,24 +416,95 @@ int cgit_close_filter(struct cgit_filter *filter)
409int readfile(const char *path, char **buf, size_t *size) 416int readfile(const char *path, char **buf, size_t *size)
410{ 417{
411 int fd, e; 418 int fd, e;
412 struct stat st; 419 struct stat st;
413 420
414 fd = open(path, O_RDONLY); 421 fd = open(path, O_RDONLY);
415 if (fd == -1) 422 if (fd == -1)
416 return errno; 423 return errno;
417 if (fstat(fd, &st)) { 424 if (fstat(fd, &st)) {
418 e = errno; 425 e = errno;
419 close(fd); 426 close(fd);
420 return e; 427 return e;
421 } 428 }
422 if (!S_ISREG(st.st_mode)) { 429 if (!S_ISREG(st.st_mode)) {
423 close(fd); 430 close(fd);
424 return EISDIR; 431 return EISDIR;
425 } 432 }
426 *buf = xmalloc(st.st_size + 1); 433 *buf = xmalloc(st.st_size + 1);
427 *size = read_in_full(fd, *buf, st.st_size); 434 *size = read_in_full(fd, *buf, st.st_size);
428 e = errno; 435 e = errno;
429 (*buf)[*size] = '\0'; 436 (*buf)[*size] = '\0';
430 close(fd); 437 close(fd);
431 return (*size == st.st_size ? 0 : e); 438 return (*size == st.st_size ? 0 : e);
432} 439}
440
441int is_token_char(char c)
442{
443 return isalnum(c) || c == '_';
444}
445
446/* Replace name with getenv(name), return pointer to zero-terminating char
447 */
448char *expand_macro(char *name, int maxlength)
449{
450 char *value;
451 int len;
452
453 len = 0;
454 value = getenv(name);
455 if (value) {
456 len = strlen(value);
457 if (len > maxlength)
458 len = maxlength;
459 strncpy(name, value, len);
460 }
461 return name + len;
462}
463
464#define EXPBUFSIZE (1024 * 8)
465
466/* Replace all tokens prefixed by '$' in the specified text with the
467 * value of the named environment variable.
468 * NB: the return value is a static buffer, i.e. it must be strdup'd
469 * by the caller.
470 */
471char *expand_macros(const char *txt)
472{
473 static char result[EXPBUFSIZE];
474 char *p, *start;
475 int len;
476
477 p = result;
478 start = NULL;
479 while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
480 *p = *txt;
481 if (start) {
482 if (!is_token_char(*txt)) {
483 if (p - start > 0) {
484 *p = '\0';
485 len = result + EXPBUFSIZE - start - 1;
486 p = expand_macro(start, len) - 1;
487 }
488 start = NULL;
489 txt--;
490 }
491 p++;
492 txt++;
493 continue;
494 }
495 if (*txt == '$') {
496 start = p;
497 txt++;
498 continue;
499 }
500 p++;
501 txt++;
502 }
503 *p = '\0';
504 if (start && p - start > 0) {
505 len = result + EXPBUFSIZE - start - 1;
506 p = expand_macro(start, len);
507 *p = '\0';
508 }
509 return result;
510}
diff --git a/ui-atom.c b/ui-atom.c
index 881872c..5c854c7 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -64,49 +64,51 @@ void add_entry(struct commit *commit, char *host)
64 htmlf("<id>%s</id>\n", hex); 64 htmlf("<id>%s</id>\n", hex);
65 html("<content type='text'>\n"); 65 html("<content type='text'>\n");
66 html_txt(info->msg); 66 html_txt(info->msg);
67 html("</content>\n"); 67 html("</content>\n");
68 html("<content type='xhtml'>\n"); 68 html("<content type='xhtml'>\n");
69 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n"); 69 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n");
70 html("<pre>\n"); 70 html("<pre>\n");
71 html_txt(info->msg); 71 html_txt(info->msg);
72 html("</pre>\n"); 72 html("</pre>\n");
73 html("</div>\n"); 73 html("</div>\n");
74 html("</content>\n"); 74 html("</content>\n");
75 html("</entry>\n"); 75 html("</entry>\n");
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;
101 rev.max_count = max_count; 103 rev.max_count = max_count;
102 setup_revisions(argc, argv, &rev, NULL); 104 setup_revisions(argc, argv, &rev, NULL);
103 prepare_revision_walk(&rev); 105 prepare_revision_walk(&rev);
104 106
105 host = cgit_hosturl(); 107 host = cgit_hosturl();
106 ctx.page.mimetype = "text/xml"; 108 ctx.page.mimetype = "text/xml";
107 ctx.page.charset = "utf-8"; 109 ctx.page.charset = "utf-8";
108 cgit_print_http_headers(&ctx); 110 cgit_print_http_headers(&ctx);
109 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n"); 111 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
110 html("<title>"); 112 html("<title>");
111 html_txt(ctx.repo->name); 113 html_txt(ctx.repo->name);
112 html("</title>\n"); 114 html("</title>\n");
diff --git a/ui-blob.c b/ui-blob.c
index 89330ce..ec435e1 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -1,51 +1,84 @@
1/* ui-blob.c: show blob content 1/* ui-blob.c: show blob content
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
7 */ 8 */
8 9
9#include "cgit.h" 10#include "cgit.h"
10#include "html.h" 11#include "html.h"
11#include "ui-shared.h" 12#include "ui-shared.h"
12 13
13static char *match_path; 14static char *match_path;
14static unsigned char *matched_sha1; 15static unsigned char *matched_sha1;
16static int found_path;
15 17
16static int walk_tree(const unsigned char *sha1, const char *base,int baselen, 18static int walk_tree(const unsigned char *sha1, const char *base,int baselen,
17 const char *pathname, unsigned mode, int stage, void *cbdata) { 19 const char *pathname, unsigned mode, int stage, void *cbdata) {
18 if(strncmp(base,match_path,baselen) 20 if(strncmp(base,match_path,baselen)
19 || strcmp(match_path+baselen,pathname) ) 21 || strcmp(match_path+baselen,pathname) )
20 return READ_TREE_RECURSIVE; 22 return READ_TREE_RECURSIVE;
21 memmove(matched_sha1,sha1,20); 23 memmove(matched_sha1,sha1,20);
24 found_path = 1;
22 return 0; 25 return 0;
23} 26}
24 27
25void cgit_print_blob(const char *hex, char *path, const char *head) 28int cgit_print_file(char *path, const char *head)
26{ 29{
30 unsigned char sha1[20];
31 enum object_type type;
32 char *buf;
33 unsigned long size;
34 struct commit *commit;
35 const char *paths[] = {path, NULL};
36 if (get_sha1(head, sha1))
37 return -1;
38 type = sha1_object_info(sha1, &size);
39 if(type == OBJ_COMMIT && path) {
40 commit = lookup_commit_reference(sha1);
41 match_path = path;
42 matched_sha1 = sha1;
43 found_path = 0;
44 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
45 if (!found_path)
46 return -1;
47 type = sha1_object_info(sha1, &size);
48 }
49 if (type == OBJ_BAD)
50 return -1;
51 buf = read_sha1_file(sha1, &type, &size);
52 if (!buf)
53 return -1;
54 buf[size] = '\0';
55 html_raw(buf, size);
56 return 0;
57}
27 58
59void cgit_print_blob(const char *hex, char *path, const char *head)
60{
28 unsigned char sha1[20]; 61 unsigned char sha1[20];
29 enum object_type type; 62 enum object_type type;
30 char *buf; 63 char *buf;
31 unsigned long size; 64 unsigned long size;
32 struct commit *commit; 65 struct commit *commit;
33 const char *paths[] = {path, NULL}; 66 const char *paths[] = {path, NULL};
34 67
35 if (hex) { 68 if (hex) {
36 if (get_sha1_hex(hex, sha1)){ 69 if (get_sha1_hex(hex, sha1)){
37 cgit_print_error(fmt("Bad hex value: %s", hex)); 70 cgit_print_error(fmt("Bad hex value: %s", hex));
38 return; 71 return;
39 } 72 }
40 } else { 73 } else {
41 if (get_sha1(head,sha1)) { 74 if (get_sha1(head,sha1)) {
42 cgit_print_error(fmt("Bad ref: %s", head)); 75 cgit_print_error(fmt("Bad ref: %s", head));
43 return; 76 return;
44 } 77 }
45 } 78 }
46 79
47 type = sha1_object_info(sha1, &size); 80 type = sha1_object_info(sha1, &size);
48 81
49 if((!hex) && type == OBJ_COMMIT && path) { 82 if((!hex) && type == OBJ_COMMIT && path) {
50 commit = lookup_commit_reference(sha1); 83 commit = lookup_commit_reference(sha1);
51 match_path = path; 84 match_path = path;
@@ -54,26 +87,26 @@ void cgit_print_blob(const char *hex, char *path, const char *head)
54 type = sha1_object_info(sha1,&size); 87 type = sha1_object_info(sha1,&size);
55 } 88 }
56 89
57 if (type == OBJ_BAD) { 90 if (type == OBJ_BAD) {
58 cgit_print_error(fmt("Bad object name: %s", hex)); 91 cgit_print_error(fmt("Bad object name: %s", hex));
59 return; 92 return;
60 } 93 }
61 94
62 buf = read_sha1_file(sha1, &type, &size); 95 buf = read_sha1_file(sha1, &type, &size);
63 if (!buf) { 96 if (!buf) {
64 cgit_print_error(fmt("Error reading object %s", hex)); 97 cgit_print_error(fmt("Error reading object %s", hex));
65 return; 98 return;
66 } 99 }
67 100
68 buf[size] = '\0'; 101 buf[size] = '\0';
69 ctx.page.mimetype = ctx.qry.mimetype; 102 ctx.page.mimetype = ctx.qry.mimetype;
70 if (!ctx.page.mimetype) { 103 if (!ctx.page.mimetype) {
71 if (buffer_is_binary(buf, size)) 104 if (buffer_is_binary(buf, size))
72 ctx.page.mimetype = "application/octet-stream"; 105 ctx.page.mimetype = "application/octet-stream";
73 else 106 else
74 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
75 } 108 }
76 ctx.page.filename = path; 109 ctx.page.filename = path;
77 cgit_print_http_headers(&ctx); 110 cgit_print_http_headers(&ctx);
78 write(htmlfd, buf, size); 111 html_raw(buf, size);
79} 112}
diff --git a/ui-blob.h b/ui-blob.h
index dad275a..d7e7d45 100644
--- a/ui-blob.h
+++ b/ui-blob.h
@@ -1,6 +1,7 @@
1#ifndef UI_BLOB_H 1#ifndef UI_BLOB_H
2#define UI_BLOB_H 2#define UI_BLOB_H
3 3
4extern int cgit_print_file(char *path, const char *head);
4extern void cgit_print_blob(const char *hex, char *path, const char *head); 5extern void cgit_print_blob(const char *hex, char *path, const char *head);
5 6
6#endif /* UI_BLOB_H */ 7#endif /* UI_BLOB_H */
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..2b4f677 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,118 +1,146 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
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-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 struct strbuf notes = STRBUF_INIT;
20 unsigned char sha1[20]; 21 unsigned char sha1[20];
21 char *tmp; 22 char *tmp, *tmp2;
22 int parents = 0; 23 int parents = 0;
23 24
24 if (!hex) 25 if (!hex)
25 hex = ctx.qry.head; 26 hex = ctx.qry.head;
26 27
27 if (get_sha1(hex, sha1)) { 28 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 29 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 30 return;
30 } 31 }
31 commit = lookup_commit_reference(sha1); 32 commit = lookup_commit_reference(sha1);
32 if (!commit) { 33 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 34 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 35 return;
35 } 36 }
36 info = cgit_parse_commit(commit); 37 info = cgit_parse_commit(commit);
37 38
39 format_note(NULL, sha1, &notes, PAGE_ENCODING, 0);
40
38 load_ref_decorations(DECORATE_FULL_REFS); 41 load_ref_decorations(DECORATE_FULL_REFS);
39 42
40 html("<table summary='commit info' class='commit-info'>\n"); 43 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 44 html("<tr><th>author</th><td>");
42 html_txt(info->author); 45 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 46 if (!ctx.cfg.noplainemail) {
44 html(" "); 47 html(" ");
45 html_txt(info->author_email); 48 html_txt(info->author_email);
46 } 49 }
47 html("</td><td class='right'>"); 50 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 51 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 52 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 53 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 54 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 55 if (!ctx.cfg.noplainemail) {
53 html(" "); 56 html(" ");
54 html_txt(info->committer_email); 57 html_txt(info->committer_email);
55 } 58 }
56 html("</td><td class='right'>"); 59 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 60 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 61 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 62 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 63 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 64 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
62 html(" ("); 65 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 66 cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
67 html(") (");
68 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
69 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
70 else
71 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
64 html(")</td></tr>\n"); 72 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 73 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 74 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 75 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 76 ctx.qry.head, tmp, NULL);
77 if (prefix) {
78 html(" /");
79 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
80 }
69 html("</td></tr>\n"); 81 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 82 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 83 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 84 if (!parent) {
73 html("<tr><td colspan='3'>"); 85 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 86 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 87 html("</td></tr>");
76 continue; 88 continue;
77 } 89 }
78 html("<tr><th>parent</th>" 90 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 91 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 92 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 93 if (ctx.repo->enable_subject_links) {
94 parent_info = cgit_parse_commit(parent);
95 tmp2 = parent_info->subject;
96 }
97 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
82 html(" ("); 98 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 99 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 100 sha1_to_hex(p->item->object.sha1), prefix, 0);
85 html(")</td></tr>"); 101 html(")</td></tr>");
86 parents++; 102 parents++;
87 } 103 }
88 if (ctx.repo->snapshots) { 104 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 105 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 106 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
91 hex, ctx.repo->snapshots); 107 hex, ctx.repo->snapshots);
92 html("</td></tr>"); 108 html("</td></tr>");
93 } 109 }
94 html("</table>\n"); 110 html("</table>\n");
95 html("<div class='commit-subject'>"); 111 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
97 cgit_open_filter(ctx.repo->commit_filter); 113 cgit_open_filter(ctx.repo->commit_filter);
98 html_txt(info->subject); 114 html_txt(info->subject);
99 if (ctx.repo->commit_filter) 115 if (ctx.repo->commit_filter)
100 cgit_close_filter(ctx.repo->commit_filter); 116 cgit_close_filter(ctx.repo->commit_filter);
101 show_commit_decorations(commit); 117 show_commit_decorations(commit);
102 html("</div>"); 118 html("</div>");
103 html("<div class='commit-msg'>"); 119 html("<div class='commit-msg'>");
104 if (ctx.repo->commit_filter) 120 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter); 121 cgit_open_filter(ctx.repo->commit_filter);
106 html_txt(info->msg); 122 html_txt(info->msg);
107 if (ctx.repo->commit_filter) 123 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter); 124 cgit_close_filter(ctx.repo->commit_filter);
109 html("</div>"); 125 html("</div>");
126 if (notes.len != 0) {
127 html("<div class='notes-header'>Notes</div>");
128 html("<div class='notes'>");
129 if (ctx.repo->commit_filter)
130 cgit_open_filter(ctx.repo->commit_filter);
131 html_txt(notes.buf);
132 if (ctx.repo->commit_filter)
133 cgit_close_filter(ctx.repo->commit_filter);
134 html("</div>");
135 html("<div class='notes-footer'></div>");
136 }
110 if (parents < 3) { 137 if (parents < 3) {
111 if (parents) 138 if (parents)
112 tmp = sha1_to_hex(commit->parents->item->object.sha1); 139 tmp = sha1_to_hex(commit->parents->item->object.sha1);
113 else 140 else
114 tmp = NULL; 141 tmp = NULL;
115 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 142 cgit_print_diff(ctx.qry.sha1, tmp, prefix);
116 } 143 }
144 strbuf_release(&notes);
117 cgit_free_commitinfo(info); 145 cgit_free_commitinfo(info);
118} 146}
diff --git a/ui-commit.h b/ui-commit.h
index 40bcb31..8198b4b 100644
--- a/ui-commit.h
+++ b/ui-commit.h
@@ -1,6 +1,6 @@
1#ifndef UI_COMMIT_H 1#ifndef UI_COMMIT_H
2#define UI_COMMIT_H 2#define UI_COMMIT_H
3 3
4extern void cgit_print_commit(char *hex); 4extern void cgit_print_commit(char *hex, const char *prefix);
5 5
6#endif /* UI_COMMIT_H */ 6#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..7ff7e46 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,58 +1,60 @@
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;
47 case DIFF_STATUS_DELETED: 49 case DIFF_STATUS_DELETED:
48 class = "del"; 50 class = "del";
49 break; 51 break;
50 case DIFF_STATUS_MODIFIED: 52 case DIFF_STATUS_MODIFIED:
51 class = "upd"; 53 class = "upd";
52 break; 54 break;
53 case DIFF_STATUS_RENAMED: 55 case DIFF_STATUS_RENAMED:
54 class = "mov"; 56 class = "mov";
55 break; 57 break;
56 case DIFF_STATUS_TYPE_CHANGED: 58 case DIFF_STATUS_TYPE_CHANGED:
57 class = "typ"; 59 class = "typ";
58 break; 60 break;
@@ -62,128 +64,144 @@ static void print_fileinfo(struct fileinfo *info)
62 case DIFF_STATUS_UNMERGED: 64 case DIFF_STATUS_UNMERGED:
63 class = "stg"; 65 class = "stg";
64 break; 66 break;
65 default: 67 default:
66 die("bug: unhandled diff status %c", info->status); 68 die("bug: unhandled diff status %c", info->status);
67 } 69 }
68 70
69 html("<tr>"); 71 html("<tr>");
70 htmlf("<td class='mode'>"); 72 htmlf("<td class='mode'>");
71 if (is_null_sha1(info->new_sha1)) { 73 if (is_null_sha1(info->new_sha1)) {
72 cgit_print_filemode(info->old_mode); 74 cgit_print_filemode(info->old_mode);
73 } else { 75 } else {
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'>%ld -> %ld 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'>");
99 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
100 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
101 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
102 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
103 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
104 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
105 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
106 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
107} 109}
108 110
109static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
110{ 112{
111 if (line && (len > 0)) { 113 if (line && (len > 0)) {
112 if (line[0] == '+') 114 if (line[0] == '+')
113 lines_added++; 115 lines_added++;
114 else if (line[0] == '-') 116 else if (line[0] == '-')
115 lines_removed++; 117 lines_removed++;
116 } 118 }
117} 119}
118 120
119static void inspect_filepair(struct diff_filepair *pair) 121static void inspect_filepair(struct diff_filepair *pair)
120{ 122{
121 int binary = 0; 123 int binary = 0;
122 unsigned long old_size = 0; 124 unsigned long old_size = 0;
123 unsigned long new_size = 0; 125 unsigned long new_size = 0;
124 files++; 126 files++;
125 lines_added = 0; 127 lines_added = 0;
126 lines_removed = 0; 128 lines_removed = 0;
127 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
128 &binary, count_diff_lines); 130 &binary, 0, ctx.qry.ignorews, count_diff_lines);
129 if (files >= slots) { 131 if (files >= slots) {
130 if (slots == 0) 132 if (slots == 0)
131 slots = 4; 133 slots = 4;
132 else 134 else
133 slots = slots * 2; 135 slots = slots * 2;
134 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
135 } 137 }
136 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
137 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
138 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
139 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
140 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
141 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
142 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
143 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
144 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
145 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
146 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
147 items[files-1].binary = binary; 149 items[files-1].binary = binary;
148 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
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, const char *prefix)
156{ 158{
157 int i; 159 int i, save_context = ctx.qry.context;
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);
164 if (prefix)
165 htmlf(" (limited to '%s')", prefix);
166 html(" (");
167 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1;
168 cgit_self_link("more", NULL, NULL, &ctx);
169 html("/");
170 ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1;
171 cgit_self_link("less", NULL, NULL, &ctx);
172 ctx.qry.context = save_context;
173 html(" context)");
174 html(" (");
175 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
176 cgit_self_link(ctx.qry.ignorews ? "ignore" : "show", NULL, NULL, &ctx);
177 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
178 html(" whitespace changes)");
162 html("</div>"); 179 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 180 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 181 max_changes = 0;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 182 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix,
183 ctx.qry.ignorews);
166 for(i = 0; i<files; i++) 184 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 185 print_fileinfo(&items[i]);
168 html("</table>"); 186 html("</table>");
169 html("<div class='diffstat-summary'>"); 187 html("<div class='diffstat-summary'>");
170 htmlf("%d files changed, %d insertions, %d deletions", 188 htmlf("%d files changed, %d insertions, %d deletions",
171 files, total_adds, total_rems); 189 files, total_adds, total_rems);
172 html("</div>"); 190 html("</div>");
173} 191}
174 192
175 193
176/* 194/*
177 * print a single line returned from xdiff 195 * print a single line returned from xdiff
178 */ 196 */
179static void print_line(char *line, int len) 197static void print_line(char *line, int len)
180{ 198{
181 char *class = "ctx"; 199 char *class = "ctx";
182 char c = line[len-1]; 200 char c = line[len-1];
183 201
184 if (line[0] == '+') 202 if (line[0] == '+')
185 class = "add"; 203 class = "add";
186 else if (line[0] == '-') 204 else if (line[0] == '-')
187 class = "del"; 205 class = "del";
188 else if (line[0] == '@') 206 else if (line[0] == '@')
189 class = "hunk"; 207 class = "hunk";
@@ -225,89 +243,129 @@ static void header(unsigned char *sha1, char *path1, int mode1,
225 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 243 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
226 free(abbrev1); 244 free(abbrev1);
227 free(abbrev2); 245 free(abbrev2);
228 if (mode1 != 0 && mode2 != 0) { 246 if (mode1 != 0 && mode2 != 0) {
229 htmlf(" %.6o", mode1); 247 htmlf(" %.6o", mode1);
230 if (mode2 != mode1) 248 if (mode2 != mode1)
231 htmlf("..%.6o", mode2); 249 htmlf("..%.6o", mode2);
232 } 250 }
233 html("<br/>--- a/"); 251 html("<br/>--- a/");
234 if (mode1 != 0) 252 if (mode1 != 0)
235 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 253 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
236 sha1_to_hex(old_rev_sha1), path1); 254 sha1_to_hex(old_rev_sha1), path1);
237 else 255 else
238 html_txt(path1); 256 html_txt(path1);
239 html("<br/>+++ b/"); 257 html("<br/>+++ b/");
240 if (mode2 != 0) 258 if (mode2 != 0)
241 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 259 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
242 sha1_to_hex(new_rev_sha1), path2); 260 sha1_to_hex(new_rev_sha1), path2);
243 else 261 else
244 html_txt(path2); 262 html_txt(path2);
245 } 263 }
246 html("</div>"); 264 html("</div>");
247} 265}
248 266
267static void print_ssdiff_link()
268{
269 if (!strcmp(ctx.qry.page, "diff")) {
270 if (use_ssdiff)
271 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
272 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
273 else
274 cgit_diff_link("Side-by-side diff", NULL, NULL,
275 ctx.qry.head, ctx.qry.sha1,
276 ctx.qry.sha2, ctx.qry.path, 1);
277 }
278}
279
249static void filepair_cb(struct diff_filepair *pair) 280static void filepair_cb(struct diff_filepair *pair)
250{ 281{
251 unsigned long old_size = 0; 282 unsigned long old_size = 0;
252 unsigned long new_size = 0; 283 unsigned long new_size = 0;
253 int binary = 0; 284 int binary = 0;
285 linediff_fn print_line_fn = print_line;
254 286
287 if (use_ssdiff) {
288 cgit_ssdiff_header_begin();
289 print_line_fn = cgit_ssdiff_line_cb;
290 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 291 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 292 pair->two->sha1, pair->two->path, pair->two->mode);
293 if (use_ssdiff)
294 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 295 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 296 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 297 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 298 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 299 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
300 if (use_ssdiff)
301 cgit_ssdiff_footer();
262 return; 302 return;
263 } 303 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 304 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 305 &new_size, &binary, ctx.qry.context,
306 ctx.qry.ignorews, print_line_fn))
266 cgit_print_error("Error running diff"); 307 cgit_print_error("Error running diff");
267 if (binary) 308 if (binary) {
309 if (use_ssdiff)
310 html("<tr><td colspan='4'>Binary files differ</td></tr>");
311 else
268 html("Binary files differ"); 312 html("Binary files differ");
269} 313}
314 if (use_ssdiff)
315 cgit_ssdiff_footer();
316}
270 317
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 318void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
272{ 319{
273 enum object_type type; 320 enum object_type type;
274 unsigned long size; 321 unsigned long size;
275 struct commit *commit, *commit2; 322 struct commit *commit, *commit2;
276 323
277 if (!new_rev) 324 if (!new_rev)
278 new_rev = ctx.qry.head; 325 new_rev = ctx.qry.head;
279 get_sha1(new_rev, new_rev_sha1); 326 get_sha1(new_rev, new_rev_sha1);
280 type = sha1_object_info(new_rev_sha1, &size); 327 type = sha1_object_info(new_rev_sha1, &size);
281 if (type == OBJ_BAD) { 328 if (type == OBJ_BAD) {
282 cgit_print_error(fmt("Bad object name: %s", new_rev)); 329 cgit_print_error(fmt("Bad object name: %s", new_rev));
283 return; 330 return;
284 } 331 }
285 commit = lookup_commit_reference(new_rev_sha1); 332 commit = lookup_commit_reference(new_rev_sha1);
286 if (!commit || parse_commit(commit)) 333 if (!commit || parse_commit(commit))
287 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
288 335
289 if (old_rev) 336 if (old_rev)
290 get_sha1(old_rev, old_rev_sha1); 337 get_sha1(old_rev, old_rev_sha1);
291 else if (commit->parents && commit->parents->item) 338 else if (commit->parents && commit->parents->item)
292 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 339 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
293 else 340 else
294 hashclr(old_rev_sha1); 341 hashclr(old_rev_sha1);
295 342
296 if (!is_null_sha1(old_rev_sha1)) { 343 if (!is_null_sha1(old_rev_sha1)) {
297 type = sha1_object_info(old_rev_sha1, &size); 344 type = sha1_object_info(old_rev_sha1, &size);
298 if (type == OBJ_BAD) { 345 if (type == OBJ_BAD) {
299 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 346 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
300 return; 347 return;
301 } 348 }
302 commit2 = lookup_commit_reference(old_rev_sha1); 349 commit2 = lookup_commit_reference(old_rev_sha1);
303 if (!commit2 || parse_commit(commit2)) 350 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 351 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 352 }
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 353
354 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
355 use_ssdiff = 1;
356
357 print_ssdiff_link();
358 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
359
360 if (use_ssdiff) {
361 html("<table summary='ssdiff' class='ssdiff'>");
362 } else {
308 html("<table summary='diff' class='diff'>"); 363 html("<table summary='diff' class='diff'>");
309 html("<tr><td>"); 364 html("<tr><td>");
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 365 }
366 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix,
367 ctx.qry.ignorews);
368 if (!use_ssdiff)
311 html("</td></tr>"); 369 html("</td></tr>");
312 html("</table>"); 370 html("</table>");
313} 371}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..41b5225 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -12,223 +12,241 @@
12 12
13int files, add_lines, rem_lines; 13int files, add_lines, rem_lines;
14 14
15void count_lines(char *line, int size) 15void count_lines(char *line, int size)
16{ 16{
17 if (size <= 0) 17 if (size <= 0)
18 return; 18 return;
19 19
20 if (line[0] == '+') 20 if (line[0] == '+')
21 add_lines++; 21 add_lines++;
22 22
23 else if (line[0] == '-') 23 else if (line[0] == '-')
24 rem_lines++; 24 rem_lines++;
25} 25}
26 26
27void inspect_files(struct diff_filepair *pair) 27void inspect_files(struct diff_filepair *pair)
28{ 28{
29 unsigned long old_size = 0; 29 unsigned long old_size = 0;
30 unsigned long new_size = 0; 30 unsigned long new_size = 0;
31 int binary = 0; 31 int binary = 0;
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, 0, ctx.qry.ignorews,
37 count_lines);
37} 38}
38 39
39void show_commit_decorations(struct commit *commit) 40void show_commit_decorations(struct commit *commit)
40{ 41{
41 struct name_decoration *deco; 42 struct name_decoration *deco;
42 static char buf[1024]; 43 static char buf[1024];
43 44
44 buf[sizeof(buf) - 1] = 0; 45 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 46 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 47 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 48 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 49 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 50 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 51 ctx.qry.vpath, 0, NULL, NULL,
52 ctx.qry.showmsg);
51 } 53 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 54 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 55 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 56 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 57 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 58 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 59 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 60 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 61 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 62 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 63 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 64 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 65 sha1_to_hex(commit->object.sha1),
64 0, NULL, NULL, ctx.qry.showmsg); 66 ctx.qry.vpath, 0, NULL, NULL,
67 ctx.qry.showmsg);
65 } 68 }
66 else { 69 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 70 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 71 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 72 sha1_to_hex(commit->object.sha1),
73 ctx.qry.vpath, 0);
70 } 74 }
71 deco = deco->next; 75 deco = deco->next;
72 } 76 }
73} 77}
74 78
75void print_commit(struct commit *commit) 79void print_commit(struct commit *commit)
76{ 80{
77 struct commitinfo *info; 81 struct commitinfo *info;
78 char *tmp; 82 char *tmp;
79 int cols = 2; 83 int cols = 2;
80 84
81 info = cgit_parse_commit(commit); 85 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 86 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 87 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 88 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 89 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
86 html_link_open(tmp, NULL, NULL); 90 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 91 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 92 html_link_close();
89 htmlf("</td><td%s>", 93 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 94 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 95 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 96 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
93 show_commit_decorations(commit); 97 show_commit_decorations(commit);
94 html("</td><td>"); 98 html("</td><td>");
95 html_txt(info->author); 99 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 100 if (ctx.repo->enable_log_filecount) {
97 files = 0; 101 files = 0;
98 add_lines = 0; 102 add_lines = 0;
99 rem_lines = 0; 103 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 104 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 105 html("</td><td>");
102 htmlf("%d", files); 106 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 107 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 108 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 109 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 110 }
107 } 111 }
108 html("</td></tr>\n"); 112 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 113 if (ctx.qry.showmsg) {
114 struct strbuf notes = STRBUF_INIT;
115 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0);
116
110 if (ctx.repo->enable_log_filecount) { 117 if (ctx.repo->enable_log_filecount) {
111 cols++; 118 cols++;
112 if (ctx.repo->enable_log_linecount) 119 if (ctx.repo->enable_log_linecount)
113 cols++; 120 cols++;
114 } 121 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 122 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 123 cols);
117 html_txt(info->msg); 124 html_txt(info->msg);
118 html("</td></tr>\n"); 125 html("</td></tr>\n");
126 if (notes.len != 0) {
127 html("<tr class='nohover'>");
128 html("<td class='lognotes-label'>Notes:</td>");
129 htmlf("<td colspan='%d' class='lognotes'>",
130 cols);
131 html_txt(notes.buf);
132 html("</td></tr>\n");
133 }
134 strbuf_release(&notes);
119 } 135 }
120 cgit_free_commitinfo(info); 136 cgit_free_commitinfo(info);
121} 137}
122 138
123static const char *disambiguate_ref(const char *ref) 139static const char *disambiguate_ref(const char *ref)
124{ 140{
125 unsigned char sha1[20]; 141 unsigned char sha1[20];
126 const char *longref; 142 const char *longref;
127 143
128 longref = fmt("refs/heads/%s", ref); 144 longref = fmt("refs/heads/%s", ref);
129 if (get_sha1(longref, sha1) == 0) 145 if (get_sha1(longref, sha1) == 0)
130 return longref; 146 return longref;
131 147
132 return ref; 148 return ref;
133} 149}
134 150
135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 151void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
136 char *path, int pager) 152 char *path, int pager)
137{ 153{
138 struct rev_info rev; 154 struct rev_info rev;
139 struct commit *commit; 155 struct commit *commit;
140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 156 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
141 int argc = 2; 157 int argc = 2;
142 int i, columns = 3; 158 int i, columns = 3;
143 159
144 if (!tip) 160 if (!tip)
145 tip = ctx.qry.head; 161 tip = ctx.qry.head;
146 162
147 argv[1] = disambiguate_ref(tip); 163 argv[1] = disambiguate_ref(tip);
148 164
149 if (grep && pattern && (!strcmp(grep, "grep") || 165 if (grep && pattern) {
150 !strcmp(grep, "author") || 166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
151 !strcmp(grep, "committer"))) 167 !strcmp(grep, "committer"))
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 168 argv[argc++] = fmt("--%s=%s", grep, pattern);
169 if (!strcmp(grep, "range"))
170 argv[1] = pattern;
171 }
153 172
154 if (path) { 173 if (path) {
155 argv[argc++] = "--"; 174 argv[argc++] = "--";
156 argv[argc++] = path; 175 argv[argc++] = path;
157 } 176 }
158 init_revisions(&rev, NULL); 177 init_revisions(&rev, NULL);
159 rev.abbrev = DEFAULT_ABBREV; 178 rev.abbrev = DEFAULT_ABBREV;
160 rev.commit_format = CMIT_FMT_DEFAULT; 179 rev.commit_format = CMIT_FMT_DEFAULT;
161 rev.verbose_header = 1; 180 rev.verbose_header = 1;
162 rev.show_root_diff = 0; 181 rev.show_root_diff = 0;
163 setup_revisions(argc, argv, &rev, NULL); 182 setup_revisions(argc, argv, &rev, NULL);
164 load_ref_decorations(DECORATE_FULL_REFS); 183 load_ref_decorations(DECORATE_FULL_REFS);
165 rev.show_decorations = 1; 184 rev.show_decorations = 1;
166 rev.grep_filter.regflags |= REG_ICASE; 185 rev.grep_filter.regflags |= REG_ICASE;
167 compile_grep_patterns(&rev.grep_filter); 186 compile_grep_patterns(&rev.grep_filter);
168 prepare_revision_walk(&rev); 187 prepare_revision_walk(&rev);
169 188
170 if (pager) 189 if (pager)
171 html("<table class='list nowrap'>"); 190 html("<table class='list nowrap'>");
172 191
173 html("<tr class='nohover'><th class='left'>Age</th>" 192 html("<tr class='nohover'><th class='left'>Age</th>"
174 "<th class='left'>Commit message"); 193 "<th class='left'>Commit message");
175 if (pager) { 194 if (pager) {
176 html(" ("); 195 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 197 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 198 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 199 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 200 html(")");
182 } 201 }
183 html("</th><th class='left'>Author</th>"); 202 html("</th><th class='left'>Author</th>");
184 if (ctx.repo->enable_log_filecount) { 203 if (ctx.repo->enable_log_filecount) {
185 html("<th class='left'>Files</th>"); 204 html("<th class='left'>Files</th>");
186 columns++; 205 columns++;
187 if (ctx.repo->enable_log_linecount) { 206 if (ctx.repo->enable_log_linecount) {
188 html("<th class='left'>Lines</th>"); 207 html("<th class='left'>Lines</th>");
189 columns++; 208 columns++;
190 } 209 }
191 } 210 }
192 html("</tr>\n"); 211 html("</tr>\n");
193 212
194 if (ofs<0) 213 if (ofs<0)
195 ofs = 0; 214 ofs = 0;
196 215
197 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 216 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
198 free(commit->buffer); 217 free(commit->buffer);
199 commit->buffer = NULL; 218 commit->buffer = NULL;
200 free_commit_list(commit->parents); 219 free_commit_list(commit->parents);
201 commit->parents = NULL; 220 commit->parents = NULL;
202 } 221 }
203 222
204 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 223 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
205 print_commit(commit); 224 print_commit(commit);
206 free(commit->buffer); 225 free(commit->buffer);
207 commit->buffer = NULL; 226 commit->buffer = NULL;
208 free_commit_list(commit->parents); 227 free_commit_list(commit->parents);
209 commit->parents = NULL; 228 commit->parents = NULL;
210 } 229 }
211 if (pager) { 230 if (pager) {
212 htmlf("</table><div class='pager'>", 231 html("</table><div class='pager'>");
213 columns);
214 if (ofs > 0) { 232 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 233 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 234 ctx.qry.sha1, ctx.qry.vpath,
217 ofs - cnt, ctx.qry.grep, 235 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 236 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 237 html("&nbsp;");
220 } 238 }
221 if ((commit = get_revision(&rev)) != NULL) { 239 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 240 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 241 ctx.qry.sha1, ctx.qry.vpath,
224 ofs + cnt, ctx.qry.grep, 242 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 243 ctx.qry.search, ctx.qry.showmsg);
226 } 244 }
227 html("</div>"); 245 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 246 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 247 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 248 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
231 NULL, NULL, ctx.qry.showmsg); 249 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 250 html("</td></tr>\n");
233 } 251 }
234} 252}
diff --git a/ui-patch.c b/ui-patch.c
index 2a8f7a5..ca008f3 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -50,80 +50,82 @@ static void header(unsigned char *sha1, char *path1, int mode1,
50 if (mode2 != mode1) 50 if (mode2 != mode1)
51 htmlf("..%.6o", mode2); 51 htmlf("..%.6o", mode2);
52 } 52 }
53 htmlf("\n--- a/%s\n", path1); 53 htmlf("\n--- a/%s\n", path1);
54 htmlf("+++ b/%s\n", path2); 54 htmlf("+++ b/%s\n", path2);
55 } 55 }
56} 56}
57 57
58static void filepair_cb(struct diff_filepair *pair) 58static void filepair_cb(struct diff_filepair *pair)
59{ 59{
60 unsigned long old_size = 0; 60 unsigned long old_size = 0;
61 unsigned long new_size = 0; 61 unsigned long new_size = 0;
62 int binary = 0; 62 int binary = 0;
63 63
64 header(pair->one->sha1, pair->one->path, pair->one->mode, 64 header(pair->one->sha1, pair->one->path, pair->one->mode,
65 pair->two->sha1, pair->two->path, pair->two->mode); 65 pair->two->sha1, pair->two->path, pair->two->mode);
66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
67 if (S_ISGITLINK(pair->one->mode)) 67 if (S_ISGITLINK(pair->one->mode))
68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
69 if (S_ISGITLINK(pair->two->mode)) 69 if (S_ISGITLINK(pair->two->mode))
70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, 0, 0, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex, const char *prefix)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
84 unsigned char sha1[20], old_sha1[20]; 84 unsigned char sha1[20], old_sha1[20];
85 char *patchname; 85 char *patchname;
86 86
87 if (!hex) 87 if (!hex)
88 hex = ctx.qry.head; 88 hex = ctx.qry.head;
89 89
90 if (get_sha1(hex, sha1)) { 90 if (get_sha1(hex, sha1)) {
91 cgit_print_error(fmt("Bad object id: %s", hex)); 91 cgit_print_error(fmt("Bad object id: %s", hex));
92 return; 92 return;
93 } 93 }
94 commit = lookup_commit_reference(sha1); 94 commit = lookup_commit_reference(sha1);
95 if (!commit) { 95 if (!commit) {
96 cgit_print_error(fmt("Bad commit reference: %s", hex)); 96 cgit_print_error(fmt("Bad commit reference: %s", hex));
97 return; 97 return;
98 } 98 }
99 info = cgit_parse_commit(commit); 99 info = cgit_parse_commit(commit);
100 100
101 if (commit->parents && commit->parents->item) 101 if (commit->parents && commit->parents->item)
102 hashcpy(old_sha1, commit->parents->item->object.sha1); 102 hashcpy(old_sha1, commit->parents->item->object.sha1);
103 else 103 else
104 hashclr(old_sha1); 104 hashclr(old_sha1);
105 105
106 patchname = fmt("%s.patch", sha1_to_hex(sha1)); 106 patchname = fmt("%s.patch", sha1_to_hex(sha1));
107 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
108 ctx.page.filename = patchname; 108 ctx.page.filename = patchname;
109 cgit_print_http_headers(&ctx); 109 cgit_print_http_headers(&ctx);
110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); 110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
111 htmlf("From: %s", info->author); 111 htmlf("From: %s", info->author);
112 if (!ctx.cfg.noplainemail) { 112 if (!ctx.cfg.noplainemail) {
113 htmlf(" %s", info->author_email); 113 htmlf(" %s", info->author_email);
114 } 114 }
115 html("\n"); 115 html("\n");
116 html("Date: "); 116 html("Date: ");
117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); 117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time);
118 htmlf("Subject: %s\n\n", info->subject); 118 htmlf("Subject: %s\n\n", info->subject);
119 if (info->msg && *info->msg) { 119 if (info->msg && *info->msg) {
120 htmlf("%s", info->msg); 120 htmlf("%s", info->msg);
121 if (info->msg[strlen(info->msg) - 1] != '\n') 121 if (info->msg[strlen(info->msg) - 1] != '\n')
122 html("\n"); 122 html("\n");
123 } 123 }
124 html("---\n"); 124 html("---\n");
125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix, 0);
126 html("--\n"); 128 html("--\n");
127 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
128 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
129} 131}
diff --git a/ui-patch.h b/ui-patch.h
index 9f68212..1641cea 100644
--- a/ui-patch.h
+++ b/ui-patch.h
@@ -1,6 +1,6 @@
1#ifndef UI_PATCH_H 1#ifndef UI_PATCH_H
2#define UI_PATCH_H 2#define UI_PATCH_H
3 3
4extern void cgit_print_patch(char *hex); 4extern void cgit_print_patch(char *hex, const char *prefix);
5 5
6#endif /* UI_PATCH_H */ 6#endif /* UI_PATCH_H */
diff --git a/ui-plain.c b/ui-plain.c
index 66cb19c..1b2b672 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -1,94 +1,146 @@
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);
27 return; 26 return;
28 } 27 }
29 28
30 buf = read_sha1_file(sha1, &type, &size); 29 buf = read_sha1_file(sha1, &type, &size);
31 if (!buf) { 30 if (!buf) {
32 html_status(404, "Not found", 0); 31 html_status(404, "Not found", 0);
33 return; 32 return;
34 } 33 }
35 ctx.page.mimetype = NULL; 34 ctx.page.mimetype = NULL;
36 ext = strrchr(path, '.'); 35 ext = strrchr(path, '.');
37 if (ext && *(++ext)) { 36 if (ext && *(++ext)) {
38 mime = string_list_lookup(ext, &ctx.cfg.mimetypes); 37 mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
39 if (mime) 38 if (mime)
40 ctx.page.mimetype = (char *)mime->util; 39 ctx.page.mimetype = (char *)mime->util;
41 } 40 }
42 if (!ctx.page.mimetype) { 41 if (!ctx.page.mimetype) {
43 if (buffer_is_binary(buf, size)) 42 if (buffer_is_binary(buf, size))
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 6571cc4..caddfbc 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -55,49 +55,49 @@ static int get_ref_age(struct refinfo *ref)
55 return 0; 55 return 0;
56} 56}
57 57
58static int cmp_tag_age(const void *a, const void *b) 58static int cmp_tag_age(const void *a, const void *b)
59{ 59{
60 struct refinfo *r1 = *(struct refinfo **)a; 60 struct refinfo *r1 = *(struct refinfo **)a;
61 struct refinfo *r2 = *(struct refinfo **)b; 61 struct refinfo *r2 = *(struct refinfo **)b;
62 62
63 return cmp_age(get_ref_age(r1), get_ref_age(r2)); 63 return cmp_age(get_ref_age(r1), get_ref_age(r2));
64} 64}
65 65
66static int print_branch(struct refinfo *ref) 66static int print_branch(struct refinfo *ref)
67{ 67{
68 struct commitinfo *info = ref->commit; 68 struct commitinfo *info = ref->commit;
69 char *name = (char *)ref->refname; 69 char *name = (char *)ref->refname;
70 70
71 if (!info) 71 if (!info)
72 return 1; 72 return 1;
73 html("<tr><td>"); 73 html("<tr><td>");
74 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 74 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
75 ctx.qry.showmsg); 75 ctx.qry.showmsg);
76 html("</td><td>"); 76 html("</td><td>");
77 77
78 if (ref->object->type == OBJ_COMMIT) { 78 if (ref->object->type == OBJ_COMMIT) {
79 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 79 cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0);
80 html("</td><td>"); 80 html("</td><td>");
81 html_txt(info->author); 81 html_txt(info->author);
82 html("</td><td colspan='2'>"); 82 html("</td><td colspan='2'>");
83 cgit_print_age(info->commit->date, -1, NULL); 83 cgit_print_age(info->commit->date, -1, NULL);
84 } else { 84 } else {
85 html("</td><td></td><td>"); 85 html("</td><td></td><td>");
86 cgit_object_link(ref->object); 86 cgit_object_link(ref->object);
87 } 87 }
88 html("</td></tr>\n"); 88 html("</td></tr>\n");
89 return 0; 89 return 0;
90} 90}
91 91
92static void print_tag_header() 92static void print_tag_header()
93{ 93{
94 html("<tr class='nohover'><th class='left'>Tag</th>" 94 html("<tr class='nohover'><th class='left'>Tag</th>"
95 "<th class='left'>Download</th>" 95 "<th class='left'>Download</th>"
96 "<th class='left'>Author</th>" 96 "<th class='left'>Author</th>"
97 "<th class='left' colspan='2'>Age</th></tr>\n"); 97 "<th class='left' colspan='2'>Age</th></tr>\n");
98 header = 1; 98 header = 1;
99} 99}
100 100
101static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 101static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
102{ 102{
103 const struct cgit_snapshot_format* f; 103 const struct cgit_snapshot_format* f;
@@ -168,48 +168,50 @@ static int print_tag(struct refinfo *ref)
168 } 168 }
169 return 0; 169 return 0;
170} 170}
171 171
172static void print_refs_link(char *path) 172static void print_refs_link(char *path)
173{ 173{
174 html("<tr class='nohover'><td colspan='4'>"); 174 html("<tr class='nohover'><td colspan='4'>");
175 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 175 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
176 html("</td></tr>"); 176 html("</td></tr>");
177} 177}
178 178
179void cgit_print_branches(int maxcount) 179void cgit_print_branches(int maxcount)
180{ 180{
181 struct reflist list; 181 struct reflist list;
182 int i; 182 int i;
183 183
184 html("<tr class='nohover'><th class='left'>Branch</th>" 184 html("<tr class='nohover'><th class='left'>Branch</th>"
185 "<th class='left'>Commit message</th>" 185 "<th class='left'>Commit message</th>"
186 "<th class='left'>Author</th>" 186 "<th class='left'>Author</th>"
187 "<th class='left' colspan='2'>Age</th></tr>\n"); 187 "<th class='left' colspan='2'>Age</th></tr>\n");
188 188
189 list.refs = NULL; 189 list.refs = NULL;
190 list.alloc = list.count = 0; 190 list.alloc = list.count = 0;
191 for_each_branch_ref(cgit_refs_cb, &list); 191 for_each_branch_ref(cgit_refs_cb, &list);
192 if (ctx.repo->enable_remote_branches)
193 for_each_remote_ref(cgit_refs_cb, &list);
192 194
193 if (maxcount == 0 || maxcount > list.count) 195 if (maxcount == 0 || maxcount > list.count)
194 maxcount = list.count; 196 maxcount = list.count;
195 197
196 if (maxcount < list.count) { 198 if (maxcount < list.count) {
197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 199 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 200 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
199 } 201 }
200 202
201 for(i=0; i<maxcount; i++) 203 for(i=0; i<maxcount; i++)
202 print_branch(list.refs[i]); 204 print_branch(list.refs[i]);
203 205
204 if (maxcount < list.count) 206 if (maxcount < list.count)
205 print_refs_link("heads"); 207 print_refs_link("heads");
206} 208}
207 209
208void cgit_print_tags(int maxcount) 210void cgit_print_tags(int maxcount)
209{ 211{
210 struct reflist list; 212 struct reflist list;
211 int i; 213 int i;
212 214
213 header = 0; 215 header = 0;
214 list.refs = NULL; 216 list.refs = NULL;
215 list.alloc = list.count = 0; 217 list.alloc = list.count = 0;
diff --git a/ui-repolist.c b/ui-repolist.c
index 0a0b6ca..2c98668 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,38 +1,32 @@
1/* ui-repolist.c: functions for generating the repolist page 1/* ui-repolist.c: functions for generating the repolist page
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/* This is needed for strcasestr to be defined by <string.h> */
10#define _GNU_SOURCE 1
11#include <string.h>
12
13#include <time.h>
14
15#include "cgit.h" 9#include "cgit.h"
16#include "html.h" 10#include "html.h"
17#include "ui-shared.h" 11#include "ui-shared.h"
18 12
19time_t read_agefile(char *path) 13time_t read_agefile(char *path)
20{ 14{
21 time_t result; 15 time_t result;
22 size_t size; 16 size_t size;
23 char *buf; 17 char *buf;
24 static char buf2[64]; 18 static char buf2[64];
25 19
26 if (readfile(path, &buf, &size)) 20 if (readfile(path, &buf, &size))
27 return -1; 21 return -1;
28 22
29 if (parse_date(buf, buf2, sizeof(buf2))) 23 if (parse_date(buf, buf2, sizeof(buf2)))
30 result = strtoul(buf2, NULL, 10); 24 result = strtoul(buf2, NULL, 10);
31 else 25 else
32 result = 0; 26 result = 0;
33 free(buf); 27 free(buf);
34 return result; 28 return result;
35} 29}
36 30
37static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) 31static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
38{ 32{
diff --git a/ui-shared.c b/ui-shared.c
index 8a7cc32..ae29615 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -6,49 +6,49 @@
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 "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_httpscheme() 37char *cgit_httpscheme()
38{ 38{
39 if (ctx.env.https && !strcmp(ctx.env.https, "on")) 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://"; 40 return "https://";
41 else 41 else
42 return "http://"; 42 return "http://";
43} 43}
44 44
45char *cgit_hosturl() 45char *cgit_hosturl()
46{ 46{
47 if (ctx.env.http_host) 47 if (ctx.env.http_host)
48 return ctx.env.http_host; 48 return ctx.env.http_host;
49 if (!ctx.env.server_name) 49 if (!ctx.env.server_name)
50 return NULL; 50 return NULL;
51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
52 return ctx.env.server_name; 52 return ctx.env.server_name;
53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
54} 54}
@@ -112,104 +112,104 @@ const char *cgit_repobasename(const char *reponame)
112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
113 p -= 3; rvbuf[p--] = 0; 113 p -= 3; rvbuf[p--] = 0;
114 } 114 }
115 /* strip more trailing slashes if any */ 115 /* strip more trailing slashes if any */
116 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 116 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
117 /* find last slash in the remaining string */ 117 /* find last slash in the remaining string */
118 rv = strrchr(rvbuf,'/'); 118 rv = strrchr(rvbuf,'/');
119 if(rv) 119 if(rv)
120 return ++rv; 120 return ++rv;
121 return rvbuf; 121 return rvbuf;
122} 122}
123 123
124char *cgit_currurl() 124char *cgit_currurl()
125{ 125{
126 if (!ctx.cfg.virtual_root) 126 if (!ctx.cfg.virtual_root)
127 return ctx.cfg.script_name; 127 return ctx.cfg.script_name;
128 else if (ctx.qry.page) 128 else if (ctx.qry.page)
129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
143 html("/"); 143 html("/");
144 } else 144 } else
145 html(ctx.cfg.script_name); 145 html(ctx.cfg.script_name);
146 146
147 if (page) { 147 if (page) {
148 htmlf("?p=%s", page); 148 htmlf("?p=%s", page);
149 delim = "&"; 149 delim = "&";
150 } 150 }
151 if (search) { 151 if (search) {
152 html(delim); 152 html(delim);
153 html("q="); 153 html("q=");
154 html_attr(search); 154 html_attr(search);
155 delim = "&"; 155 delim = "&";
156 } 156 }
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(char *page, char *name, char *title, char *class, 163static void site_link(const char *page, const char *name, const char *title,
164 char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
171 } 171 }
172 if (class) { 172 if (class) {
173 html(" class='"); 173 html(" class='");
174 html_attr(class); 174 html_attr(class);
175 html("'"); 175 html("'");
176 } 176 }
177 html(" href='"); 177 html(" href='");
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(char *name, char *title, char *class, char *pattern, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(char *title, char *class, char *page, char *head, 190static char *repolink(const char *title, const char *class, const char *page,
191 char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
198 html_attr(title); 198 html_attr(title);
199 html("'"); 199 html("'");
200 } 200 }
201 if (class) { 201 if (class) {
202 html(" class='"); 202 html(" class='");
203 html_attr(class); 203 html_attr(class);
204 html("'"); 204 html("'");
205 } 205 }
206 html(" href='"); 206 html(" href='");
207 if (ctx.cfg.virtual_root) { 207 if (ctx.cfg.virtual_root) {
208 html_url_path(ctx.cfg.virtual_root); 208 html_url_path(ctx.cfg.virtual_root);
209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
210 html("/"); 210 html("/");
211 html_url_path(ctx.repo->url); 211 html_url_path(ctx.repo->url);
212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
213 html("/"); 213 html("/");
214 if (page) { 214 if (page) {
215 html_url_path(page); 215 html_url_path(page);
@@ -219,219 +219,327 @@ static char *repolink(char *title, char *class, char *page, char *head,
219 } 219 }
220 } else { 220 } else {
221 html(ctx.cfg.script_name); 221 html(ctx.cfg.script_name);
222 html("?url="); 222 html("?url=");
223 html_url_arg(ctx.repo->url); 223 html_url_arg(ctx.repo->url);
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(const char *page, const char *name, const char *title,
244 char *head, char *rev, char *path) 244 const char *class, const char *head, const char *rev,
245 const char *path)
245{ 246{
246 char *delim; 247 char *delim;
247 248
248 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
249 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { 250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
250 html(delim); 251 html(delim);
251 html("id="); 252 html("id=");
252 html_url_arg(rev); 253 html_url_arg(rev);
253 } 254 }
254 html("'>"); 255 html("'>");
255 html_txt(name); 256 html_txt(name);
256 html("</a>"); 257 html("</a>");
257} 258}
258 259
259void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head)
260{ 262{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 264}
263 265
264void cgit_tag_link(char *name, char *title, char *class, char *head, 266void cgit_tag_link(const char *name, const char *title, const char *class,
265 char *rev) 267 const char *head, const char *rev)
266{ 268{
267 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
268} 270}
269 271
270void cgit_tree_link(char *name, char *title, char *class, char *head, 272void cgit_tree_link(const char *name, const char *title, const char *class,
271 char *rev, char *path) 273 const char *head, const char *rev, const char *path)
272{ 274{
273 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
274} 276}
275 277
276void cgit_plain_link(char *name, char *title, char *class, char *head, 278void cgit_plain_link(const char *name, const char *title, const char *class,
277 char *rev, char *path) 279 const char *head, const char *rev, const char *path)
278{ 280{
279 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
280} 282}
281 283
282void cgit_log_link(char *name, char *title, char *class, char *head, 284void cgit_log_link(const char *name, const char *title, const char *class,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 285 const char *head, const char *rev, const char *path,
284 int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
285{ 287{
286 char *delim; 288 char *delim;
287 289
288 delim = repolink(title, class, "log", head, path); 290 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 291 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 292 html(delim);
291 html("id="); 293 html("id=");
292 html_url_arg(rev); 294 html_url_arg(rev);
293 delim = "&"; 295 delim = "&";
294 } 296 }
295 if (grep && pattern) { 297 if (grep && pattern) {
296 html(delim); 298 html(delim);
297 html("qt="); 299 html("qt=");
298 html_url_arg(grep); 300 html_url_arg(grep);
299 delim = "&"; 301 delim = "&";
300 html(delim); 302 html(delim);
301 html("q="); 303 html("q=");
302 html_url_arg(pattern); 304 html_url_arg(pattern);
303 } 305 }
304 if (ofs > 0) { 306 if (ofs > 0) {
305 html(delim); 307 html(delim);
306 html("ofs="); 308 html("ofs=");
307 htmlf("%d", ofs); 309 htmlf("%d", ofs);
308 delim = "&"; 310 delim = "&";
309 } 311 }
310 if (showmsg) { 312 if (showmsg) {
311 html(delim); 313 html(delim);
312 html("showmsg=1"); 314 html("showmsg=1");
313 } 315 }
314 html("'>"); 316 html("'>");
315 html_txt(name); 317 html_txt(name);
316 html("</a>"); 318 html("</a>");
317} 319}
318 320
319void cgit_commit_link(char *name, char *title, char *class, char *head, 321void cgit_commit_link(char *name, const char *title, const char *class,
320 char *rev) 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff)
321{ 324{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 327 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 330 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 331
332 char *delim;
333
334 delim = repolink(title, class, "commit", head, path);
335 if (rev && strcmp(rev, ctx.qry.head)) {
336 html(delim);
337 html("id=");
338 html_url_arg(rev);
339 delim = "&amp;";
340 }
341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
342 html(delim);
343 html("ss=1");
344 delim = "&amp;";
345 }
346 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
347 html(delim);
348 html("context=");
349 htmlf("%d", ctx.qry.context);
350 delim = "&amp;";
351 }
352 if (ctx.qry.ignorews) {
353 html(delim);
354 html("ignorews=1");
355 delim = "&amp;";
356 }
357 html("'>");
358 html_txt(name);
359 html("</a>");
329} 360}
330 361
331void cgit_refs_link(char *name, char *title, char *class, char *head, 362void cgit_refs_link(const char *name, const char *title, const char *class,
332 char *rev, char *path) 363 const char *head, const char *rev, const char *path)
333{ 364{
334 reporevlink("refs", name, title, class, head, rev, path); 365 reporevlink("refs", name, title, class, head, rev, path);
335} 366}
336 367
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 368void cgit_snapshot_link(const char *name, const char *title, const char *class,
338 char *rev, char *archivename) 369 const char *head, const char *rev,
370 const char *archivename)
339{ 371{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 372 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 373}
342 374
343void cgit_diff_link(char *name, char *title, char *class, char *head, 375void cgit_diff_link(const char *name, const char *title, const char *class,
344 char *new_rev, char *old_rev, char *path) 376 const char *head, const char *new_rev, const char *old_rev,
377 const char *path, int toggle_ssdiff)
345{ 378{
346 char *delim; 379 char *delim;
347 380
348 delim = repolink(title, class, "diff", head, path); 381 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 382 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 383 html(delim);
351 html("id="); 384 html("id=");
352 html_url_arg(new_rev); 385 html_url_arg(new_rev);
353 delim = "&amp;"; 386 delim = "&amp;";
354 } 387 }
355 if (old_rev) { 388 if (old_rev) {
356 html(delim); 389 html(delim);
357 html("id2="); 390 html("id2=");
358 html_url_arg(old_rev); 391 html_url_arg(old_rev);
392 delim = "&amp;";
393 }
394 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
395 html(delim);
396 html("ss=1");
397 delim = "&amp;";
398 }
399 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
400 html(delim);
401 html("context=");
402 htmlf("%d", ctx.qry.context);
403 delim = "&amp;";
404 }
405 if (ctx.qry.ignorews) {
406 html(delim);
407 html("ignorews=1");
408 delim = "&amp;";
359 } 409 }
360 html("'>"); 410 html("'>");
361 html_txt(name); 411 html_txt(name);
362 html("</a>"); 412 html("</a>");
363} 413}
364 414
365void cgit_patch_link(char *name, char *title, char *class, char *head, 415void cgit_patch_link(const char *name, const char *title, const char *class,
366 char *rev) 416 const char *head, const char *rev, const char *path)
367{ 417{
368 reporevlink("patch", name, title, class, head, rev, NULL); 418 reporevlink("patch", name, title, class, head, rev, path);
369} 419}
370 420
371void cgit_stats_link(char *name, char *title, char *class, char *head, 421void cgit_stats_link(const char *name, const char *title, const char *class,
372 char *path) 422 const char *head, const char *path)
373{ 423{
374 reporevlink("stats", name, title, class, head, NULL, path); 424 reporevlink("stats", name, title, class, head, NULL, path);
375} 425}
376 426
427void cgit_self_link(char *name, const char *title, const char *class,
428 struct cgit_context *ctx)
429{
430 if (!strcmp(ctx->qry.page, "repolist"))
431 return cgit_index_link(name, title, class, ctx->qry.search,
432 ctx->qry.ofs);
433 else if (!strcmp(ctx->qry.page, "summary"))
434 return cgit_summary_link(name, title, class, ctx->qry.head);
435 else if (!strcmp(ctx->qry.page, "tag"))
436 return cgit_tag_link(name, title, class, ctx->qry.head,
437 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
438 else if (!strcmp(ctx->qry.page, "tree"))
439 return cgit_tree_link(name, title, class, ctx->qry.head,
440 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
441 ctx->qry.path);
442 else if (!strcmp(ctx->qry.page, "plain"))
443 return cgit_plain_link(name, title, class, ctx->qry.head,
444 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
445 ctx->qry.path);
446 else if (!strcmp(ctx->qry.page, "log"))
447 return cgit_log_link(name, title, class, ctx->qry.head,
448 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
449 ctx->qry.path, ctx->qry.ofs,
450 ctx->qry.grep, ctx->qry.search,
451 ctx->qry.showmsg);
452 else if (!strcmp(ctx->qry.page, "commit"))
453 return cgit_commit_link(name, title, class, ctx->qry.head,
454 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
455 ctx->qry.path, 0);
456 else if (!strcmp(ctx->qry.page, "patch"))
457 return cgit_patch_link(name, title, class, ctx->qry.head,
458 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
459 ctx->qry.path);
460 else if (!strcmp(ctx->qry.page, "refs"))
461 return cgit_refs_link(name, title, class, ctx->qry.head,
462 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
463 ctx->qry.path);
464 else if (!strcmp(ctx->qry.page, "snapshot"))
465 return cgit_snapshot_link(name, title, class, ctx->qry.head,
466 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
467 ctx->qry.path);
468 else if (!strcmp(ctx->qry.page, "diff"))
469 return cgit_diff_link(name, title, class, ctx->qry.head,
470 ctx->qry.sha1, ctx->qry.sha2,
471 ctx->qry.path, 0);
472 else if (!strcmp(ctx->qry.page, "stats"))
473 return cgit_stats_link(name, title, class, ctx->qry.head,
474 ctx->qry.path);
475
476 /* Don't known how to make link for this page */
477 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
478 html("><!-- cgit_self_link() doesn't know how to make link for page '");
479 html_txt(ctx->qry.page);
480 html("' -->");
481 html_txt(name);
482 html("</a>");
483}
484
377void cgit_object_link(struct object *obj) 485void cgit_object_link(struct object *obj)
378{ 486{
379 char *page, *shortrev, *fullrev, *name; 487 char *page, *shortrev, *fullrev, *name;
380 488
381 fullrev = sha1_to_hex(obj->sha1); 489 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 490 shortrev = xstrdup(fullrev);
383 shortrev[10] = '\0'; 491 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 492 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 493 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 494 ctx.qry.head, fullrev, NULL, 0);
387 return; 495 return;
388 } else if (obj->type == OBJ_TREE) 496 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 497 page = "tree";
390 else if (obj->type == OBJ_TAG) 498 else if (obj->type == OBJ_TAG)
391 page = "tag"; 499 page = "tag";
392 else 500 else
393 page = "blob"; 501 page = "blob";
394 name = fmt("%s %s...", typename(obj->type), shortrev); 502 name = fmt("%s %s...", typename(obj->type), shortrev);
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 503 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 504}
397 505
398void cgit_print_date(time_t secs, char *format, int local_time) 506void cgit_print_date(time_t secs, const char *format, int local_time)
399{ 507{
400 char buf[64]; 508 char buf[64];
401 struct tm *time; 509 struct tm *time;
402 510
403 if (!secs) 511 if (!secs)
404 return; 512 return;
405 if(local_time) 513 if(local_time)
406 time = localtime(&secs); 514 time = localtime(&secs);
407 else 515 else
408 time = gmtime(&secs); 516 time = gmtime(&secs);
409 strftime(buf, sizeof(buf)-1, format, time); 517 strftime(buf, sizeof(buf)-1, format, time);
410 html_txt(buf); 518 html_txt(buf);
411} 519}
412 520
413void cgit_print_age(time_t t, time_t max_relative, char *format) 521void cgit_print_age(time_t t, time_t max_relative, const char *format)
414{ 522{
415 time_t now, secs; 523 time_t now, secs;
416 524
417 if (!t) 525 if (!t)
418 return; 526 return;
419 time(&now); 527 time(&now);
420 secs = now - t; 528 secs = now - t;
421 529
422 if (secs > max_relative && max_relative >= 0) { 530 if (secs > max_relative && max_relative >= 0) {
423 cgit_print_date(t, format, ctx.cfg.local_time); 531 cgit_print_date(t, format, ctx.cfg.local_time);
424 return; 532 return;
425 } 533 }
426 534
427 if (secs < TM_HOUR * 2) { 535 if (secs < TM_HOUR * 2) {
428 htmlf("<span class='age-mins'>%.0f min.</span>", 536 htmlf("<span class='age-mins'>%.0f min.</span>",
429 secs * 1.0 / TM_MIN); 537 secs * 1.0 / TM_MIN);
430 return; 538 return;
431 } 539 }
432 if (secs < TM_DAY * 2) { 540 if (secs < TM_DAY * 2) {
433 htmlf("<span class='age-hours'>%.0f hours</span>", 541 htmlf("<span class='age-hours'>%.0f hours</span>",
434 secs * 1.0 / TM_HOUR); 542 secs * 1.0 / TM_HOUR);
435 return; 543 return;
436 } 544 }
437 if (secs < TM_WEEK * 2) { 545 if (secs < TM_WEEK * 2) {
@@ -488,49 +596,49 @@ void cgit_print_docstart(struct cgit_context *ctx)
488 } 596 }
489 597
490 char *host = cgit_hosturl(); 598 char *host = cgit_hosturl();
491 html(cgit_doctype); 599 html(cgit_doctype);
492 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 600 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
493 html("<head>\n"); 601 html("<head>\n");
494 html("<title>"); 602 html("<title>");
495 html_txt(ctx->page.title); 603 html_txt(ctx->page.title);
496 html("</title>\n"); 604 html("</title>\n");
497 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 605 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
498 if (ctx->cfg.robots && *ctx->cfg.robots) 606 if (ctx->cfg.robots && *ctx->cfg.robots)
499 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 607 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
500 html("<link rel='stylesheet' type='text/css' href='"); 608 html("<link rel='stylesheet' type='text/css' href='");
501 html_attr(ctx->cfg.css); 609 html_attr(ctx->cfg.css);
502 html("'/>\n"); 610 html("'/>\n");
503 if (ctx->cfg.favicon) { 611 if (ctx->cfg.favicon) {
504 html("<link rel='shortcut icon' href='"); 612 html("<link rel='shortcut icon' href='");
505 html_attr(ctx->cfg.favicon); 613 html_attr(ctx->cfg.favicon);
506 html("'/>\n"); 614 html("'/>\n");
507 } 615 }
508 if (host && ctx->repo) { 616 if (host && ctx->repo) {
509 html("<link rel='alternate' title='Atom feed' href='"); 617 html("<link rel='alternate' title='Atom feed' href='");
510 html(cgit_httpscheme()); 618 html(cgit_httpscheme());
511 html_attr(cgit_hosturl()); 619 html_attr(cgit_hosturl());
512 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 620 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
513 fmt("h=%s", ctx->qry.head))); 621 fmt("h=%s", ctx->qry.head)));
514 html("' type='application/atom+xml'/>\n"); 622 html("' type='application/atom+xml'/>\n");
515 } 623 }
516 if (ctx->cfg.head_include) 624 if (ctx->cfg.head_include)
517 html_include(ctx->cfg.head_include); 625 html_include(ctx->cfg.head_include);
518 html("</head>\n"); 626 html("</head>\n");
519 html("<body>\n"); 627 html("<body>\n");
520 if (ctx->cfg.header) 628 if (ctx->cfg.header)
521 html_include(ctx->cfg.header); 629 html_include(ctx->cfg.header);
522} 630}
523 631
524void cgit_print_docend() 632void cgit_print_docend()
525{ 633{
526 html("</div> <!-- class=content -->\n"); 634 html("</div> <!-- class=content -->\n");
527 if (ctx.cfg.embedded) { 635 if (ctx.cfg.embedded) {
528 html("</div> <!-- id=cgit -->\n"); 636 html("</div> <!-- id=cgit -->\n");
529 if (ctx.cfg.footer) 637 if (ctx.cfg.footer)
530 html_include(ctx.cfg.footer); 638 html_include(ctx.cfg.footer);
531 return; 639 return;
532 } 640 }
533 if (ctx.cfg.footer) 641 if (ctx.cfg.footer)
534 html_include(ctx.cfg.footer); 642 html_include(ctx.cfg.footer);
535 else { 643 else {
536 htmlf("<div class='footer'>generated by cgit %s at ", 644 htmlf("<div class='footer'>generated by cgit %s at ",
@@ -568,83 +676,103 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
568 return 1; 676 return 1;
569 if (obj->type == OBJ_TAG) { 677 if (obj->type == OBJ_TAG) {
570 tag = lookup_tag(sha1); 678 tag = lookup_tag(sha1);
571 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 679 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
572 return 0; 680 return 0;
573 hashcpy(fileid, tag->tagged->sha1); 681 hashcpy(fileid, tag->tagged->sha1);
574 } else if (obj->type != OBJ_BLOB) { 682 } else if (obj->type != OBJ_BLOB) {
575 return 0; 683 return 0;
576 } else { 684 } else {
577 hashcpy(fileid, sha1); 685 hashcpy(fileid, sha1);
578 } 686 }
579 if (!*header) { 687 if (!*header) {
580 html("<h1>download</h1>\n"); 688 html("<h1>download</h1>\n");
581 *header = 1; 689 *header = 1;
582 } 690 }
583 url = cgit_pageurl(ctx.qry.repo, "blob", 691 url = cgit_pageurl(ctx.qry.repo, "blob",
584 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 692 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
585 buf)); 693 buf));
586 html_link_open(url, NULL, "menu"); 694 html_link_open(url, NULL, "menu");
587 html_txt(strlpart(buf, 20)); 695 html_txt(strlpart(buf, 20));
588 html_link_close(); 696 html_link_close();
589 return 0; 697 return 0;
590} 698}
591 699
592void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 700void cgit_add_hidden_formfields(int incl_head, int incl_search,
701 const char *page)
593{ 702{
594 char *url; 703 char *url;
595 704
596 if (!ctx.cfg.virtual_root) { 705 if (!ctx.cfg.virtual_root) {
597 url = fmt("%s/%s", ctx.qry.repo, page); 706 url = fmt("%s/%s", ctx.qry.repo, page);
598 if (ctx.qry.path) 707 if (ctx.qry.vpath)
599 url = fmt("%s/%s", url, ctx.qry.path); 708 url = fmt("%s/%s", url, ctx.qry.vpath);
600 html_hidden("url", url); 709 html_hidden("url", url);
601 } 710 }
602 711
603 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 712 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
604 strcmp(ctx.qry.head, ctx.repo->defbranch)) 713 strcmp(ctx.qry.head, ctx.repo->defbranch))
605 html_hidden("h", ctx.qry.head); 714 html_hidden("h", ctx.qry.head);
606 715
607 if (ctx.qry.sha1) 716 if (ctx.qry.sha1)
608 html_hidden("id", ctx.qry.sha1); 717 html_hidden("id", ctx.qry.sha1);
609 if (ctx.qry.sha2) 718 if (ctx.qry.sha2)
610 html_hidden("id2", ctx.qry.sha2); 719 html_hidden("id2", ctx.qry.sha2);
611 if (ctx.qry.showmsg) 720 if (ctx.qry.showmsg)
612 html_hidden("showmsg", "1"); 721 html_hidden("showmsg", "1");
613 722
614 if (incl_search) { 723 if (incl_search) {
615 if (ctx.qry.grep) 724 if (ctx.qry.grep)
616 html_hidden("qt", ctx.qry.grep); 725 html_hidden("qt", ctx.qry.grep);
617 if (ctx.qry.search) 726 if (ctx.qry.search)
618 html_hidden("q", ctx.qry.search); 727 html_hidden("q", ctx.qry.search);
619 } 728 }
620} 729}
621 730
622const char *fallback_cmd = "repolist"; 731static const char *hc(struct cgit_context *ctx, const char *page)
732{
733 return strcmp(ctx->qry.page, page) ? NULL : "active";
734}
623 735
624char *hc(struct cgit_cmd *cmd, const char *page) 736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
625{ 737{
626 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 738 char *old_path = ctx->qry.path;
739 char *p = path, *q, *end = path + strlen(path);
740
741 ctx->qry.path = NULL;
742 cgit_self_link("root", NULL, NULL, ctx);
743 ctx->qry.path = p = path;
744 while (p < end) {
745 if (!(q = strchr(p, '/')))
746 q = end;
747 *q = '\0';
748 html_txt("/");
749 cgit_self_link(p, NULL, NULL, ctx);
750 if (q < end)
751 *q = '/';
752 p = q + 1;
753 }
754 ctx->qry.path = old_path;
627} 755}
628 756
629static void print_header(struct cgit_context *ctx) 757static void print_header(struct cgit_context *ctx)
630{ 758{
631 html("<table id='header'>\n"); 759 html("<table id='header'>\n");
632 html("<tr>\n"); 760 html("<tr>\n");
633 761
634 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 762 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
635 html("<td class='logo' rowspan='2'><a href='"); 763 html("<td class='logo' rowspan='2'><a href='");
636 if (ctx->cfg.logo_link) 764 if (ctx->cfg.logo_link)
637 html_attr(ctx->cfg.logo_link); 765 html_attr(ctx->cfg.logo_link);
638 else 766 else
639 html_attr(cgit_rooturl()); 767 html_attr(cgit_rooturl());
640 html("'><img src='"); 768 html("'><img src='");
641 html_attr(ctx->cfg.logo); 769 html_attr(ctx->cfg.logo);
642 html("' alt='cgit logo'/></a></td>\n"); 770 html("' alt='cgit logo'/></a></td>\n");
643 } 771 }
644 772
645 html("<td class='main'>"); 773 html("<td class='main'>");
646 if (ctx->repo) { 774 if (ctx->repo) {
647 cgit_index_link("index", NULL, NULL, NULL, 0); 775 cgit_index_link("index", NULL, NULL, NULL, 0);
648 html(" : "); 776 html(" : ");
649 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 777 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
650 html("</td><td class='form'>"); 778 html("</td><td class='form'>");
@@ -654,120 +782,128 @@ static void print_header(struct cgit_context *ctx)
654 for_each_branch_ref(print_branch_option, ctx->qry.head); 782 for_each_branch_ref(print_branch_option, ctx->qry.head);
655 html("</select> "); 783 html("</select> ");
656 html("<input type='submit' name='' value='switch'/>"); 784 html("<input type='submit' name='' value='switch'/>");
657 html("</form>"); 785 html("</form>");
658 } else 786 } else
659 html_txt(ctx->cfg.root_title); 787 html_txt(ctx->cfg.root_title);
660 html("</td></tr>\n"); 788 html("</td></tr>\n");
661 789
662 html("<tr><td class='sub'>"); 790 html("<tr><td class='sub'>");
663 if (ctx->repo) { 791 if (ctx->repo) {
664 html_txt(ctx->repo->desc); 792 html_txt(ctx->repo->desc);
665 html("</td><td class='sub right'>"); 793 html("</td><td class='sub right'>");
666 html_txt(ctx->repo->owner); 794 html_txt(ctx->repo->owner);
667 } else { 795 } else {
668 if (ctx->cfg.root_desc) 796 if (ctx->cfg.root_desc)
669 html_txt(ctx->cfg.root_desc); 797 html_txt(ctx->cfg.root_desc);
670 else if (ctx->cfg.index_info) 798 else if (ctx->cfg.index_info)
671 html_include(ctx->cfg.index_info); 799 html_include(ctx->cfg.index_info);
672 } 800 }
673 html("</td></tr></table>\n"); 801 html("</td></tr></table>\n");
674} 802}
675 803
676void cgit_print_pageheader(struct cgit_context *ctx) 804void cgit_print_pageheader(struct cgit_context *ctx)
677{ 805{
678 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
679
680 if (!cmd && ctx->repo)
681 fallback_cmd = "summary";
682
683 html("<div id='cgit'>"); 806 html("<div id='cgit'>");
684 if (!ctx->cfg.noheader) 807 if (!ctx->cfg.noheader)
685 print_header(ctx); 808 print_header(ctx);
686 809
687 html("<table class='tabs'><tr><td>\n"); 810 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 811 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 812 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
690 ctx->qry.head); 813 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 814 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 815 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 816 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 817 NULL, ctx->qry.vpath, 0, NULL, NULL,
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 818 ctx->qry.showmsg);
696 ctx->qry.sha1, NULL); 819 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 820 ctx->qry.sha1, ctx->qry.vpath);
698 ctx->qry.head, ctx->qry.sha1); 821 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 822 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 823 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
824 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
701 if (ctx->repo->max_stats) 825 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 826 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
703 ctx->qry.head, NULL); 827 ctx->qry.head, ctx->qry.vpath);
704 if (ctx->repo->readme) 828 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 829 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 830 hc(ctx, "about"), ctx->qry.head, NULL,
707 NULL); 831 NULL);
708 html("</td><td class='form'>"); 832 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 833 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 834 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 835 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 836 ctx->qry.vpath, NULL));
713 html("'>\n"); 837 html("'>\n");
714 cgit_add_hidden_formfields(1, 0, "log"); 838 cgit_add_hidden_formfields(1, 0, "log");
715 html("<select name='qt'>\n"); 839 html("<select name='qt'>\n");
716 html_option("grep", "log msg", ctx->qry.grep); 840 html_option("grep", "log msg", ctx->qry.grep);
717 html_option("author", "author", ctx->qry.grep); 841 html_option("author", "author", ctx->qry.grep);
718 html_option("committer", "committer", ctx->qry.grep); 842 html_option("committer", "committer", ctx->qry.grep);
843 html_option("range", "range", ctx->qry.grep);
719 html("</select>\n"); 844 html("</select>\n");
720 html("<input class='txt' type='text' size='10' name='q' value='"); 845 html("<input class='txt' type='text' size='10' name='q' value='");
721 html_attr(ctx->qry.search); 846 html_attr(ctx->qry.search);
722 html("'/>\n"); 847 html("'/>\n");
723 html("<input type='submit' value='search'/>\n"); 848 html("<input type='submit' value='search'/>\n");
724 html("</form>\n"); 849 html("</form>\n");
725 } else { 850 } else {
726 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 851 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
727 if (ctx->cfg.root_readme) 852 if (ctx->cfg.root_readme)
728 site_link("about", "about", NULL, hc(cmd, "about"), 853 site_link("about", "about", NULL, hc(ctx, "about"),
729 NULL, 0); 854 NULL, 0);
730 html("</td><td class='form'>"); 855 html("</td><td class='form'>");
731 html("<form method='get' action='"); 856 html("<form method='get' action='");
732 html_attr(cgit_rooturl()); 857 html_attr(cgit_rooturl());
733 html("'>\n"); 858 html("'>\n");
734 html("<input type='text' name='q' size='10' value='"); 859 html("<input type='text' name='q' size='10' value='");
735 html_attr(ctx->qry.search); 860 html_attr(ctx->qry.search);
736 html("'/>\n"); 861 html("'/>\n");
737 html("<input type='submit' value='search'/>\n"); 862 html("<input type='submit' value='search'/>\n");
738 html("</form>"); 863 html("</form>");
739 } 864 }
740 html("</td></tr></table>\n"); 865 html("</td></tr></table>\n");
866 if (ctx->qry.vpath) {
867 html("<div class='path'>");
868 html("path: ");
869 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
870 html("</div>");
871 }
741 html("<div class='content'>"); 872 html("<div class='content'>");
742} 873}
743 874
744void cgit_print_filemode(unsigned short mode) 875void cgit_print_filemode(unsigned short mode)
745{ 876{
746 if (S_ISDIR(mode)) 877 if (S_ISDIR(mode))
747 html("d"); 878 html("d");
748 else if (S_ISLNK(mode)) 879 else if (S_ISLNK(mode))
749 html("l"); 880 html("l");
750 else if (S_ISGITLINK(mode)) 881 else if (S_ISGITLINK(mode))
751 html("m"); 882 html("m");
752 else 883 else
753 html("-"); 884 html("-");
754 html_fileperm(mode >> 6); 885 html_fileperm(mode >> 6);
755 html_fileperm(mode >> 3); 886 html_fileperm(mode >> 3);
756 html_fileperm(mode); 887 html_fileperm(mode);
757} 888}
758 889
759void cgit_print_snapshot_links(const char *repo, const char *head, 890void cgit_print_snapshot_links(const char *repo, const char *head,
760 const char *hex, int snapshots) 891 const char *hex, int snapshots)
761{ 892{
762 const struct cgit_snapshot_format* f; 893 const struct cgit_snapshot_format* f;
894 char *prefix;
763 char *filename; 895 char *filename;
896 unsigned char sha1[20];
764 897
898 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
899 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
900 hex++;
901 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
765 for (f = cgit_snapshot_formats; f->suffix; f++) { 902 for (f = cgit_snapshot_formats; f->suffix; f++) {
766 if (!(snapshots & f->bit)) 903 if (!(snapshots & f->bit))
767 continue; 904 continue;
768 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 905 filename = fmt("%s%s", prefix, f->suffix);
769 f->suffix);
770 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 906 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
771 html("<br/>"); 907 html("<br/>");
772 } 908 }
773} 909}
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..3cc1258 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,51 +1,66 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_rooturl(); 6extern char *cgit_rooturl();
7extern char *cgit_repourl(const char *reponame); 7extern char *cgit_repourl(const char *reponame);
8extern char *cgit_fileurl(const char *reponame, const char *pagename, 8extern char *cgit_fileurl(const char *reponame, const char *pagename,
9 const char *filename, const char *query); 9 const char *filename, const char *query);
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(const char *name, const char *title,
14 char *pattern, int ofs); 14 const char *class, const char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(const char *name, const char *title,
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16 const char *class, const char *head);
17 char *rev); 17extern void cgit_tag_link(const char *name, const char *title,
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18 const char *class, const char *head,
19 char *rev, char *path); 19 const char *rev);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_tree_link(const char *name, const char *title,
21 char *rev, char *path); 21 const char *class, const char *head,
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22 const char *rev, const char *path);
23 char *rev, char *path, int ofs, char *grep, 23extern void cgit_plain_link(const char *name, const char *title,
24 char *pattern, int showmsg); 24 const char *class, const char *head,
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25 const char *rev, const char *path);
26 char *rev); 26extern void cgit_log_link(const char *name, const char *title,
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27 const char *class, const char *head, const char *rev,
28 char *rev); 28 const char *path, int ofs, const char *grep,
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29 const char *pattern, int showmsg);
30 char *rev, char *path); 30extern void cgit_commit_link(char *name, const char *title,
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31 const char *class, const char *head,
32 char *head, char *rev, char *archivename); 32 const char *rev, const char *path,
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33 int toggle_ssdiff);
34 char *new_rev, char *old_rev, char *path); 34extern void cgit_patch_link(const char *name, const char *title,
35extern void cgit_stats_link(char *name, char *title, char *class, char *head, 35 const char *class, const char *head,
36 char *path); 36 const char *rev, const char *path);
37extern void cgit_refs_link(const char *name, const char *title,
38 const char *class, const char *head,
39 const char *rev, const char *path);
40extern void cgit_snapshot_link(const char *name, const char *title,
41 const char *class, const char *head,
42 const char *rev, const char *archivename);
43extern void cgit_diff_link(const char *name, const char *title,
44 const char *class, const char *head,
45 const char *new_rev, const char *old_rev,
46 const char *path, int toggle_ssdiff);
47extern void cgit_stats_link(const char *name, const char *title,
48 const char *class, const char *head,
49 const char *path);
50extern void cgit_self_link(char *name, const char *title,
51 const char *class, struct cgit_context *ctx);
37extern void cgit_object_link(struct object *obj); 52extern void cgit_object_link(struct object *obj);
38 53
39extern void cgit_print_error(char *msg); 54extern void cgit_print_error(const char *msg);
40extern void cgit_print_date(time_t secs, char *format, int local_time); 55extern void cgit_print_date(time_t secs, const char *format, int local_time);
41extern void cgit_print_age(time_t t, time_t max_relative, char *format); 56extern void cgit_print_age(time_t t, time_t max_relative, const char *format);
42extern void cgit_print_http_headers(struct cgit_context *ctx); 57extern void cgit_print_http_headers(struct cgit_context *ctx);
43extern void cgit_print_docstart(struct cgit_context *ctx); 58extern void cgit_print_docstart(struct cgit_context *ctx);
44extern void cgit_print_docend(); 59extern void cgit_print_docend();
45extern void cgit_print_pageheader(struct cgit_context *ctx); 60extern void cgit_print_pageheader(struct cgit_context *ctx);
46extern void cgit_print_filemode(unsigned short mode); 61extern void cgit_print_filemode(unsigned short mode);
47extern void cgit_print_snapshot_links(const char *repo, const char *head, 62extern void cgit_print_snapshot_links(const char *repo, const char *head,
48 const char *hex, int snapshots); 63 const char *hex, int snapshots);
49extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 64extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
50 char *page); 65 const char *page);
51#endif /* UI_SHARED_H */ 66#endif /* UI_SHARED_H */
diff --git a/ui-snapshot.c b/ui-snapshot.c
index dbb5564..6e3412c 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -14,53 +14,59 @@ static int write_compressed_tar_archive(struct archiver_args *args,const char *f
14{ 14{
15 int rv; 15 int rv;
16 struct cgit_filter f; 16 struct cgit_filter f;
17 17
18 f.cmd = xstrdup(filter); 18 f.cmd = xstrdup(filter);
19 f.argv = malloc(2 * sizeof(char *)); 19 f.argv = malloc(2 * sizeof(char *));
20 f.argv[0] = f.cmd; 20 f.argv[0] = f.cmd;
21 f.argv[1] = NULL; 21 f.argv[1] = NULL;
22 cgit_open_filter(&f); 22 cgit_open_filter(&f);
23 rv = write_tar_archive(args); 23 rv = write_tar_archive(args);
24 cgit_close_filter(&f); 24 cgit_close_filter(&f);
25 return rv; 25 return rv;
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)
55 continue; 61 continue;
56 if (!strcmp(fmt->suffix, filename + fl - sl)) 62 if (!strcmp(fmt->suffix, filename + fl - sl))
57 return fmt; 63 return fmt;
58 } 64 }
59 return NULL; 65 return NULL;
60} 66}
61 67
62static int make_snapshot(const struct cgit_snapshot_format *format, 68static int make_snapshot(const struct cgit_snapshot_format *format,
63 const char *hex, const char *prefix, 69 const char *hex, const char *prefix,
64 const char *filename) 70 const char *filename)
65{ 71{
66 struct archiver_args args; 72 struct archiver_args args;
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-stats.c b/ui-stats.c
index bdaf9cc..946a6ea 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -1,31 +1,37 @@
1#include <string-list.h> 1#include <string-list.h>
2 2
3#include "cgit.h" 3#include "cgit.h"
4#include "html.h" 4#include "html.h"
5#include "ui-shared.h" 5#include "ui-shared.h"
6#include "ui-stats.h" 6#include "ui-stats.h"
7 7
8#ifdef NO_C99_FORMAT
9#define SZ_FMT "%u"
10#else
11#define SZ_FMT "%zu"
12#endif
13
8#define MONTHS 6 14#define MONTHS 6
9 15
10struct authorstat { 16struct authorstat {
11 long total; 17 long total;
12 struct string_list list; 18 struct string_list list;
13}; 19};
14 20
15#define DAY_SECS (60 * 60 * 24) 21#define DAY_SECS (60 * 60 * 24)
16#define WEEK_SECS (DAY_SECS * 7) 22#define WEEK_SECS (DAY_SECS * 7)
17 23
18static void trunc_week(struct tm *tm) 24static void trunc_week(struct tm *tm)
19{ 25{
20 time_t t = timegm(tm); 26 time_t t = timegm(tm);
21 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS; 27 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
22 gmtime_r(&t, tm); 28 gmtime_r(&t, tm);
23} 29}
24 30
25static void dec_week(struct tm *tm) 31static void dec_week(struct tm *tm)
26{ 32{
27 time_t t = timegm(tm); 33 time_t t = timegm(tm);
28 t -= WEEK_SECS; 34 t -= WEEK_SECS;
29 gmtime_r(&t, tm); 35 gmtime_r(&t, tm);
30} 36}
31 37
@@ -154,60 +160,60 @@ int cgit_find_stats_period(const char *expr, struct cgit_period **period)
154 return 0; 160 return 0;
155} 161}
156 162
157const char *cgit_find_stats_periodname(int idx) 163const char *cgit_find_stats_periodname(int idx)
158{ 164{
159 if (idx > 0 && idx < 4) 165 if (idx > 0 && idx < 4)
160 return periods[idx - 1].name; 166 return periods[idx - 1].name;
161 else 167 else
162 return ""; 168 return "";
163} 169}
164 170
165static void add_commit(struct string_list *authors, struct commit *commit, 171static void add_commit(struct string_list *authors, struct commit *commit,
166 struct cgit_period *period) 172 struct cgit_period *period)
167{ 173{
168 struct commitinfo *info; 174 struct commitinfo *info;
169 struct string_list_item *author, *item; 175 struct string_list_item *author, *item;
170 struct authorstat *authorstat; 176 struct authorstat *authorstat;
171 struct string_list *items; 177 struct string_list *items;
172 char *tmp; 178 char *tmp;
173 struct tm *date; 179 struct tm *date;
174 time_t t; 180 time_t t;
175 181
176 info = cgit_parse_commit(commit); 182 info = cgit_parse_commit(commit);
177 tmp = xstrdup(info->author); 183 tmp = xstrdup(info->author);
178 author = string_list_insert(tmp, authors); 184 author = string_list_insert(authors, tmp);
179 if (!author->util) 185 if (!author->util)
180 author->util = xcalloc(1, sizeof(struct authorstat)); 186 author->util = xcalloc(1, sizeof(struct authorstat));
181 else 187 else
182 free(tmp); 188 free(tmp);
183 authorstat = author->util; 189 authorstat = author->util;
184 items = &authorstat->list; 190 items = &authorstat->list;
185 t = info->committer_date; 191 t = info->committer_date;
186 date = gmtime(&t); 192 date = gmtime(&t);
187 period->trunc(date); 193 period->trunc(date);
188 tmp = xstrdup(period->pretty(date)); 194 tmp = xstrdup(period->pretty(date));
189 item = string_list_insert(tmp, items); 195 item = string_list_insert(items, tmp);
190 if (item->util) 196 if (item->util)
191 free(tmp); 197 free(tmp);
192 item->util++; 198 item->util++;
193 authorstat->total++; 199 authorstat->total++;
194 cgit_free_commitinfo(info); 200 cgit_free_commitinfo(info);
195} 201}
196 202
197static int cmp_total_commits(const void *a1, const void *a2) 203static int cmp_total_commits(const void *a1, const void *a2)
198{ 204{
199 const struct string_list_item *i1 = a1; 205 const struct string_list_item *i1 = a1;
200 const struct string_list_item *i2 = a2; 206 const struct string_list_item *i2 = a2;
201 const struct authorstat *auth1 = i1->util; 207 const struct authorstat *auth1 = i1->util;
202 const struct authorstat *auth2 = i2->util; 208 const struct authorstat *auth2 = i2->util;
203 209
204 return auth2->total - auth1->total; 210 return auth2->total - auth1->total;
205} 211}
206 212
207/* Walk the commit DAG and collect number of commits per author per 213/* Walk the commit DAG and collect number of commits per author per
208 * timeperiod into a nested string_list collection. 214 * timeperiod into a nested string_list collection.
209 */ 215 */
210struct string_list collect_stats(struct cgit_context *ctx, 216struct string_list collect_stats(struct cgit_context *ctx,
211 struct cgit_period *period) 217 struct cgit_period *period)
212{ 218{
213 struct string_list authors; 219 struct string_list authors;
@@ -258,137 +264,137 @@ void print_combined_authorrow(struct string_list *authors, int from, int to,
258 struct string_list *items; 264 struct string_list *items;
259 struct string_list_item *date; 265 struct string_list_item *date;
260 time_t now; 266 time_t now;
261 long i, j, total, subtotal; 267 long i, j, total, subtotal;
262 struct tm *tm; 268 struct tm *tm;
263 char *tmp; 269 char *tmp;
264 270
265 time(&now); 271 time(&now);
266 tm = gmtime(&now); 272 tm = gmtime(&now);
267 period->trunc(tm); 273 period->trunc(tm);
268 for (i = 1; i < period->count; i++) 274 for (i = 1; i < period->count; i++)
269 period->dec(tm); 275 period->dec(tm);
270 276
271 total = 0; 277 total = 0;
272 htmlf("<tr><td class='%s'>%s</td>", leftclass, 278 htmlf("<tr><td class='%s'>%s</td>", leftclass,
273 fmt(name, to - from + 1)); 279 fmt(name, to - from + 1));
274 for (j = 0; j < period->count; j++) { 280 for (j = 0; j < period->count; j++) {
275 tmp = period->pretty(tm); 281 tmp = period->pretty(tm);
276 period->inc(tm); 282 period->inc(tm);
277 subtotal = 0; 283 subtotal = 0;
278 for (i = from; i <= to; i++) { 284 for (i = from; i <= to; i++) {
279 author = &authors->items[i]; 285 author = &authors->items[i];
280 authorstat = author->util; 286 authorstat = author->util;
281 items = &authorstat->list; 287 items = &authorstat->list;
282 date = string_list_lookup(tmp, items); 288 date = string_list_lookup(items, tmp);
283 if (date) 289 if (date)
284 subtotal += (size_t)date->util; 290 subtotal += (size_t)date->util;
285 } 291 }
286 htmlf("<td class='%s'>%d</td>", centerclass, subtotal); 292 htmlf("<td class='%s'>%ld</td>", centerclass, subtotal);
287 total += subtotal; 293 total += subtotal;
288 } 294 }
289 htmlf("<td class='%s'>%d</td></tr>", rightclass, total); 295 htmlf("<td class='%s'>%ld</td></tr>", rightclass, total);
290} 296}
291 297
292void print_authors(struct string_list *authors, int top, 298void print_authors(struct string_list *authors, int top,
293 struct cgit_period *period) 299 struct cgit_period *period)
294{ 300{
295 struct string_list_item *author; 301 struct string_list_item *author;
296 struct authorstat *authorstat; 302 struct authorstat *authorstat;
297 struct string_list *items; 303 struct string_list *items;
298 struct string_list_item *date; 304 struct string_list_item *date;
299 time_t now; 305 time_t now;
300 long i, j, total; 306 long i, j, total;
301 struct tm *tm; 307 struct tm *tm;
302 char *tmp; 308 char *tmp;
303 309
304 time(&now); 310 time(&now);
305 tm = gmtime(&now); 311 tm = gmtime(&now);
306 period->trunc(tm); 312 period->trunc(tm);
307 for (i = 1; i < period->count; i++) 313 for (i = 1; i < period->count; i++)
308 period->dec(tm); 314 period->dec(tm);
309 315
310 html("<table class='stats'><tr><th>Author</th>"); 316 html("<table class='stats'><tr><th>Author</th>");
311 for (j = 0; j < period->count; j++) { 317 for (j = 0; j < period->count; j++) {
312 tmp = period->pretty(tm); 318 tmp = period->pretty(tm);
313 htmlf("<th>%s</th>", tmp); 319 htmlf("<th>%s</th>", tmp);
314 period->inc(tm); 320 period->inc(tm);
315 } 321 }
316 html("<th>Total</th></tr>\n"); 322 html("<th>Total</th></tr>\n");
317 323
318 if (top <= 0 || top > authors->nr) 324 if (top <= 0 || top > authors->nr)
319 top = authors->nr; 325 top = authors->nr;
320 326
321 for (i = 0; i < top; i++) { 327 for (i = 0; i < top; i++) {
322 author = &authors->items[i]; 328 author = &authors->items[i];
323 html("<tr><td class='left'>"); 329 html("<tr><td class='left'>");
324 html_txt(author->string); 330 html_txt(author->string);
325 html("</td>"); 331 html("</td>");
326 authorstat = author->util; 332 authorstat = author->util;
327 items = &authorstat->list; 333 items = &authorstat->list;
328 total = 0; 334 total = 0;
329 for (j = 0; j < period->count; j++) 335 for (j = 0; j < period->count; j++)
330 period->dec(tm); 336 period->dec(tm);
331 for (j = 0; j < period->count; j++) { 337 for (j = 0; j < period->count; j++) {
332 tmp = period->pretty(tm); 338 tmp = period->pretty(tm);
333 period->inc(tm); 339 period->inc(tm);
334 date = string_list_lookup(tmp, items); 340 date = string_list_lookup(items, tmp);
335 if (!date) 341 if (!date)
336 html("<td>0</td>"); 342 html("<td>0</td>");
337 else { 343 else {
338 htmlf("<td>%d</td>", date->util); 344 htmlf("<td>"SZ_FMT"</td>", (size_t)date->util);
339 total += (size_t)date->util; 345 total += (size_t)date->util;
340 } 346 }
341 } 347 }
342 htmlf("<td class='sum'>%d</td></tr>", total); 348 htmlf("<td class='sum'>%ld</td></tr>", total);
343 } 349 }
344 350
345 if (top < authors->nr) 351 if (top < authors->nr)
346 print_combined_authorrow(authors, top, authors->nr - 1, 352 print_combined_authorrow(authors, top, authors->nr - 1,
347 "Others (%d)", "left", "", "sum", period); 353 "Others (%ld)", "left", "", "sum", period);
348 354
349 print_combined_authorrow(authors, 0, authors->nr - 1, "Total", 355 print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
350 "total", "sum", "sum", period); 356 "total", "sum", "sum", period);
351 html("</table>"); 357 html("</table>");
352} 358}
353 359
354/* Create a sorted string_list with one entry per author. The util-field 360/* Create a sorted string_list with one entry per author. The util-field
355 * for each author is another string_list which is used to calculate the 361 * for each author is another string_list which is used to calculate the
356 * number of commits per time-interval. 362 * number of commits per time-interval.
357 */ 363 */
358void cgit_show_stats(struct cgit_context *ctx) 364void cgit_show_stats(struct cgit_context *ctx)
359{ 365{
360 struct string_list authors; 366 struct string_list authors;
361 struct cgit_period *period; 367 struct cgit_period *period;
362 int top, i; 368 int top, i;
363 const char *code = "w"; 369 const char *code = "w";
364 370
365 if (ctx->qry.period) 371 if (ctx->qry.period)
366 code = ctx->qry.period; 372 code = ctx->qry.period;
367 373
368 i = cgit_find_stats_period(code, &period); 374 i = cgit_find_stats_period(code, &period);
369 if (!i) { 375 if (!i) {
370 cgit_print_error(fmt("Unknown statistics type: %c", code)); 376 cgit_print_error(fmt("Unknown statistics type: %c", code[0]));
371 return; 377 return;
372 } 378 }
373 if (i > ctx->repo->max_stats) { 379 if (i > ctx->repo->max_stats) {
374 cgit_print_error(fmt("Statistics type disabled: %s", 380 cgit_print_error(fmt("Statistics type disabled: %s",
375 period->name)); 381 period->name));
376 return; 382 return;
377 } 383 }
378 authors = collect_stats(ctx, period); 384 authors = collect_stats(ctx, period);
379 qsort(authors.items, authors.nr, sizeof(struct string_list_item), 385 qsort(authors.items, authors.nr, sizeof(struct string_list_item),
380 cmp_total_commits); 386 cmp_total_commits);
381 387
382 top = ctx->qry.ofs; 388 top = ctx->qry.ofs;
383 if (!top) 389 if (!top)
384 top = 10; 390 top = 10;
385 htmlf("<h2>Commits per author per %s", period->name); 391 htmlf("<h2>Commits per author per %s", period->name);
386 if (ctx->qry.path) { 392 if (ctx->qry.path) {
387 html(" (path '"); 393 html(" (path '");
388 html_txt(ctx->qry.path); 394 html_txt(ctx->qry.path);
389 html("')"); 395 html("')");
390 } 396 }
391 html("</h2>"); 397 html("</h2>");
392 398
393 html("<form method='get' action='' style='float: right; text-align: right;'>"); 399 html("<form method='get' action='' style='float: right; text-align: right;'>");
394 cgit_add_hidden_formfields(1, 0, "stats"); 400 cgit_add_hidden_formfields(1, 0, "stats");
diff --git a/ui-summary.c b/ui-summary.c
index a2c018e..b203bcc 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -1,36 +1,38 @@
1/* ui-summary.c: functions for generating repo summary page 1/* ui-summary.c: functions for generating repo summary page
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
7 */ 8 */
8 9
9#include "cgit.h" 10#include "cgit.h"
10#include "html.h" 11#include "html.h"
11#include "ui-log.h" 12#include "ui-log.h"
12#include "ui-refs.h" 13#include "ui-refs.h"
14#include "ui-blob.h"
13 15
14int urls = 0; 16int urls = 0;
15 17
16static void print_url(char *base, char *suffix) 18static void print_url(char *base, char *suffix)
17{ 19{
18 if (!base || !*base) 20 if (!base || !*base)
19 return; 21 return;
20 if (urls++ == 0) { 22 if (urls++ == 0) {
21 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 23 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
22 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n"); 24 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n");
23 } 25 }
24 if (suffix && *suffix) 26 if (suffix && *suffix)
25 base = fmt("%s/%s", base, suffix); 27 base = fmt("%s/%s", base, suffix);
26 html("<tr><td colspan='4'><a href='"); 28 html("<tr><td colspan='4'><a href='");
27 html_url_path(base); 29 html_url_path(base);
28 html("'>"); 30 html("'>");
29 html_txt(base); 31 html_txt(base);
30 html("</a></td></tr>\n"); 32 html("</a></td></tr>\n");
31} 33}
32 34
33static void print_urls(char *txt, char *suffix) 35static void print_urls(char *txt, char *suffix)
34{ 36{
35 char *h = txt, *t, c; 37 char *h = txt, *t, c;
36 38
@@ -47,46 +49,76 @@ static void print_urls(char *txt, char *suffix)
47 h = t; 49 h = t;
48 } 50 }
49} 51}
50 52
51void cgit_print_summary() 53void cgit_print_summary()
52{ 54{
53 html("<table summary='repository info' class='list nowrap'>"); 55 html("<table summary='repository info' class='list nowrap'>");
54 cgit_print_branches(ctx.cfg.summary_branches); 56 cgit_print_branches(ctx.cfg.summary_branches);
55 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
56 cgit_print_tags(ctx.cfg.summary_tags); 58 cgit_print_tags(ctx.cfg.summary_tags);
57 if (ctx.cfg.summary_log > 0) { 59 if (ctx.cfg.summary_log > 0) {
58 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
59 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, 61 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
60 NULL, NULL, 0); 62 NULL, NULL, 0);
61 } 63 }
62 if (ctx.repo->clone_url) 64 if (ctx.repo->clone_url)
63 print_urls(ctx.repo->clone_url, NULL); 65 print_urls(ctx.repo->clone_url, NULL);
64 else if (ctx.cfg.clone_prefix) 66 else if (ctx.cfg.clone_prefix)
65 print_urls(ctx.cfg.clone_prefix, ctx.repo->url); 67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
66 html("</table>"); 68 html("</table>");
67} 69}
68 70
69void cgit_print_repo_readme(char *path) 71void cgit_print_repo_readme(char *path)
70{ 72{
71 char *slash, *tmp; 73 char *slash, *tmp, *colon, *ref;
72 74
73 if (!ctx.repo->readme) 75 if (!ctx.repo->readme || !(*ctx.repo->readme))
74 return; 76 return;
75 77
78 ref = NULL;
79
80 /* Check if the readme is tracked in the git repo. */
81 colon = strchr(ctx.repo->readme, ':');
82 if (colon && strlen(colon) > 1) {
83 *colon = '\0';
84 ref = ctx.repo->readme;
85 ctx.repo->readme = colon + 1;
86 if (!(*ctx.repo->readme))
87 return;
88 }
89
90 /* Prepend repo path to relative readme path unless tracked. */
91 if (!ref && *ctx.repo->readme != '/')
92 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path,
93 ctx.repo->readme));
94
95 /* If a subpath is specified for the about page, make it relative
96 * to the directory containing the configured readme.
97 */
76 if (path) { 98 if (path) {
77 slash = strrchr(ctx.repo->readme, '/'); 99 slash = strrchr(ctx.repo->readme, '/');
78 if (!slash) 100 if (!slash) {
101 if (!colon)
79 return; 102 return;
103 slash = colon;
104 }
80 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1); 105 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
81 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1); 106 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
82 strcpy(tmp + (slash - ctx.repo->readme + 1), path); 107 strcpy(tmp + (slash - ctx.repo->readme + 1), path);
83 } else 108 } else
84 tmp = ctx.repo->readme; 109 tmp = ctx.repo->readme;
110
111 /* Print the calculated readme, either from the git repo or from the
112 * filesystem, while applying the about-filter.
113 */
85 html("<div id='summary'>"); 114 html("<div id='summary'>");
86 if (ctx.repo->about_filter) 115 if (ctx.repo->about_filter)
87 cgit_open_filter(ctx.repo->about_filter); 116 cgit_open_filter(ctx.repo->about_filter);
117 if (ref)
118 cgit_print_file(tmp, ref);
119 else
88 html_include(tmp); 120 html_include(tmp);
89 if (ctx.repo->about_filter) 121 if (ctx.repo->about_filter)
90 cgit_close_filter(ctx.repo->about_filter); 122 cgit_close_filter(ctx.repo->about_filter);
91 html("</div>"); 123 html("</div>");
92} 124}
diff --git a/ui-tag.c b/ui-tag.c
index c2d72af..39e4cb8 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -9,84 +9,96 @@
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
13static void print_tag_content(char *buf) 13static void print_tag_content(char *buf)
14{ 14{
15 char *p; 15 char *p;
16 16
17 if (!buf) 17 if (!buf)
18 return; 18 return;
19 19
20 html("<div class='commit-subject'>"); 20 html("<div class='commit-subject'>");
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));
45 return; 53 return;
46 } 54 }
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..0b1b531 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -25,108 +25,114 @@ static void print_text_buffer(const char *name, char *buf, unsigned long size)
25 25
26 if (ctx.cfg.enable_tree_linenumbers) { 26 if (ctx.cfg.enable_tree_linenumbers) {
27 html("<tr><td class='linenumbers'><pre>"); 27 html("<tr><td class='linenumbers'><pre>");
28 idx = 0; 28 idx = 0;
29 lineno = 0; 29 lineno = 0;
30 30
31 if (size) { 31 if (size) {
32 htmlf(numberfmt, ++lineno); 32 htmlf(numberfmt, ++lineno);
33 while(idx < size - 1) { // skip absolute last newline 33 while(idx < size - 1) { // skip absolute last newline
34 if (buf[idx] == '\n') 34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno); 35 htmlf(numberfmt, ++lineno);
36 idx++; 36 idx++;
37 } 37 }
38 } 38 }
39 html("</pre></td>\n"); 39 html("</pre></td>\n");
40 } 40 }
41 else { 41 else {
42 html("<tr>\n"); 42 html("<tr>\n");
43 } 43 }
44 44
45 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size); 49 html_raw(buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
52 return; 52 return;
53 } 53 }
54 54
55 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
56 html_txt(buf); 56 html_txt(buf);
57 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
58} 58}
59 59
60#define ROWLEN 32 60#define ROWLEN 32
61 61
62static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
63{ 63{
64 unsigned long ofs, idx; 64 unsigned long ofs, idx;
65 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
66 66
67 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 htmlf("%*s%02x", 72 htmlf("%*s%02x",
73 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
74 buf[idx] & 0xff); 74 buf[idx] & 0xff);
75 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
78 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
79 html_txt(ascii); 79 html_txt(ascii);
80 html("</td></tr>\n"); 80 html("</td></tr>\n");
81 } 81 }
82 html("</table>\n"); 82 html("</table>\n");
83} 83}
84 84
85static void print_object(const unsigned char *sha1, char *path, const char *basename) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
86{ 86{
87 enum object_type type; 87 enum object_type type;
88 char *buf; 88 char *buf;
89 unsigned long size; 89 unsigned long size;
90 90
91 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
92 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
93 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
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 htmlf("blob: %s (", sha1_to_hex(sha1));
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 html(")\n");
109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size);
113 return;
114 }
109 115
110 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
111 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
112 else 118 else
113 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
114} 120}
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;
122 char *fullpath; 128 char *fullpath;
123 char *class; 129 char *class;
124 enum object_type type; 130 enum object_type type;
125 unsigned long size = 0; 131 unsigned long size = 0;
126 132
127 name = xstrdup(pathname); 133 name = xstrdup(pathname);
128 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
129 ctx.qry.path ? "/" : "", name); 135 ctx.qry.path ? "/" : "", name);
130 136
131 if (!S_ISGITLINK(mode)) { 137 if (!S_ISGITLINK(mode)) {
132 type = sha1_object_info(sha1, &size); 138 type = sha1_object_info(sha1, &size);
@@ -148,138 +154,129 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
148 sha1_to_hex(sha1))); 154 sha1_to_hex(sha1)));
149 html("'>"); 155 html("'>");
150 html_txt(name); 156 html_txt(name);
151 html("</a>"); 157 html("</a>");
152 } else if (S_ISDIR(mode)) { 158 } else if (S_ISDIR(mode)) {
153 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
154 curr_rev, fullpath); 160 curr_rev, fullpath);
155 } else { 161 } else {
156 class = strrchr(name, '.'); 162 class = strrchr(name, '.');
157 if (class != NULL) { 163 if (class != NULL) {
158 class = fmt("ls-blob %s", class + 1); 164 class = fmt("ls-blob %s", class + 1);
159 } else 165 } else
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>");
184 html("<th/>"); 192 html("<th/>");
185 html("</tr>\n"); 193 html("</tr>\n");
186 header = 1; 194 header = 1;
187} 195}
188 196
189static void ls_tail() 197static void ls_tail()
190{ 198{
191 if (!header) 199 if (!header)
192 return; 200 return;
193 html("</table>\n"); 201 html("</table>\n");
194 header = 0; 202 header = 0;
195} 203}
196 204
197static void ls_tree(const unsigned char *sha1, char *path) 205static void ls_tree(const unsigned char *sha1, char *path)
198{ 206{
199 struct tree *tree; 207 struct tree *tree;
200 208
201 tree = parse_tree_indirect(sha1); 209 tree = parse_tree_indirect(sha1);
202 if (!tree) { 210 if (!tree) {
203 cgit_print_error(fmt("Not a tree object: %s", 211 cgit_print_error(fmt("Not a tree object: %s",
204 sha1_to_hex(sha1))); 212 sha1_to_hex(sha1)));
205 return; 213 return;
206 } 214 }
207 215
208 ls_head(); 216 ls_head();
209 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
210 ls_tail(); 218 ls_tail();
211} 219}
212 220
213 221
214static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 222static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
215 const char *pathname, unsigned mode, int stage, 223 const char *pathname, unsigned mode, int stage,
216 void *cbdata) 224 void *cbdata)
217{ 225{
218 static int state; 226 static int state;
219 static char buffer[PATH_MAX]; 227 static char buffer[PATH_MAX];
220 char *url;
221 228
222 if (state == 0) { 229 if (state == 0) {
223 memcpy(buffer, base, baselen); 230 memcpy(buffer, base, baselen);
224 strcpy(buffer+baselen, pathname); 231 strcpy(buffer+baselen, pathname);
225 url = cgit_pageurl(ctx.qry.repo, "tree",
226 fmt("h=%s&amp;path=%s", curr_rev, buffer));
227 html("/");
228 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
229 curr_rev, buffer);
230
231 if (strcmp(match_path, buffer)) 232 if (strcmp(match_path, buffer))
232 return READ_TREE_RECURSIVE; 233 return READ_TREE_RECURSIVE;
233 234
234 if (S_ISDIR(mode)) { 235 if (S_ISDIR(mode)) {
235 state = 1; 236 state = 1;
236 ls_head(); 237 ls_head();
237 return READ_TREE_RECURSIVE; 238 return READ_TREE_RECURSIVE;
238 } else { 239 } else {
239 print_object(sha1, buffer, pathname); 240 print_object(sha1, buffer, pathname);
240 return 0; 241 return 0;
241 } 242 }
242 } 243 }
243 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 244 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
244 return 0; 245 return 0;
245} 246}
246 247
247 248
248/* 249/*
249 * Show a tree or a blob 250 * Show a tree or a blob
250 * rev: the commit pointing at the root tree object 251 * rev: the commit pointing at the root tree object
251 * path: path to tree or blob 252 * path: path to tree or blob
252 */ 253 */
253void cgit_print_tree(const char *rev, char *path) 254void cgit_print_tree(const char *rev, char *path)
254{ 255{
255 unsigned char sha1[20]; 256 unsigned char sha1[20];
256 struct commit *commit; 257 struct commit *commit;
257 const char *paths[] = {path, NULL}; 258 const char *paths[] = {path, NULL};
258 259
259 if (!rev) 260 if (!rev)
260 rev = ctx.qry.head; 261 rev = ctx.qry.head;
261 262
262 curr_rev = xstrdup(rev); 263 curr_rev = xstrdup(rev);
263 if (get_sha1(rev, sha1)) { 264 if (get_sha1(rev, sha1)) {
264 cgit_print_error(fmt("Invalid revision name: %s", rev)); 265 cgit_print_error(fmt("Invalid revision name: %s", rev));
265 return; 266 return;
266 } 267 }
267 commit = lookup_commit_reference(sha1); 268 commit = lookup_commit_reference(sha1);
268 if (!commit || parse_commit(commit)) { 269 if (!commit || parse_commit(commit)) {
269 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 270 cgit_print_error(fmt("Invalid commit reference: %s", rev));
270 return; 271 return;
271 } 272 }
272 273
273 html("path: <a href='");
274 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
275 html("'>root</a>");
276
277 if (path == NULL) { 274 if (path == NULL) {
278 ls_tree(commit->tree->object.sha1, NULL); 275 ls_tree(commit->tree->object.sha1, NULL);
279 return; 276 return;
280 } 277 }
281 278
282 match_path = path; 279 match_path = path;
283 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 280 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
284 ls_tail(); 281 ls_tail();
285} 282}