summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile1
-rw-r--r--cgit.c6
-rw-r--r--cgit.css77
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt8
-rw-r--r--cmd.c10
-rw-r--r--shared.c1
-rw-r--r--ui-shared.c3
-rw-r--r--ui-stats.c380
-rw-r--r--ui-stats.h8
10 files changed, 497 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 561af76..f426f98 100644
--- a/Makefile
+++ b/Makefile
@@ -1,129 +1,130 @@
1CGIT_VERSION = v0.8.1 1CGIT_VERSION = v0.8.1
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_CONFIG = /etc/cgitrc 4CGIT_CONFIG = /etc/cgitrc
5CACHE_ROOT = /var/cache/cgit 5CACHE_ROOT = /var/cache/cgit
6SHA1_HEADER = <openssl/sha.h> 6SHA1_HEADER = <openssl/sha.h>
7GIT_VER = 1.6.0.2 7GIT_VER = 1.6.0.2
8GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 8GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
9 9
10# 10#
11# Let the user override the above settings. 11# Let the user override the above settings.
12# 12#
13-include cgit.conf 13-include cgit.conf
14 14
15# 15#
16# Define a way to invoke make in subdirs quietly, shamelessly ripped 16# Define a way to invoke make in subdirs quietly, shamelessly ripped
17# from git.git 17# from git.git
18# 18#
19QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 19QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
20QUIET_SUBDIR1 = 20QUIET_SUBDIR1 =
21 21
22ifneq ($(findstring $(MAKEFLAGS),w),w) 22ifneq ($(findstring $(MAKEFLAGS),w),w)
23PRINT_DIR = --no-print-directory 23PRINT_DIR = --no-print-directory
24else # "make -w" 24else # "make -w"
25NO_SUBDIR = : 25NO_SUBDIR = :
26endif 26endif
27 27
28ifndef V 28ifndef V
29 QUIET_CC = @echo ' ' CC $@; 29 QUIET_CC = @echo ' ' CC $@;
30 QUIET_MM = @echo ' ' MM $@; 30 QUIET_MM = @echo ' ' MM $@;
31 QUIET_SUBDIR0 = +@subdir= 31 QUIET_SUBDIR0 = +@subdir=
32 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 32 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
33 $(MAKE) $(PRINT_DIR) -C $$subdir 33 $(MAKE) $(PRINT_DIR) -C $$subdir
34endif 34endif
35 35
36# 36#
37# Define a pattern rule for automatic dependency building 37# Define a pattern rule for automatic dependency building
38# 38#
39%.d: %.c 39%.d: %.c
40 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 40 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
41 41
42# 42#
43# Define a pattern rule for silent object building 43# Define a pattern rule for silent object building
44# 44#
45%.o: %.c 45%.o: %.c
46 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 46 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
47 47
48 48
49EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 49EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto
50OBJECTS = 50OBJECTS =
51OBJECTS += cache.o 51OBJECTS += cache.o
52OBJECTS += cgit.o 52OBJECTS += cgit.o
53OBJECTS += cmd.o 53OBJECTS += cmd.o
54OBJECTS += configfile.o 54OBJECTS += configfile.o
55OBJECTS += html.o 55OBJECTS += html.o
56OBJECTS += parsing.o 56OBJECTS += parsing.o
57OBJECTS += scan-tree.o 57OBJECTS += scan-tree.o
58OBJECTS += shared.o 58OBJECTS += shared.o
59OBJECTS += ui-atom.o 59OBJECTS += ui-atom.o
60OBJECTS += ui-blob.o 60OBJECTS += ui-blob.o
61OBJECTS += ui-clone.o 61OBJECTS += ui-clone.o
62OBJECTS += ui-commit.o 62OBJECTS += ui-commit.o
63OBJECTS += ui-diff.o 63OBJECTS += ui-diff.o
64OBJECTS += ui-log.o 64OBJECTS += ui-log.o
65OBJECTS += ui-patch.o 65OBJECTS += ui-patch.o
66OBJECTS += ui-plain.o 66OBJECTS += ui-plain.o
67OBJECTS += ui-refs.o 67OBJECTS += ui-refs.o
68OBJECTS += ui-repolist.o 68OBJECTS += ui-repolist.o
69OBJECTS += ui-shared.o 69OBJECTS += ui-shared.o
70OBJECTS += ui-snapshot.o 70OBJECTS += ui-snapshot.o
71OBJECTS += ui-stats.o
71OBJECTS += ui-summary.o 72OBJECTS += ui-summary.o
72OBJECTS += ui-tag.o 73OBJECTS += ui-tag.o
73OBJECTS += ui-tree.o 74OBJECTS += ui-tree.o
74 75
75ifdef NEEDS_LIBICONV 76ifdef NEEDS_LIBICONV
76 EXTLIBS += -liconv 77 EXTLIBS += -liconv
77endif 78endif
78 79
79 80
80.PHONY: all libgit test install uninstall clean force-version get-git 81.PHONY: all libgit test install uninstall clean force-version get-git
81 82
82all: cgit 83all: cgit
83 84
84VERSION: force-version 85VERSION: force-version
85 @./gen-version.sh "$(CGIT_VERSION)" 86 @./gen-version.sh "$(CGIT_VERSION)"
86-include VERSION 87-include VERSION
87 88
88 89
89CFLAGS += -g -Wall -Igit 90CFLAGS += -g -Wall -Igit
90CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 91CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
91CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 92CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
92CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 93CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
93CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 94CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
94CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 95CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
95 96
96ifdef NO_ICONV 97ifdef NO_ICONV
97 CFLAGS += -DNO_ICONV 98 CFLAGS += -DNO_ICONV
98endif 99endif
99 100
100cgit: $(OBJECTS) libgit 101cgit: $(OBJECTS) libgit
101 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 102 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
102 103
103cgit.o: VERSION 104cgit.o: VERSION
104 105
105-include $(OBJECTS:.o=.d) 106-include $(OBJECTS:.o=.d)
106 107
107libgit: 108libgit:
108 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a 109 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a
109 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a 110 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a
110 111
111test: all 112test: all
112 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 113 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
113 114
114install: all 115install: all
115 mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) 116 mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH)
116 install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 117 install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
117 install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css 118 install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css
118 install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png 119 install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png
119 120
120uninstall: 121uninstall:
121 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 122 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
122 rm -f $(CGIT_SCRIPT_PATH)/cgit.css 123 rm -f $(CGIT_SCRIPT_PATH)/cgit.css
123 rm -f $(CGIT_SCRIPT_PATH)/cgit.png 124 rm -f $(CGIT_SCRIPT_PATH)/cgit.png
124 125
125clean: 126clean:
126 rm -f cgit VERSION *.o *.d 127 rm -f cgit VERSION *.o *.d
127 128
128get-git: 129get-git:
129 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 130 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit.c b/cgit.c
index c82587b..22b6d7c 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,252 +1,258 @@
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 * 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 "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "scan-tree.h" 15#include "scan-tree.h"
16 16
17const char *cgit_version = CGIT_VERSION; 17const char *cgit_version = CGIT_VERSION;
18 18
19void config_cb(const char *name, const char *value) 19void config_cb(const char *name, const char *value)
20{ 20{
21 if (!strcmp(name, "root-title")) 21 if (!strcmp(name, "root-title"))
22 ctx.cfg.root_title = xstrdup(value); 22 ctx.cfg.root_title = xstrdup(value);
23 else if (!strcmp(name, "root-desc")) 23 else if (!strcmp(name, "root-desc"))
24 ctx.cfg.root_desc = xstrdup(value); 24 ctx.cfg.root_desc = xstrdup(value);
25 else if (!strcmp(name, "root-readme")) 25 else if (!strcmp(name, "root-readme"))
26 ctx.cfg.root_readme = xstrdup(value); 26 ctx.cfg.root_readme = xstrdup(value);
27 else if (!strcmp(name, "css")) 27 else if (!strcmp(name, "css"))
28 ctx.cfg.css = xstrdup(value); 28 ctx.cfg.css = xstrdup(value);
29 else if (!strcmp(name, "favicon")) 29 else if (!strcmp(name, "favicon"))
30 ctx.cfg.favicon = xstrdup(value); 30 ctx.cfg.favicon = xstrdup(value);
31 else if (!strcmp(name, "footer")) 31 else if (!strcmp(name, "footer"))
32 ctx.cfg.footer = xstrdup(value); 32 ctx.cfg.footer = xstrdup(value);
33 else if (!strcmp(name, "logo")) 33 else if (!strcmp(name, "logo"))
34 ctx.cfg.logo = xstrdup(value); 34 ctx.cfg.logo = xstrdup(value);
35 else if (!strcmp(name, "index-header")) 35 else if (!strcmp(name, "index-header"))
36 ctx.cfg.index_header = xstrdup(value); 36 ctx.cfg.index_header = xstrdup(value);
37 else if (!strcmp(name, "index-info")) 37 else if (!strcmp(name, "index-info"))
38 ctx.cfg.index_info = xstrdup(value); 38 ctx.cfg.index_info = xstrdup(value);
39 else if (!strcmp(name, "logo-link")) 39 else if (!strcmp(name, "logo-link"))
40 ctx.cfg.logo_link = xstrdup(value); 40 ctx.cfg.logo_link = xstrdup(value);
41 else if (!strcmp(name, "module-link")) 41 else if (!strcmp(name, "module-link"))
42 ctx.cfg.module_link = xstrdup(value); 42 ctx.cfg.module_link = xstrdup(value);
43 else if (!strcmp(name, "virtual-root")) { 43 else if (!strcmp(name, "virtual-root")) {
44 ctx.cfg.virtual_root = trim_end(value, '/'); 44 ctx.cfg.virtual_root = trim_end(value, '/');
45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
46 ctx.cfg.virtual_root = ""; 46 ctx.cfg.virtual_root = "";
47 } else if (!strcmp(name, "nocache")) 47 } else if (!strcmp(name, "nocache"))
48 ctx.cfg.nocache = atoi(value); 48 ctx.cfg.nocache = atoi(value);
49 else if (!strcmp(name, "snapshots")) 49 else if (!strcmp(name, "snapshots"))
50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
51 else if (!strcmp(name, "enable-index-links")) 51 else if (!strcmp(name, "enable-index-links"))
52 ctx.cfg.enable_index_links = atoi(value); 52 ctx.cfg.enable_index_links = atoi(value);
53 else if (!strcmp(name, "enable-log-filecount")) 53 else if (!strcmp(name, "enable-log-filecount"))
54 ctx.cfg.enable_log_filecount = atoi(value); 54 ctx.cfg.enable_log_filecount = atoi(value);
55 else if (!strcmp(name, "enable-log-linecount")) 55 else if (!strcmp(name, "enable-log-linecount"))
56 ctx.cfg.enable_log_linecount = atoi(value); 56 ctx.cfg.enable_log_linecount = atoi(value);
57 else if (!strcmp(name, "enable-stats"))
58 ctx.cfg.enable_stats = atoi(value);
57 else if (!strcmp(name, "cache-size")) 59 else if (!strcmp(name, "cache-size"))
58 ctx.cfg.cache_size = atoi(value); 60 ctx.cfg.cache_size = atoi(value);
59 else if (!strcmp(name, "cache-root")) 61 else if (!strcmp(name, "cache-root"))
60 ctx.cfg.cache_root = xstrdup(value); 62 ctx.cfg.cache_root = xstrdup(value);
61 else if (!strcmp(name, "cache-root-ttl")) 63 else if (!strcmp(name, "cache-root-ttl"))
62 ctx.cfg.cache_root_ttl = atoi(value); 64 ctx.cfg.cache_root_ttl = atoi(value);
63 else if (!strcmp(name, "cache-repo-ttl")) 65 else if (!strcmp(name, "cache-repo-ttl"))
64 ctx.cfg.cache_repo_ttl = atoi(value); 66 ctx.cfg.cache_repo_ttl = atoi(value);
65 else if (!strcmp(name, "cache-static-ttl")) 67 else if (!strcmp(name, "cache-static-ttl"))
66 ctx.cfg.cache_static_ttl = atoi(value); 68 ctx.cfg.cache_static_ttl = atoi(value);
67 else if (!strcmp(name, "cache-dynamic-ttl")) 69 else if (!strcmp(name, "cache-dynamic-ttl"))
68 ctx.cfg.cache_dynamic_ttl = atoi(value); 70 ctx.cfg.cache_dynamic_ttl = atoi(value);
69 else if (!strcmp(name, "max-message-length")) 71 else if (!strcmp(name, "max-message-length"))
70 ctx.cfg.max_msg_len = atoi(value); 72 ctx.cfg.max_msg_len = atoi(value);
71 else if (!strcmp(name, "max-repodesc-length")) 73 else if (!strcmp(name, "max-repodesc-length"))
72 ctx.cfg.max_repodesc_len = atoi(value); 74 ctx.cfg.max_repodesc_len = atoi(value);
73 else if (!strcmp(name, "max-repo-count")) 75 else if (!strcmp(name, "max-repo-count"))
74 ctx.cfg.max_repo_count = atoi(value); 76 ctx.cfg.max_repo_count = atoi(value);
75 else if (!strcmp(name, "max-commit-count")) 77 else if (!strcmp(name, "max-commit-count"))
76 ctx.cfg.max_commit_count = atoi(value); 78 ctx.cfg.max_commit_count = atoi(value);
77 else if (!strcmp(name, "summary-log")) 79 else if (!strcmp(name, "summary-log"))
78 ctx.cfg.summary_log = atoi(value); 80 ctx.cfg.summary_log = atoi(value);
79 else if (!strcmp(name, "summary-branches")) 81 else if (!strcmp(name, "summary-branches"))
80 ctx.cfg.summary_branches = atoi(value); 82 ctx.cfg.summary_branches = atoi(value);
81 else if (!strcmp(name, "summary-tags")) 83 else if (!strcmp(name, "summary-tags"))
82 ctx.cfg.summary_tags = atoi(value); 84 ctx.cfg.summary_tags = atoi(value);
83 else if (!strcmp(name, "agefile")) 85 else if (!strcmp(name, "agefile"))
84 ctx.cfg.agefile = xstrdup(value); 86 ctx.cfg.agefile = xstrdup(value);
85 else if (!strcmp(name, "renamelimit")) 87 else if (!strcmp(name, "renamelimit"))
86 ctx.cfg.renamelimit = atoi(value); 88 ctx.cfg.renamelimit = atoi(value);
87 else if (!strcmp(name, "robots")) 89 else if (!strcmp(name, "robots"))
88 ctx.cfg.robots = xstrdup(value); 90 ctx.cfg.robots = xstrdup(value);
89 else if (!strcmp(name, "clone-prefix")) 91 else if (!strcmp(name, "clone-prefix"))
90 ctx.cfg.clone_prefix = xstrdup(value); 92 ctx.cfg.clone_prefix = xstrdup(value);
91 else if (!strcmp(name, "local-time")) 93 else if (!strcmp(name, "local-time"))
92 ctx.cfg.local_time = atoi(value); 94 ctx.cfg.local_time = atoi(value);
93 else if (!strcmp(name, "repo.group")) 95 else if (!strcmp(name, "repo.group"))
94 ctx.cfg.repo_group = xstrdup(value); 96 ctx.cfg.repo_group = xstrdup(value);
95 else if (!strcmp(name, "repo.url")) 97 else if (!strcmp(name, "repo.url"))
96 ctx.repo = cgit_add_repo(value); 98 ctx.repo = cgit_add_repo(value);
97 else if (!strcmp(name, "repo.name")) 99 else if (!strcmp(name, "repo.name"))
98 ctx.repo->name = xstrdup(value); 100 ctx.repo->name = xstrdup(value);
99 else if (ctx.repo && !strcmp(name, "repo.path")) 101 else if (ctx.repo && !strcmp(name, "repo.path"))
100 ctx.repo->path = trim_end(value, '/'); 102 ctx.repo->path = trim_end(value, '/');
101 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 103 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
102 ctx.repo->clone_url = xstrdup(value); 104 ctx.repo->clone_url = xstrdup(value);
103 else if (ctx.repo && !strcmp(name, "repo.desc")) 105 else if (ctx.repo && !strcmp(name, "repo.desc"))
104 ctx.repo->desc = xstrdup(value); 106 ctx.repo->desc = xstrdup(value);
105 else if (ctx.repo && !strcmp(name, "repo.owner")) 107 else if (ctx.repo && !strcmp(name, "repo.owner"))
106 ctx.repo->owner = xstrdup(value); 108 ctx.repo->owner = xstrdup(value);
107 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 109 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
108 ctx.repo->defbranch = xstrdup(value); 110 ctx.repo->defbranch = xstrdup(value);
109 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 111 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 112 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 113 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 114 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 115 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 116 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
117 else if (ctx.repo && !strcmp(name, "repo.enable-stats"))
118 ctx.repo->enable_stats = ctx.cfg.enable_stats && atoi(value);
115 else if (ctx.repo && !strcmp(name, "repo.module-link")) 119 else if (ctx.repo && !strcmp(name, "repo.module-link"))
116 ctx.repo->module_link= xstrdup(value); 120 ctx.repo->module_link= xstrdup(value);
117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 121 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
118 if (*value == '/') 122 if (*value == '/')
119 ctx.repo->readme = xstrdup(value); 123 ctx.repo->readme = xstrdup(value);
120 else 124 else
121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 125 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
122 } else if (!strcmp(name, "include")) 126 } else if (!strcmp(name, "include"))
123 parse_configfile(value, config_cb); 127 parse_configfile(value, config_cb);
124} 128}
125 129
126static void querystring_cb(const char *name, const char *value) 130static void querystring_cb(const char *name, const char *value)
127{ 131{
128 if (!strcmp(name,"r")) { 132 if (!strcmp(name,"r")) {
129 ctx.qry.repo = xstrdup(value); 133 ctx.qry.repo = xstrdup(value);
130 ctx.repo = cgit_get_repoinfo(value); 134 ctx.repo = cgit_get_repoinfo(value);
131 } else if (!strcmp(name, "p")) { 135 } else if (!strcmp(name, "p")) {
132 ctx.qry.page = xstrdup(value); 136 ctx.qry.page = xstrdup(value);
133 } else if (!strcmp(name, "url")) { 137 } else if (!strcmp(name, "url")) {
134 ctx.qry.url = xstrdup(value); 138 ctx.qry.url = xstrdup(value);
135 cgit_parse_url(value); 139 cgit_parse_url(value);
136 } else if (!strcmp(name, "qt")) { 140 } else if (!strcmp(name, "qt")) {
137 ctx.qry.grep = xstrdup(value); 141 ctx.qry.grep = xstrdup(value);
138 } else if (!strcmp(name, "q")) { 142 } else if (!strcmp(name, "q")) {
139 ctx.qry.search = xstrdup(value); 143 ctx.qry.search = xstrdup(value);
140 } else if (!strcmp(name, "h")) { 144 } else if (!strcmp(name, "h")) {
141 ctx.qry.head = xstrdup(value); 145 ctx.qry.head = xstrdup(value);
142 ctx.qry.has_symref = 1; 146 ctx.qry.has_symref = 1;
143 } else if (!strcmp(name, "id")) { 147 } else if (!strcmp(name, "id")) {
144 ctx.qry.sha1 = xstrdup(value); 148 ctx.qry.sha1 = xstrdup(value);
145 ctx.qry.has_sha1 = 1; 149 ctx.qry.has_sha1 = 1;
146 } else if (!strcmp(name, "id2")) { 150 } else if (!strcmp(name, "id2")) {
147 ctx.qry.sha2 = xstrdup(value); 151 ctx.qry.sha2 = xstrdup(value);
148 ctx.qry.has_sha1 = 1; 152 ctx.qry.has_sha1 = 1;
149 } else if (!strcmp(name, "ofs")) { 153 } else if (!strcmp(name, "ofs")) {
150 ctx.qry.ofs = atoi(value); 154 ctx.qry.ofs = atoi(value);
151 } else if (!strcmp(name, "path")) { 155 } else if (!strcmp(name, "path")) {
152 ctx.qry.path = trim_end(value, '/'); 156 ctx.qry.path = trim_end(value, '/');
153 } else if (!strcmp(name, "name")) { 157 } else if (!strcmp(name, "name")) {
154 ctx.qry.name = xstrdup(value); 158 ctx.qry.name = xstrdup(value);
155 } else if (!strcmp(name, "mimetype")) { 159 } else if (!strcmp(name, "mimetype")) {
156 ctx.qry.mimetype = xstrdup(value); 160 ctx.qry.mimetype = xstrdup(value);
161 } else if (!strcmp(name, "period")) {
162 ctx.qry.period = xstrdup(value);
157 } 163 }
158} 164}
159 165
160static void prepare_context(struct cgit_context *ctx) 166static void prepare_context(struct cgit_context *ctx)
161{ 167{
162 memset(ctx, 0, sizeof(ctx)); 168 memset(ctx, 0, sizeof(ctx));
163 ctx->cfg.agefile = "info/web/last-modified"; 169 ctx->cfg.agefile = "info/web/last-modified";
164 ctx->cfg.nocache = 0; 170 ctx->cfg.nocache = 0;
165 ctx->cfg.cache_size = 0; 171 ctx->cfg.cache_size = 0;
166 ctx->cfg.cache_dynamic_ttl = 5; 172 ctx->cfg.cache_dynamic_ttl = 5;
167 ctx->cfg.cache_max_create_time = 5; 173 ctx->cfg.cache_max_create_time = 5;
168 ctx->cfg.cache_repo_ttl = 5; 174 ctx->cfg.cache_repo_ttl = 5;
169 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 175 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
170 ctx->cfg.cache_root_ttl = 5; 176 ctx->cfg.cache_root_ttl = 5;
171 ctx->cfg.cache_static_ttl = -1; 177 ctx->cfg.cache_static_ttl = -1;
172 ctx->cfg.css = "/cgit.css"; 178 ctx->cfg.css = "/cgit.css";
173 ctx->cfg.logo = "/git-logo.png"; 179 ctx->cfg.logo = "/git-logo.png";
174 ctx->cfg.local_time = 0; 180 ctx->cfg.local_time = 0;
175 ctx->cfg.max_repo_count = 50; 181 ctx->cfg.max_repo_count = 50;
176 ctx->cfg.max_commit_count = 50; 182 ctx->cfg.max_commit_count = 50;
177 ctx->cfg.max_lock_attempts = 5; 183 ctx->cfg.max_lock_attempts = 5;
178 ctx->cfg.max_msg_len = 80; 184 ctx->cfg.max_msg_len = 80;
179 ctx->cfg.max_repodesc_len = 80; 185 ctx->cfg.max_repodesc_len = 80;
180 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 186 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
181 ctx->cfg.renamelimit = -1; 187 ctx->cfg.renamelimit = -1;
182 ctx->cfg.robots = "index, nofollow"; 188 ctx->cfg.robots = "index, nofollow";
183 ctx->cfg.root_title = "Git repository browser"; 189 ctx->cfg.root_title = "Git repository browser";
184 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 190 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
185 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 191 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
186 ctx->cfg.summary_branches = 10; 192 ctx->cfg.summary_branches = 10;
187 ctx->cfg.summary_log = 10; 193 ctx->cfg.summary_log = 10;
188 ctx->cfg.summary_tags = 10; 194 ctx->cfg.summary_tags = 10;
189 ctx->page.mimetype = "text/html"; 195 ctx->page.mimetype = "text/html";
190 ctx->page.charset = PAGE_ENCODING; 196 ctx->page.charset = PAGE_ENCODING;
191 ctx->page.filename = NULL; 197 ctx->page.filename = NULL;
192 ctx->page.size = 0; 198 ctx->page.size = 0;
193 ctx->page.modified = time(NULL); 199 ctx->page.modified = time(NULL);
194 ctx->page.expires = ctx->page.modified; 200 ctx->page.expires = ctx->page.modified;
195} 201}
196 202
197struct refmatch { 203struct refmatch {
198 char *req_ref; 204 char *req_ref;
199 char *first_ref; 205 char *first_ref;
200 int match; 206 int match;
201}; 207};
202 208
203int find_current_ref(const char *refname, const unsigned char *sha1, 209int find_current_ref(const char *refname, const unsigned char *sha1,
204 int flags, void *cb_data) 210 int flags, void *cb_data)
205{ 211{
206 struct refmatch *info; 212 struct refmatch *info;
207 213
208 info = (struct refmatch *)cb_data; 214 info = (struct refmatch *)cb_data;
209 if (!strcmp(refname, info->req_ref)) 215 if (!strcmp(refname, info->req_ref))
210 info->match = 1; 216 info->match = 1;
211 if (!info->first_ref) 217 if (!info->first_ref)
212 info->first_ref = xstrdup(refname); 218 info->first_ref = xstrdup(refname);
213 return info->match; 219 return info->match;
214} 220}
215 221
216char *find_default_branch(struct cgit_repo *repo) 222char *find_default_branch(struct cgit_repo *repo)
217{ 223{
218 struct refmatch info; 224 struct refmatch info;
219 char *ref; 225 char *ref;
220 226
221 info.req_ref = repo->defbranch; 227 info.req_ref = repo->defbranch;
222 info.first_ref = NULL; 228 info.first_ref = NULL;
223 info.match = 0; 229 info.match = 0;
224 for_each_branch_ref(find_current_ref, &info); 230 for_each_branch_ref(find_current_ref, &info);
225 if (info.match) 231 if (info.match)
226 ref = info.req_ref; 232 ref = info.req_ref;
227 else 233 else
228 ref = info.first_ref; 234 ref = info.first_ref;
229 if (ref) 235 if (ref)
230 ref = xstrdup(ref); 236 ref = xstrdup(ref);
231 return ref; 237 return ref;
232} 238}
233 239
234static int prepare_repo_cmd(struct cgit_context *ctx) 240static int prepare_repo_cmd(struct cgit_context *ctx)
235{ 241{
236 char *tmp; 242 char *tmp;
237 unsigned char sha1[20]; 243 unsigned char sha1[20];
238 int nongit = 0; 244 int nongit = 0;
239 245
240 setenv("GIT_DIR", ctx->repo->path, 1); 246 setenv("GIT_DIR", ctx->repo->path, 1);
241 setup_git_directory_gently(&nongit); 247 setup_git_directory_gently(&nongit);
242 if (nongit) { 248 if (nongit) {
243 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 249 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
244 "config error"); 250 "config error");
245 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 251 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
246 ctx->repo = NULL; 252 ctx->repo = NULL;
247 cgit_print_http_headers(ctx); 253 cgit_print_http_headers(ctx);
248 cgit_print_docstart(ctx); 254 cgit_print_docstart(ctx);
249 cgit_print_pageheader(ctx); 255 cgit_print_pageheader(ctx);
250 cgit_print_error(tmp); 256 cgit_print_error(tmp);
251 cgit_print_docend(); 257 cgit_print_docend();
252 return 1; 258 return 1;
diff --git a/cgit.css b/cgit.css
index a37d218..ef30fbf 100644
--- a/cgit.css
+++ b/cgit.css
@@ -363,96 +363,173 @@ table.diff td div.head {
363 font-weight: bold; 363 font-weight: bold;
364 margin-top: 1em; 364 margin-top: 1em;
365 color: black; 365 color: black;
366} 366}
367 367
368table.diff td div.hunk { 368table.diff td div.hunk {
369 color: #009; 369 color: #009;
370} 370}
371 371
372table.diff td div.add { 372table.diff td div.add {
373 color: green; 373 color: green;
374} 374}
375 375
376table.diff td div.del { 376table.diff td div.del {
377 color: red; 377 color: red;
378} 378}
379 379
380.sha1 { 380.sha1 {
381 font-family: monospace; 381 font-family: monospace;
382 font-size: 90%; 382 font-size: 90%;
383} 383}
384 384
385.left { 385.left {
386 text-align: left; 386 text-align: left;
387} 387}
388 388
389.right { 389.right {
390 text-align: right; 390 text-align: right;
391} 391}
392 392
393table.list td.repogroup { 393table.list td.repogroup {
394 font-style: italic; 394 font-style: italic;
395 color: #888; 395 color: #888;
396} 396}
397 397
398a.button { 398a.button {
399 font-size: 80%; 399 font-size: 80%;
400 padding: 0em 0.5em; 400 padding: 0em 0.5em;
401} 401}
402 402
403a.primary { 403a.primary {
404 font-size: 100%; 404 font-size: 100%;
405} 405}
406 406
407a.secondary { 407a.secondary {
408 font-size: 90%; 408 font-size: 90%;
409} 409}
410 410
411td.toplevel-repo { 411td.toplevel-repo {
412 412
413} 413}
414 414
415table.list td.sublevel-repo { 415table.list td.sublevel-repo {
416 padding-left: 1.5em; 416 padding-left: 1.5em;
417} 417}
418 418
419div.pager { 419div.pager {
420 text-align: center; 420 text-align: center;
421 margin: 1em 0em 0em 0em; 421 margin: 1em 0em 0em 0em;
422} 422}
423 423
424div.pager a { 424div.pager a {
425 color: #777; 425 color: #777;
426 margin: 0em 0.5em; 426 margin: 0em 0.5em;
427} 427}
428 428
429span.age-mins { 429span.age-mins {
430 font-weight: bold; 430 font-weight: bold;
431 color: #080; 431 color: #080;
432} 432}
433 433
434span.age-hours { 434span.age-hours {
435 color: #080; 435 color: #080;
436} 436}
437 437
438span.age-days { 438span.age-days {
439 color: #040; 439 color: #040;
440} 440}
441 441
442span.age-weeks { 442span.age-weeks {
443 color: #444; 443 color: #444;
444} 444}
445 445
446span.age-months { 446span.age-months {
447 color: #888; 447 color: #888;
448} 448}
449 449
450span.age-years { 450span.age-years {
451 color: #bbb; 451 color: #bbb;
452} 452}
453div.footer { 453div.footer {
454 margin-top: 0.5em; 454 margin-top: 0.5em;
455 text-align: center; 455 text-align: center;
456 font-size: 80%; 456 font-size: 80%;
457 color: #ccc; 457 color: #ccc;
458} 458}
459table.stats {
460 border: solid 1px black;
461 border-collapse: collapse;
462}
463
464table.stats th {
465 text-align: left;
466 padding: 1px 0.5em;
467 background-color: #eee;
468 border: solid 1px black;
469}
470
471table.stats td {
472 text-align: right;
473 padding: 1px 0.5em;
474 border: solid 1px black;
475}
476
477table.stats td.total {
478 font-weight: bold;
479 text-align: left;
480}
481
482table.stats td.sum {
483 color: #c00;
484 font-weight: bold;
485 /*background-color: #eee; */
486}
487
488table.stats td.left {
489 text-align: left;
490}
491
492table.vgraph {
493 border-collapse: separate;
494 border: solid 1px black;
495 height: 200px;
496}
497
498table.vgraph th {
499 background-color: #eee;
500 font-weight: bold;
501 border: solid 1px white;
502 padding: 1px 0.5em;
503}
504
505table.vgraph td {
506 vertical-align: bottom;
507 padding: 0px 10px;
508}
509
510table.vgraph div.bar {
511 background-color: #eee;
512}
513
514table.hgraph {
515 border: solid 1px black;
516 width: 800px;
517}
518
519table.hgraph th {
520 background-color: #eee;
521 font-weight: bold;
522 border: solid 1px black;
523 padding: 1px 0.5em;
524}
525
526table.hgraph td {
527 vertical-align: center;
528 padding: 2px 2px;
529}
530
531table.hgraph div.bar {
532 background-color: #eee;
533 height: 1em;
534}
535
diff --git a/cgit.h b/cgit.h
index 91db98a..85045c4 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,243 +1,246 @@
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 <xdiff/xdiff.h> 18#include <xdiff/xdiff.h>
19#include <utf8.h> 19#include <utf8.h>
20 20
21 21
22/* 22/*
23 * Dateformats used on misc. pages 23 * Dateformats used on misc. pages
24 */ 24 */
25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
26#define FMT_SHORTDATE "%Y-%m-%d" 26#define FMT_SHORTDATE "%Y-%m-%d"
27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
28 28
29 29
30/* 30/*
31 * Limits used for relative dates 31 * Limits used for relative dates
32 */ 32 */
33#define TM_MIN 60 33#define TM_MIN 60
34#define TM_HOUR (TM_MIN * 60) 34#define TM_HOUR (TM_MIN * 60)
35#define TM_DAY (TM_HOUR * 24) 35#define TM_DAY (TM_HOUR * 24)
36#define TM_WEEK (TM_DAY * 7) 36#define TM_WEEK (TM_DAY * 7)
37#define TM_YEAR (TM_DAY * 365) 37#define TM_YEAR (TM_DAY * 365)
38#define TM_MONTH (TM_YEAR / 12.0) 38#define TM_MONTH (TM_YEAR / 12.0)
39 39
40 40
41/* 41/*
42 * Default encoding 42 * Default encoding
43 */ 43 */
44#define PAGE_ENCODING "UTF-8" 44#define PAGE_ENCODING "UTF-8"
45 45
46typedef void (*configfn)(const char *name, const char *value); 46typedef void (*configfn)(const char *name, const char *value);
47typedef void (*filepair_fn)(struct diff_filepair *pair); 47typedef void (*filepair_fn)(struct diff_filepair *pair);
48typedef void (*linediff_fn)(char *line, int len); 48typedef void (*linediff_fn)(char *line, int len);
49 49
50struct cgit_repo { 50struct cgit_repo {
51 char *url; 51 char *url;
52 char *name; 52 char *name;
53 char *path; 53 char *path;
54 char *desc; 54 char *desc;
55 char *owner; 55 char *owner;
56 char *defbranch; 56 char *defbranch;
57 char *group; 57 char *group;
58 char *module_link; 58 char *module_link;
59 char *readme; 59 char *readme;
60 char *clone_url; 60 char *clone_url;
61 int snapshots; 61 int snapshots;
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64 int enable_stats;
64}; 65};
65 66
66struct cgit_repolist { 67struct cgit_repolist {
67 int length; 68 int length;
68 int count; 69 int count;
69 struct cgit_repo *repos; 70 struct cgit_repo *repos;
70}; 71};
71 72
72struct commitinfo { 73struct commitinfo {
73 struct commit *commit; 74 struct commit *commit;
74 char *author; 75 char *author;
75 char *author_email; 76 char *author_email;
76 unsigned long author_date; 77 unsigned long author_date;
77 char *committer; 78 char *committer;
78 char *committer_email; 79 char *committer_email;
79 unsigned long committer_date; 80 unsigned long committer_date;
80 char *subject; 81 char *subject;
81 char *msg; 82 char *msg;
82 char *msg_encoding; 83 char *msg_encoding;
83}; 84};
84 85
85struct taginfo { 86struct taginfo {
86 char *tagger; 87 char *tagger;
87 char *tagger_email; 88 char *tagger_email;
88 unsigned long tagger_date; 89 unsigned long tagger_date;
89 char *msg; 90 char *msg;
90}; 91};
91 92
92struct refinfo { 93struct refinfo {
93 const char *refname; 94 const char *refname;
94 struct object *object; 95 struct object *object;
95 union { 96 union {
96 struct taginfo *tag; 97 struct taginfo *tag;
97 struct commitinfo *commit; 98 struct commitinfo *commit;
98 }; 99 };
99}; 100};
100 101
101struct reflist { 102struct reflist {
102 struct refinfo **refs; 103 struct refinfo **refs;
103 int alloc; 104 int alloc;
104 int count; 105 int count;
105}; 106};
106 107
107struct cgit_query { 108struct cgit_query {
108 int has_symref; 109 int has_symref;
109 int has_sha1; 110 int has_sha1;
110 char *raw; 111 char *raw;
111 char *repo; 112 char *repo;
112 char *page; 113 char *page;
113 char *search; 114 char *search;
114 char *grep; 115 char *grep;
115 char *head; 116 char *head;
116 char *sha1; 117 char *sha1;
117 char *sha2; 118 char *sha2;
118 char *path; 119 char *path;
119 char *name; 120 char *name;
120 char *mimetype; 121 char *mimetype;
121 char *url; 122 char *url;
123 char *period;
122 int ofs; 124 int ofs;
123 int nohead; 125 int nohead;
124}; 126};
125 127
126struct cgit_config { 128struct cgit_config {
127 char *agefile; 129 char *agefile;
128 char *cache_root; 130 char *cache_root;
129 char *clone_prefix; 131 char *clone_prefix;
130 char *css; 132 char *css;
131 char *favicon; 133 char *favicon;
132 char *footer; 134 char *footer;
133 char *index_header; 135 char *index_header;
134 char *index_info; 136 char *index_info;
135 char *logo; 137 char *logo;
136 char *logo_link; 138 char *logo_link;
137 char *module_link; 139 char *module_link;
138 char *repo_group; 140 char *repo_group;
139 char *robots; 141 char *robots;
140 char *root_title; 142 char *root_title;
141 char *root_desc; 143 char *root_desc;
142 char *root_readme; 144 char *root_readme;
143 char *script_name; 145 char *script_name;
144 char *virtual_root; 146 char *virtual_root;
145 int cache_size; 147 int cache_size;
146 int cache_dynamic_ttl; 148 int cache_dynamic_ttl;
147 int cache_max_create_time; 149 int cache_max_create_time;
148 int cache_repo_ttl; 150 int cache_repo_ttl;
149 int cache_root_ttl; 151 int cache_root_ttl;
150 int cache_static_ttl; 152 int cache_static_ttl;
151 int enable_index_links; 153 int enable_index_links;
152 int enable_log_filecount; 154 int enable_log_filecount;
153 int enable_log_linecount; 155 int enable_log_linecount;
156 int enable_stats;
154 int local_time; 157 int local_time;
155 int max_repo_count; 158 int max_repo_count;
156 int max_commit_count; 159 int max_commit_count;
157 int max_lock_attempts; 160 int max_lock_attempts;
158 int max_msg_len; 161 int max_msg_len;
159 int max_repodesc_len; 162 int max_repodesc_len;
160 int nocache; 163 int nocache;
161 int renamelimit; 164 int renamelimit;
162 int snapshots; 165 int snapshots;
163 int summary_branches; 166 int summary_branches;
164 int summary_log; 167 int summary_log;
165 int summary_tags; 168 int summary_tags;
166}; 169};
167 170
168struct cgit_page { 171struct cgit_page {
169 time_t modified; 172 time_t modified;
170 time_t expires; 173 time_t expires;
171 size_t size; 174 size_t size;
172 char *mimetype; 175 char *mimetype;
173 char *charset; 176 char *charset;
174 char *filename; 177 char *filename;
175 char *title; 178 char *title;
176}; 179};
177 180
178struct cgit_context { 181struct cgit_context {
179 struct cgit_query qry; 182 struct cgit_query qry;
180 struct cgit_config cfg; 183 struct cgit_config cfg;
181 struct cgit_repo *repo; 184 struct cgit_repo *repo;
182 struct cgit_page page; 185 struct cgit_page page;
183}; 186};
184 187
185struct cgit_snapshot_format { 188struct cgit_snapshot_format {
186 const char *suffix; 189 const char *suffix;
187 const char *mimetype; 190 const char *mimetype;
188 write_archive_fn_t write_func; 191 write_archive_fn_t write_func;
189 int bit; 192 int bit;
190}; 193};
191 194
192extern const char *cgit_version; 195extern const char *cgit_version;
193 196
194extern struct cgit_repolist cgit_repolist; 197extern struct cgit_repolist cgit_repolist;
195extern struct cgit_context ctx; 198extern struct cgit_context ctx;
196extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 199extern const struct cgit_snapshot_format cgit_snapshot_formats[];
197 200
198extern struct cgit_repo *cgit_add_repo(const char *url); 201extern struct cgit_repo *cgit_add_repo(const char *url);
199extern struct cgit_repo *cgit_get_repoinfo(const char *url); 202extern struct cgit_repo *cgit_get_repoinfo(const char *url);
200extern void cgit_repo_config_cb(const char *name, const char *value); 203extern void cgit_repo_config_cb(const char *name, const char *value);
201 204
202extern int chk_zero(int result, char *msg); 205extern int chk_zero(int result, char *msg);
203extern int chk_positive(int result, char *msg); 206extern int chk_positive(int result, char *msg);
204extern int chk_non_negative(int result, char *msg); 207extern int chk_non_negative(int result, char *msg);
205 208
206extern char *trim_end(const char *str, char c); 209extern char *trim_end(const char *str, char c);
207extern char *strlpart(char *txt, int maxlen); 210extern char *strlpart(char *txt, int maxlen);
208extern char *strrpart(char *txt, int maxlen); 211extern char *strrpart(char *txt, int maxlen);
209 212
210extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 213extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
211extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 214extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
212 int flags, void *cb_data); 215 int flags, void *cb_data);
213 216
214extern void *cgit_free_commitinfo(struct commitinfo *info); 217extern void *cgit_free_commitinfo(struct commitinfo *info);
215 218
216extern int cgit_diff_files(const unsigned char *old_sha1, 219extern int cgit_diff_files(const unsigned char *old_sha1,
217 const unsigned char *new_sha1, 220 const unsigned char *new_sha1,
218 linediff_fn fn); 221 linediff_fn fn);
219 222
220extern void cgit_diff_tree(const unsigned char *old_sha1, 223extern void cgit_diff_tree(const unsigned char *old_sha1,
221 const unsigned char *new_sha1, 224 const unsigned char *new_sha1,
222 filepair_fn fn, const char *prefix); 225 filepair_fn fn, const char *prefix);
223 226
224extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 227extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
225 228
226extern char *fmt(const char *format,...); 229extern char *fmt(const char *format,...);
227 230
228extern struct commitinfo *cgit_parse_commit(struct commit *commit); 231extern struct commitinfo *cgit_parse_commit(struct commit *commit);
229extern struct taginfo *cgit_parse_tag(struct tag *tag); 232extern struct taginfo *cgit_parse_tag(struct tag *tag);
230extern void cgit_parse_url(const char *url); 233extern void cgit_parse_url(const char *url);
231 234
232extern const char *cgit_repobasename(const char *reponame); 235extern const char *cgit_repobasename(const char *reponame);
233 236
234extern int cgit_parse_snapshots_mask(const char *str); 237extern int cgit_parse_snapshots_mask(const char *str);
235 238
236/* libgit.a either links against or compiles its own implementation of 239/* libgit.a either links against or compiles its own implementation of
237 * strcasestr(), and we'd like to reuse it. Simply re-declaring it 240 * strcasestr(), and we'd like to reuse it. Simply re-declaring it
238 * seems to do the trick. 241 * seems to do the trick.
239 */ 242 */
240extern char *strcasestr(const char *haystack, const char *needle); 243extern char *strcasestr(const char *haystack, const char *needle);
241 244
242 245
243#endif /* CGIT_H */ 246#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 7887b02..60d3ea4 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,316 +1,324 @@
1CGITRC 1CGITRC
2====== 2======
3 3
4 4
5NAME 5NAME
6---- 6----
7 cgitrc - runtime configuration for cgit 7 cgitrc - runtime configuration for cgit
8 8
9 9
10DESCRIPTION 10DESCRIPTION
11----------- 11-----------
12Cgitrc contains all runtime settings for cgit, including the list of git 12Cgitrc contains all runtime settings for cgit, including the list of git
13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
14lines, and lines starting with '#', are ignored. 14lines, and lines starting with '#', are ignored.
15 15
16 16
17GLOBAL SETTINGS 17GLOBAL SETTINGS
18--------------- 18---------------
19agefile 19agefile
20 Specifies a path, relative to each repository path, which can be used 20 Specifies a path, relative to each repository path, which can be used
21 to specify the date and time of the youngest commit in the repository. 21 to specify the date and time of the youngest commit in the repository.
22 The first line in the file is used as input to the "parse_date" 22 The first line in the file is used as input to the "parse_date"
23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
24 hh:mm:ss". Default value: "info/web/last-modified". 24 hh:mm:ss". Default value: "info/web/last-modified".
25 25
26cache-root 26cache-root
27 Path used to store the cgit cache entries. Default value: 27 Path used to store the cgit cache entries. Default value:
28 "/var/cache/cgit". 28 "/var/cache/cgit".
29 29
30cache-dynamic-ttl 30cache-dynamic-ttl
31 Number which specifies the time-to-live, in minutes, for the cached 31 Number which specifies the time-to-live, in minutes, for the cached
32 version of repository pages accessed without a fixed SHA1. Default 32 version of repository pages accessed without a fixed SHA1. Default
33 value: "5". 33 value: "5".
34 34
35cache-repo-ttl 35cache-repo-ttl
36 Number which specifies the time-to-live, in minutes, for the cached 36 Number which specifies the time-to-live, in minutes, for the cached
37 version of the repository summary page. Default value: "5". 37 version of the repository summary page. Default value: "5".
38 38
39cache-root-ttl 39cache-root-ttl
40 Number which specifies the time-to-live, in minutes, for the cached 40 Number which specifies the time-to-live, in minutes, for the cached
41 version of the repository index page. Default value: "5". 41 version of the repository index page. Default value: "5".
42 42
43cache-size 43cache-size
44 The maximum number of entries in the cgit cache. Default value: "0" 44 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 45 (i.e. caching is disabled).
46 46
47cache-static-ttl 47cache-static-ttl
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 49 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 50 "5".
51 51
52clone-prefix 52clone-prefix
53 Space-separated list of common prefixes which, when combined with a 53 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 54 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 55 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 56 none.
57 57
58css 58css
59 Url which specifies the css document to include in all cgit pages. 59 Url which specifies the css document to include in all cgit pages.
60 Default value: "/cgit.css". 60 Default value: "/cgit.css".
61 61
62enable-index-links 62enable-index-links
63 Flag which, when set to "1", will make cgit generate extra links for 63 Flag which, when set to "1", will make cgit generate extra links for
64 each repo in the repository index (specifically, to the "summary", 64 each repo in the repository index (specifically, to the "summary",
65 "commit" and "tree" pages). Default value: "0". 65 "commit" and "tree" pages). Default value: "0".
66 66
67enable-log-filecount 67enable-log-filecount
68 Flag which, when set to "1", will make cgit print the number of 68 Flag which, when set to "1", will make cgit print the number of
69 modified files for each commit on the repository log page. Default 69 modified files for each commit on the repository log page. Default
70 value: "0". 70 value: "0".
71 71
72enable-log-linecount 72enable-log-linecount
73 Flag which, when set to "1", will make cgit print the number of added 73 Flag which, when set to "1", will make cgit print the number of added
74 and removed lines for each commit on the repository log page. Default 74 and removed lines for each commit on the repository log page. Default
75 value: "0". 75 value: "0".
76 76
77enable-stats
78 Globally enable/disable statistics for each repository. Default
79 value: "0".
80
77favicon 81favicon
78 Url used as link to a shortcut icon for cgit. If specified, it is 82 Url used as link to a shortcut icon for cgit. If specified, it is
79 suggested to use the value "/favicon.ico" since certain browsers will 83 suggested to use the value "/favicon.ico" since certain browsers will
80 ignore other values. Default value: none. 84 ignore other values. Default value: none.
81 85
82footer 86footer
83 The content of the file specified with this option will be included 87 The content of the file specified with this option will be included
84 verbatim at the bottom of all pages (i.e. it replaces the standard 88 verbatim at the bottom of all pages (i.e. it replaces the standard
85 "generated by..." message. Default value: none. 89 "generated by..." message. Default value: none.
86 90
87include 91include
88 Name of a configfile to include before the rest of the current config- 92 Name of a configfile to include before the rest of the current config-
89 file is parsed. Default value: none. 93 file is parsed. Default value: none.
90 94
91index-header 95index-header
92 The content of the file specified with this option will be included 96 The content of the file specified with this option will be included
93 verbatim above the repository index. This setting is deprecated, and 97 verbatim above the repository index. This setting is deprecated, and
94 will not be supported by cgit-1.0 (use root-readme instead). Default 98 will not be supported by cgit-1.0 (use root-readme instead). Default
95 value: none. 99 value: none.
96 100
97index-info 101index-info
98 The content of the file specified with this option will be included 102 The content of the file specified with this option will be included
99 verbatim below the heading on the repository index page. This setting 103 verbatim below the heading on the repository index page. This setting
100 is deprecated, and will not be supported by cgit-1.0 (use root-desc 104 is deprecated, and will not be supported by cgit-1.0 (use root-desc
101 instead). Default value: none. 105 instead). Default value: none.
102 106
103local-time 107local-time
104 Flag which, if set to "1", makes cgit print commit and tag times in the 108 Flag which, if set to "1", makes cgit print commit and tag times in the
105 servers timezone. Default value: "0". 109 servers timezone. Default value: "0".
106 110
107logo 111logo
108 Url which specifies the source of an image which will be used as a logo 112 Url which specifies the source of an image which will be used as a logo
109 on all cgit pages. 113 on all cgit pages.
110 114
111logo-link 115logo-link
112 Url loaded when clicking on the cgit logo image. If unspecified the 116 Url loaded when clicking on the cgit logo image. If unspecified the
113 calculated url of the repository index page will be used. Default 117 calculated url of the repository index page will be used. Default
114 value: none. 118 value: none.
115 119
116max-commit-count 120max-commit-count
117 Specifies the number of entries to list per page in "log" view. Default 121 Specifies the number of entries to list per page in "log" view. Default
118 value: "50". 122 value: "50".
119 123
120max-message-length 124max-message-length
121 Specifies the maximum number of commit message characters to display in 125 Specifies the maximum number of commit message characters to display in
122 "log" view. Default value: "80". 126 "log" view. Default value: "80".
123 127
124max-repo-count 128max-repo-count
125 Specifies the number of entries to list per page on therepository 129 Specifies the number of entries to list per page on therepository
126 index page. Default value: "50". 130 index page. Default value: "50".
127 131
128max-repodesc-length 132max-repodesc-length
129 Specifies the maximum number of repo description characters to display 133 Specifies the maximum number of repo description characters to display
130 on the repository index page. Default value: "80". 134 on the repository index page. Default value: "80".
131 135
132module-link 136module-link
133 Text which will be used as the formatstring for a hyperlink when a 137 Text which will be used as the formatstring for a hyperlink when a
134 submodule is printed in a directory listing. The arguments for the 138 submodule is printed in a directory listing. The arguments for the
135 formatstring are the path and SHA1 of the submodule commit. Default 139 formatstring are the path and SHA1 of the submodule commit. Default
136 value: "./?repo=%s&page=commit&id=%s" 140 value: "./?repo=%s&page=commit&id=%s"
137 141
138nocache 142nocache
139 If set to the value "1" caching will be disabled. This settings is 143 If set to the value "1" caching will be disabled. This settings is
140 deprecated, and will not be honored starting with cgit-1.0. Default 144 deprecated, and will not be honored starting with cgit-1.0. Default
141 value: "0". 145 value: "0".
142 146
143renamelimit 147renamelimit
144 Maximum number of files to consider when detecting renames. The value 148 Maximum number of files to consider when detecting renames. The value
145 "-1" uses the compiletime value in git (for further info, look at 149 "-1" uses the compiletime value in git (for further info, look at
146 `man git-diff`). Default value: "-1". 150 `man git-diff`). Default value: "-1".
147 151
148repo.group 152repo.group
149 A value for the current repository group, which all repositories 153 A value for the current repository group, which all repositories
150 specified after this setting will inherit. Default value: none. 154 specified after this setting will inherit. Default value: none.
151 155
152robots 156robots
153 Text used as content for the "robots" meta-tag. Default value: 157 Text used as content for the "robots" meta-tag. Default value:
154 "index, nofollow". 158 "index, nofollow".
155 159
156root-desc 160root-desc
157 Text printed below the heading on the repository index page. Default 161 Text printed below the heading on the repository index page. Default
158 value: "a fast webinterface for the git dscm". 162 value: "a fast webinterface for the git dscm".
159 163
160root-readme: 164root-readme:
161 The content of the file specified with this option will be included 165 The content of the file specified with this option will be included
162 verbatim below the "about" link on the repository index page. Default 166 verbatim below the "about" link on the repository index page. Default
163 value: none. 167 value: none.
164 168
165root-title 169root-title
166 Text printed as heading on the repository index page. Default value: 170 Text printed as heading on the repository index page. Default value:
167 "Git Repository Browser". 171 "Git Repository Browser".
168 172
169snapshots 173snapshots
170 Text which specifies the default (and allowed) set of snapshot formats 174 Text which specifies the default (and allowed) set of snapshot formats
171 supported by cgit. The value is a space-separated list of zero or more 175 supported by cgit. The value is a space-separated list of zero or more
172 of the following values: 176 of the following values:
173 "tar" uncompressed tar-file 177 "tar" uncompressed tar-file
174 "tar.gz"gzip-compressed tar-file 178 "tar.gz"gzip-compressed tar-file
175 "tar.bz2"bzip-compressed tar-file 179 "tar.bz2"bzip-compressed tar-file
176 "zip" zip-file 180 "zip" zip-file
177 Default value: none. 181 Default value: none.
178 182
179summary-branches 183summary-branches
180 Specifies the number of branches to display in the repository "summary" 184 Specifies the number of branches to display in the repository "summary"
181 view. Default value: "10". 185 view. Default value: "10".
182 186
183summary-log 187summary-log
184 Specifies the number of log entries to display in the repository 188 Specifies the number of log entries to display in the repository
185 "summary" view. Default value: "10". 189 "summary" view. Default value: "10".
186 190
187summary-tags 191summary-tags
188 Specifies the number of tags to display in the repository "summary" 192 Specifies the number of tags to display in the repository "summary"
189 view. Default value: "10". 193 view. Default value: "10".
190 194
191virtual-root 195virtual-root
192 Url which, if specified, will be used as root for all cgit links. It 196 Url which, if specified, will be used as root for all cgit links. It
193 will also cause cgit to generate 'virtual urls', i.e. urls like 197 will also cause cgit to generate 'virtual urls', i.e. urls like
194 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 198 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
195 value: none. 199 value: none.
196 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 200 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
197 same kind of virtual urls, so this option will probably be deprecated. 201 same kind of virtual urls, so this option will probably be deprecated.
198 202
199REPOSITORY SETTINGS 203REPOSITORY SETTINGS
200------------------- 204-------------------
201repo.clone-url 205repo.clone-url
202 A list of space-separated urls which can be used to clone this repo. 206 A list of space-separated urls which can be used to clone this repo.
203 Default value: none. 207 Default value: none.
204 208
205repo.defbranch 209repo.defbranch
206 The name of the default branch for this repository. If no such branch 210 The name of the default branch for this repository. If no such branch
207 exists in the repository, the first branch name (when sorted) is used 211 exists in the repository, the first branch name (when sorted) is used
208 as default instead. Default value: "master". 212 as default instead. Default value: "master".
209 213
210repo.desc 214repo.desc
211 The value to show as repository description. Default value: none. 215 The value to show as repository description. Default value: none.
212 216
213repo.enable-log-filecount 217repo.enable-log-filecount
214 A flag which can be used to disable the global setting 218 A flag which can be used to disable the global setting
215 `enable-log-filecount'. Default value: none. 219 `enable-log-filecount'. Default value: none.
216 220
217repo.enable-log-linecount 221repo.enable-log-linecount
218 A flag which can be used to disable the global setting 222 A flag which can be used to disable the global setting
219 `enable-log-linecount'. Default value: none. 223 `enable-log-linecount'. Default value: none.
220 224
225repo.enable-stats
226 A flag which can be used to disable the global setting
227 `enable-stats'. Default value: none.
228
221repo.name 229repo.name
222 The value to show as repository name. Default value: <repo.url>. 230 The value to show as repository name. Default value: <repo.url>.
223 231
224repo.owner 232repo.owner
225 A value used to identify the owner of the repository. Default value: 233 A value used to identify the owner of the repository. Default value:
226 none. 234 none.
227 235
228repo.path 236repo.path
229 An absolute path to the repository directory. For non-bare repositories 237 An absolute path to the repository directory. For non-bare repositories
230 this is the .git-directory. Default value: none. 238 this is the .git-directory. Default value: none.
231 239
232repo.readme 240repo.readme
233 A path (relative to <repo.path>) which specifies a file to include 241 A path (relative to <repo.path>) which specifies a file to include
234 verbatim as the "About" page for this repo. Default value: none. 242 verbatim as the "About" page for this repo. Default value: none.
235 243
236repo.snapshots 244repo.snapshots
237 A mask of allowed snapshot-formats for this repo, restricted by the 245 A mask of allowed snapshot-formats for this repo, restricted by the
238 "snapshots" global setting. Default value: <snapshots>. 246 "snapshots" global setting. Default value: <snapshots>.
239 247
240repo.url 248repo.url
241 The relative url used to access the repository. This must be the first 249 The relative url used to access the repository. This must be the first
242 setting specified for each repo. Default value: none. 250 setting specified for each repo. Default value: none.
243 251
244 252
245EXAMPLE CGITRC FILE 253EXAMPLE CGITRC FILE
246------------------- 254-------------------
247 255
248# Enable caching of up to 1000 output entriess 256# Enable caching of up to 1000 output entriess
249cache-size=1000 257cache-size=1000
250 258
251 259
252# Specify some default clone prefixes 260# Specify some default clone prefixes
253clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 261clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
254 262
255# Specify the css url 263# Specify the css url
256css=/css/cgit.css 264css=/css/cgit.css
257 265
258 266
259# Show extra links for each repository on the index page 267# Show extra links for each repository on the index page
260enable-index-links=1 268enable-index-links=1
261 269
262 270
263# Show number of affected files per commit on the log pages 271# Show number of affected files per commit on the log pages
264enable-log-filecount=1 272enable-log-filecount=1
265 273
266 274
267# Show number of added/removed lines per commit on the log pages 275# Show number of added/removed lines per commit on the log pages
268enable-log-linecount=1 276enable-log-linecount=1
269 277
270 278
271# Add a cgit favicon 279# Add a cgit favicon
272favicon=/favicon.ico 280favicon=/favicon.ico
273 281
274 282
275# Use a custom logo 283# Use a custom logo
276logo=/img/mylogo.png 284logo=/img/mylogo.png
277 285
278 286
279# Set the title and heading of the repository index page 287# Set the title and heading of the repository index page
280root-title=foobar.com git repositories 288root-title=foobar.com git repositories
281 289
282 290
283# Set a subheading for the repository index page 291# Set a subheading for the repository index page
284root-desc=tracking the foobar development 292root-desc=tracking the foobar development
285 293
286 294
287# Include some more info about foobar.com on the index page 295# Include some more info about foobar.com on the index page
288root-readme=/var/www/htdocs/about.html 296root-readme=/var/www/htdocs/about.html
289 297
290 298
291# Allow download of tar.gz, tar.bz and zip-files 299# Allow download of tar.gz, tar.bz and zip-files
292snapshots=tar.gz tar.bz zip 300snapshots=tar.gz tar.bz zip
293 301
294 302
295## 303##
296## List of repositories. 304## List of repositories.
297## PS: Any repositories listed when repo.group is unset will not be 305## PS: Any repositories listed when repo.group is unset will not be
298## displayed under a group heading 306## displayed under a group heading
299## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 307## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
300## and included like this: 308## and included like this:
301## include=/etc/cgitrepos 309## include=/etc/cgitrepos
302## 310##
303 311
304 312
305repo.url=foo 313repo.url=foo
306repo.path=/pub/git/foo.git 314repo.path=/pub/git/foo.git
307repo.desc=the master foo repository 315repo.desc=the master foo repository
308repo.owner=fooman@foobar.com 316repo.owner=fooman@foobar.com
309repo.readme=info/web/about.html 317repo.readme=info/web/about.html
310 318
311 319
312repo.url=bar 320repo.url=bar
313repo.path=/pub/git/bar.git 321repo.path=/pub/git/bar.git
314repo.desc=the bars for your foo 322repo.desc=the bars for your foo
315repo.owner=barman@foobar.com 323repo.owner=barman@foobar.com
316repo.readme=info/web/about.html 324repo.readme=info/web/about.html
diff --git a/cmd.c b/cmd.c
index 5b3c14c..744bf84 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,165 +1,175 @@
1/* cmd.c: the cgit command dispatcher 1/* cmd.c: the cgit command dispatcher
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 "cmd.h" 10#include "cmd.h"
11#include "cache.h" 11#include "cache.h"
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-summary.h" 25#include "ui-summary.h"
25#include "ui-tag.h" 26#include "ui-tag.h"
26#include "ui-tree.h" 27#include "ui-tree.h"
27 28
28static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
29{ 30{
30 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
31} 32}
32 33
33static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
34{ 35{
35 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10);
36} 37}
37 38
38static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
39{ 40{
40 if (ctx->repo) 41 if (ctx->repo)
41 cgit_print_repo_readme(); 42 cgit_print_repo_readme();
42 else 43 else
43 cgit_print_site_readme(); 44 cgit_print_site_readme();
44} 45}
45 46
46static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
47{ 48{
48 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);
49} 50}
50 51
51static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
52{ 53{
53 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1);
54} 55}
55 56
56static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
57{ 58{
58 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);
59} 60}
60 61
61static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
62{ 63{
63 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
64} 65}
65 66
66static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
67{ 68{
68 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,
69 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
70} 71}
71 72
72static void ls_cache_fn(struct cgit_context *ctx) 73static void ls_cache_fn(struct cgit_context *ctx)
73{ 74{
74 ctx->page.mimetype = "text/plain"; 75 ctx->page.mimetype = "text/plain";
75 ctx->page.filename = "ls-cache.txt"; 76 ctx->page.filename = "ls-cache.txt";
76 cgit_print_http_headers(ctx); 77 cgit_print_http_headers(ctx);
77 cache_ls(ctx->cfg.cache_root); 78 cache_ls(ctx->cfg.cache_root);
78} 79}
79 80
80static void objects_fn(struct cgit_context *ctx) 81static void objects_fn(struct cgit_context *ctx)
81{ 82{
82 cgit_clone_objects(ctx); 83 cgit_clone_objects(ctx);
83} 84}
84 85
85static void repolist_fn(struct cgit_context *ctx) 86static void repolist_fn(struct cgit_context *ctx)
86{ 87{
87 cgit_print_repolist(); 88 cgit_print_repolist();
88} 89}
89 90
90static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
91{ 92{
92 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1);
93} 94}
94 95
95static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
96{ 97{
97 cgit_print_plain(ctx); 98 cgit_print_plain(ctx);
98} 99}
99 100
100static void refs_fn(struct cgit_context *ctx) 101static void refs_fn(struct cgit_context *ctx)
101{ 102{
102 cgit_print_refs(); 103 cgit_print_refs();
103} 104}
104 105
105static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
106{ 107{
107 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1,
108 cgit_repobasename(ctx->repo->url), ctx->qry.path, 109 cgit_repobasename(ctx->repo->url), ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 110 ctx->repo->snapshots, ctx->qry.nohead);
110} 111}
111 112
113static void stats_fn(struct cgit_context *ctx)
114{
115 if (ctx->repo->enable_stats)
116 cgit_show_stats(ctx);
117 else
118 cgit_print_error("Stats disabled for this repo");
119}
120
112static void summary_fn(struct cgit_context *ctx) 121static void summary_fn(struct cgit_context *ctx)
113{ 122{
114 cgit_print_summary(); 123 cgit_print_summary();
115} 124}
116 125
117static void tag_fn(struct cgit_context *ctx) 126static void tag_fn(struct cgit_context *ctx)
118{ 127{
119 cgit_print_tag(ctx->qry.sha1); 128 cgit_print_tag(ctx->qry.sha1);
120} 129}
121 130
122static void tree_fn(struct cgit_context *ctx) 131static void tree_fn(struct cgit_context *ctx)
123{ 132{
124 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 133 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
125} 134}
126 135
127#define def_cmd(name, want_repo, want_layout) \ 136#define def_cmd(name, want_repo, want_layout) \
128 {#name, name##_fn, want_repo, want_layout} 137 {#name, name##_fn, want_repo, want_layout}
129 138
130struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 139struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
131{ 140{
132 static struct cgit_cmd cmds[] = { 141 static struct cgit_cmd cmds[] = {
133 def_cmd(HEAD, 1, 0), 142 def_cmd(HEAD, 1, 0),
134 def_cmd(atom, 1, 0), 143 def_cmd(atom, 1, 0),
135 def_cmd(about, 0, 1), 144 def_cmd(about, 0, 1),
136 def_cmd(blob, 1, 0), 145 def_cmd(blob, 1, 0),
137 def_cmd(commit, 1, 1), 146 def_cmd(commit, 1, 1),
138 def_cmd(diff, 1, 1), 147 def_cmd(diff, 1, 1),
139 def_cmd(info, 1, 0), 148 def_cmd(info, 1, 0),
140 def_cmd(log, 1, 1), 149 def_cmd(log, 1, 1),
141 def_cmd(ls_cache, 0, 0), 150 def_cmd(ls_cache, 0, 0),
142 def_cmd(objects, 1, 0), 151 def_cmd(objects, 1, 0),
143 def_cmd(patch, 1, 0), 152 def_cmd(patch, 1, 0),
144 def_cmd(plain, 1, 0), 153 def_cmd(plain, 1, 0),
145 def_cmd(refs, 1, 1), 154 def_cmd(refs, 1, 1),
146 def_cmd(repolist, 0, 0), 155 def_cmd(repolist, 0, 0),
147 def_cmd(snapshot, 1, 0), 156 def_cmd(snapshot, 1, 0),
157 def_cmd(stats, 1, 1),
148 def_cmd(summary, 1, 1), 158 def_cmd(summary, 1, 1),
149 def_cmd(tag, 1, 1), 159 def_cmd(tag, 1, 1),
150 def_cmd(tree, 1, 1), 160 def_cmd(tree, 1, 1),
151 }; 161 };
152 int i; 162 int i;
153 163
154 if (ctx->qry.page == NULL) { 164 if (ctx->qry.page == NULL) {
155 if (ctx->repo) 165 if (ctx->repo)
156 ctx->qry.page = "summary"; 166 ctx->qry.page = "summary";
157 else 167 else
158 ctx->qry.page = "repolist"; 168 ctx->qry.page = "repolist";
159 } 169 }
160 170
161 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 171 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
162 if (!strcmp(ctx->qry.page, cmds[i].name)) 172 if (!strcmp(ctx->qry.page, cmds[i].name))
163 return &cmds[i]; 173 return &cmds[i];
164 return NULL; 174 return NULL;
165} 175}
diff --git a/shared.c b/shared.c
index f5875e4..37333f0 100644
--- a/shared.c
+++ b/shared.c
@@ -1,156 +1,157 @@
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; 13int cgit_cmd;
14 14
15int chk_zero(int result, char *msg) 15int chk_zero(int result, char *msg)
16{ 16{
17 if (result != 0) 17 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 18 die("%s: %s", msg, strerror(errno));
19 return result; 19 return result;
20} 20}
21 21
22int chk_positive(int result, char *msg) 22int chk_positive(int result, char *msg)
23{ 23{
24 if (result <= 0) 24 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 25 die("%s: %s", msg, strerror(errno));
26 return result; 26 return result;
27} 27}
28 28
29int chk_non_negative(int result, char *msg) 29int chk_non_negative(int result, char *msg)
30{ 30{
31 if (result < 0) 31 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 32 die("%s: %s",msg, strerror(errno));
33 return result; 33 return result;
34} 34}
35 35
36struct cgit_repo *cgit_add_repo(const char *url) 36struct cgit_repo *cgit_add_repo(const char *url)
37{ 37{
38 struct cgit_repo *ret; 38 struct cgit_repo *ret;
39 39
40 if (++cgit_repolist.count > cgit_repolist.length) { 40 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 41 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 42 cgit_repolist.length = 8;
43 else 43 else
44 cgit_repolist.length *= 2; 44 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 45 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 46 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 47 sizeof(struct cgit_repo));
48 } 48 }
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 56 ret->group = ctx.cfg.repo_group;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_stats = ctx.cfg.enable_stats;
61 ret->module_link = ctx.cfg.module_link; 62 ret->module_link = ctx.cfg.module_link;
62 ret->readme = NULL; 63 ret->readme = NULL;
63 return ret; 64 return ret;
64} 65}
65 66
66struct cgit_repo *cgit_get_repoinfo(const char *url) 67struct cgit_repo *cgit_get_repoinfo(const char *url)
67{ 68{
68 int i; 69 int i;
69 struct cgit_repo *repo; 70 struct cgit_repo *repo;
70 71
71 for (i=0; i<cgit_repolist.count; i++) { 72 for (i=0; i<cgit_repolist.count; i++) {
72 repo = &cgit_repolist.repos[i]; 73 repo = &cgit_repolist.repos[i];
73 if (!strcmp(repo->url, url)) 74 if (!strcmp(repo->url, url))
74 return repo; 75 return repo;
75 } 76 }
76 return NULL; 77 return NULL;
77} 78}
78 79
79void *cgit_free_commitinfo(struct commitinfo *info) 80void *cgit_free_commitinfo(struct commitinfo *info)
80{ 81{
81 free(info->author); 82 free(info->author);
82 free(info->author_email); 83 free(info->author_email);
83 free(info->committer); 84 free(info->committer);
84 free(info->committer_email); 85 free(info->committer_email);
85 free(info->subject); 86 free(info->subject);
86 free(info->msg); 87 free(info->msg);
87 free(info->msg_encoding); 88 free(info->msg_encoding);
88 free(info); 89 free(info);
89 return NULL; 90 return NULL;
90} 91}
91 92
92char *trim_end(const char *str, char c) 93char *trim_end(const char *str, char c)
93{ 94{
94 int len; 95 int len;
95 char *s, *t; 96 char *s, *t;
96 97
97 if (str == NULL) 98 if (str == NULL)
98 return NULL; 99 return NULL;
99 t = (char *)str; 100 t = (char *)str;
100 len = strlen(t); 101 len = strlen(t);
101 while(len > 0 && t[len - 1] == c) 102 while(len > 0 && t[len - 1] == c)
102 len--; 103 len--;
103 104
104 if (len == 0) 105 if (len == 0)
105 return NULL; 106 return NULL;
106 107
107 c = t[len]; 108 c = t[len];
108 t[len] = '\0'; 109 t[len] = '\0';
109 s = xstrdup(t); 110 s = xstrdup(t);
110 t[len] = c; 111 t[len] = c;
111 return s; 112 return s;
112} 113}
113 114
114char *strlpart(char *txt, int maxlen) 115char *strlpart(char *txt, int maxlen)
115{ 116{
116 char *result; 117 char *result;
117 118
118 if (!txt) 119 if (!txt)
119 return txt; 120 return txt;
120 121
121 if (strlen(txt) <= maxlen) 122 if (strlen(txt) <= maxlen)
122 return txt; 123 return txt;
123 result = xmalloc(maxlen + 1); 124 result = xmalloc(maxlen + 1);
124 memcpy(result, txt, maxlen - 3); 125 memcpy(result, txt, maxlen - 3);
125 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 126 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
126 result[maxlen] = '\0'; 127 result[maxlen] = '\0';
127 return result; 128 return result;
128} 129}
129 130
130char *strrpart(char *txt, int maxlen) 131char *strrpart(char *txt, int maxlen)
131{ 132{
132 char *result; 133 char *result;
133 134
134 if (!txt) 135 if (!txt)
135 return txt; 136 return txt;
136 137
137 if (strlen(txt) <= maxlen) 138 if (strlen(txt) <= maxlen)
138 return txt; 139 return txt;
139 result = xmalloc(maxlen + 1); 140 result = xmalloc(maxlen + 1);
140 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 141 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
141 result[0] = result[1] = result[2] = '.'; 142 result[0] = result[1] = result[2] = '.';
142 return result; 143 return result;
143} 144}
144 145
145void cgit_add_ref(struct reflist *list, struct refinfo *ref) 146void cgit_add_ref(struct reflist *list, struct refinfo *ref)
146{ 147{
147 size_t size; 148 size_t size;
148 149
149 if (list->count >= list->alloc) { 150 if (list->count >= list->alloc) {
150 list->alloc += (list->alloc ? list->alloc : 4); 151 list->alloc += (list->alloc ? list->alloc : 4);
151 size = list->alloc * sizeof(struct refinfo *); 152 size = list->alloc * sizeof(struct refinfo *);
152 list->refs = xrealloc(list->refs, size); 153 list->refs = xrealloc(list->refs, size);
153 } 154 }
154 list->refs[list->count++] = ref; 155 list->refs[list->count++] = ref;
155} 156}
156 157
diff --git a/ui-shared.c b/ui-shared.c
index 224e5f3..0e688a0 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -548,167 +548,170 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
548 html_link_close(); 548 html_link_close();
549 return 0; 549 return 0;
550} 550}
551 551
552void add_hidden_formfields(int incl_head, int incl_search, char *page) 552void add_hidden_formfields(int incl_head, int incl_search, char *page)
553{ 553{
554 char *url; 554 char *url;
555 555
556 if (!ctx.cfg.virtual_root) { 556 if (!ctx.cfg.virtual_root) {
557 url = fmt("%s/%s", ctx.qry.repo, page); 557 url = fmt("%s/%s", ctx.qry.repo, page);
558 if (ctx.qry.path) 558 if (ctx.qry.path)
559 url = fmt("%s/%s", url, ctx.qry.path); 559 url = fmt("%s/%s", url, ctx.qry.path);
560 html_hidden("url", url); 560 html_hidden("url", url);
561 } 561 }
562 562
563 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 563 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
564 strcmp(ctx.qry.head, ctx.repo->defbranch)) 564 strcmp(ctx.qry.head, ctx.repo->defbranch))
565 html_hidden("h", ctx.qry.head); 565 html_hidden("h", ctx.qry.head);
566 566
567 if (ctx.qry.sha1) 567 if (ctx.qry.sha1)
568 html_hidden("id", ctx.qry.sha1); 568 html_hidden("id", ctx.qry.sha1);
569 if (ctx.qry.sha2) 569 if (ctx.qry.sha2)
570 html_hidden("id2", ctx.qry.sha2); 570 html_hidden("id2", ctx.qry.sha2);
571 571
572 if (incl_search) { 572 if (incl_search) {
573 if (ctx.qry.grep) 573 if (ctx.qry.grep)
574 html_hidden("qt", ctx.qry.grep); 574 html_hidden("qt", ctx.qry.grep);
575 if (ctx.qry.search) 575 if (ctx.qry.search)
576 html_hidden("q", ctx.qry.search); 576 html_hidden("q", ctx.qry.search);
577 } 577 }
578} 578}
579 579
580char *hc(struct cgit_cmd *cmd, const char *page) 580char *hc(struct cgit_cmd *cmd, const char *page)
581{ 581{
582 return (strcmp(cmd->name, page) ? NULL : "active"); 582 return (strcmp(cmd->name, page) ? NULL : "active");
583} 583}
584 584
585void cgit_print_pageheader(struct cgit_context *ctx) 585void cgit_print_pageheader(struct cgit_context *ctx)
586{ 586{
587 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 587 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
588 588
589 html("<table id='header'>\n"); 589 html("<table id='header'>\n");
590 html("<tr>\n"); 590 html("<tr>\n");
591 html("<td class='logo' rowspan='2'><a href='"); 591 html("<td class='logo' rowspan='2'><a href='");
592 if (ctx->cfg.logo_link) 592 if (ctx->cfg.logo_link)
593 html_attr(ctx->cfg.logo_link); 593 html_attr(ctx->cfg.logo_link);
594 else 594 else
595 html_attr(cgit_rooturl()); 595 html_attr(cgit_rooturl());
596 html("'><img src='"); 596 html("'><img src='");
597 html_attr(ctx->cfg.logo); 597 html_attr(ctx->cfg.logo);
598 html("' alt='cgit logo'/></a></td>\n"); 598 html("' alt='cgit logo'/></a></td>\n");
599 599
600 html("<td class='main'>"); 600 html("<td class='main'>");
601 if (ctx->repo) { 601 if (ctx->repo) {
602 cgit_index_link("index", NULL, NULL, NULL, 0); 602 cgit_index_link("index", NULL, NULL, NULL, 0);
603 html(" : "); 603 html(" : ");
604 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 604 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
605 html("</td><td class='form'>"); 605 html("</td><td class='form'>");
606 html("<form method='get' action=''>\n"); 606 html("<form method='get' action=''>\n");
607 add_hidden_formfields(0, 1, ctx->qry.page); 607 add_hidden_formfields(0, 1, ctx->qry.page);
608 html("<select name='h' onchange='this.form.submit();'>\n"); 608 html("<select name='h' onchange='this.form.submit();'>\n");
609 for_each_branch_ref(print_branch_option, ctx->qry.head); 609 for_each_branch_ref(print_branch_option, ctx->qry.head);
610 html("</select> "); 610 html("</select> ");
611 html("<input type='submit' name='' value='switch'/>"); 611 html("<input type='submit' name='' value='switch'/>");
612 html("</form>"); 612 html("</form>");
613 } else 613 } else
614 html_txt(ctx->cfg.root_title); 614 html_txt(ctx->cfg.root_title);
615 html("</td></tr>\n"); 615 html("</td></tr>\n");
616 616
617 html("<tr><td class='sub'>"); 617 html("<tr><td class='sub'>");
618 if (ctx->repo) { 618 if (ctx->repo) {
619 html_txt(ctx->repo->desc); 619 html_txt(ctx->repo->desc);
620 html("</td><td class='sub right'>"); 620 html("</td><td class='sub right'>");
621 html_txt(ctx->repo->owner); 621 html_txt(ctx->repo->owner);
622 } else { 622 } else {
623 if (ctx->cfg.root_desc) 623 if (ctx->cfg.root_desc)
624 html_txt(ctx->cfg.root_desc); 624 html_txt(ctx->cfg.root_desc);
625 else if (ctx->cfg.index_info) 625 else if (ctx->cfg.index_info)
626 html_include(ctx->cfg.index_info); 626 html_include(ctx->cfg.index_info);
627 } 627 }
628 html("</td></tr></table>\n"); 628 html("</td></tr></table>\n");
629 629
630 html("<table class='tabs'><tr><td>\n"); 630 html("<table class='tabs'><tr><td>\n");
631 if (ctx->repo) { 631 if (ctx->repo) {
632 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 632 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
633 ctx->qry.head); 633 ctx->qry.head);
634 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 634 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
635 ctx->qry.sha1, NULL); 635 ctx->qry.sha1, NULL);
636 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 636 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
637 NULL, NULL, 0, NULL, NULL); 637 NULL, NULL, 0, NULL, NULL);
638 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 638 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
639 ctx->qry.sha1, NULL); 639 ctx->qry.sha1, NULL);
640 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 640 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
641 ctx->qry.head, ctx->qry.sha1); 641 ctx->qry.head, ctx->qry.sha1);
642 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 642 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
643 ctx->qry.sha1, ctx->qry.sha2, NULL); 643 ctx->qry.sha1, ctx->qry.sha2, NULL);
644 if (ctx->repo->enable_stats)
645 reporevlink("stats", "stats", NULL, hc(cmd, "stats"),
646 ctx->qry.head, NULL, NULL);
644 if (ctx->repo->readme) 647 if (ctx->repo->readme)
645 reporevlink("about", "about", NULL, 648 reporevlink("about", "about", NULL,
646 hc(cmd, "about"), ctx->qry.head, NULL, 649 hc(cmd, "about"), ctx->qry.head, NULL,
647 NULL); 650 NULL);
648 html("</td><td class='form'>"); 651 html("</td><td class='form'>");
649 html("<form class='right' method='get' action='"); 652 html("<form class='right' method='get' action='");
650 if (ctx->cfg.virtual_root) 653 if (ctx->cfg.virtual_root)
651 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 654 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
652 ctx->qry.path, NULL)); 655 ctx->qry.path, NULL));
653 html("'>\n"); 656 html("'>\n");
654 add_hidden_formfields(1, 0, "log"); 657 add_hidden_formfields(1, 0, "log");
655 html("<select name='qt'>\n"); 658 html("<select name='qt'>\n");
656 html_option("grep", "log msg", ctx->qry.grep); 659 html_option("grep", "log msg", ctx->qry.grep);
657 html_option("author", "author", ctx->qry.grep); 660 html_option("author", "author", ctx->qry.grep);
658 html_option("committer", "committer", ctx->qry.grep); 661 html_option("committer", "committer", ctx->qry.grep);
659 html("</select>\n"); 662 html("</select>\n");
660 html("<input class='txt' type='text' size='10' name='q' value='"); 663 html("<input class='txt' type='text' size='10' name='q' value='");
661 html_attr(ctx->qry.search); 664 html_attr(ctx->qry.search);
662 html("'/>\n"); 665 html("'/>\n");
663 html("<input type='submit' value='search'/>\n"); 666 html("<input type='submit' value='search'/>\n");
664 html("</form>\n"); 667 html("</form>\n");
665 } else { 668 } else {
666 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 669 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
667 if (ctx->cfg.root_readme) 670 if (ctx->cfg.root_readme)
668 site_link("about", "about", NULL, hc(cmd, "about"), 671 site_link("about", "about", NULL, hc(cmd, "about"),
669 NULL, 0); 672 NULL, 0);
670 html("</td><td class='form'>"); 673 html("</td><td class='form'>");
671 html("<form method='get' action='"); 674 html("<form method='get' action='");
672 html_attr(cgit_rooturl()); 675 html_attr(cgit_rooturl());
673 html("'>\n"); 676 html("'>\n");
674 html("<input type='text' name='q' size='10' value='"); 677 html("<input type='text' name='q' size='10' value='");
675 html_attr(ctx->qry.search); 678 html_attr(ctx->qry.search);
676 html("'/>\n"); 679 html("'/>\n");
677 html("<input type='submit' value='search'/>\n"); 680 html("<input type='submit' value='search'/>\n");
678 html("</form>"); 681 html("</form>");
679 } 682 }
680 html("</td></tr></table>\n"); 683 html("</td></tr></table>\n");
681 html("<div class='content'>"); 684 html("<div class='content'>");
682} 685}
683 686
684void cgit_print_filemode(unsigned short mode) 687void cgit_print_filemode(unsigned short mode)
685{ 688{
686 if (S_ISDIR(mode)) 689 if (S_ISDIR(mode))
687 html("d"); 690 html("d");
688 else if (S_ISLNK(mode)) 691 else if (S_ISLNK(mode))
689 html("l"); 692 html("l");
690 else if (S_ISGITLINK(mode)) 693 else if (S_ISGITLINK(mode))
691 html("m"); 694 html("m");
692 else 695 else
693 html("-"); 696 html("-");
694 html_fileperm(mode >> 6); 697 html_fileperm(mode >> 6);
695 html_fileperm(mode >> 3); 698 html_fileperm(mode >> 3);
696 html_fileperm(mode); 699 html_fileperm(mode);
697} 700}
698 701
699void cgit_print_snapshot_links(const char *repo, const char *head, 702void cgit_print_snapshot_links(const char *repo, const char *head,
700 const char *hex, int snapshots) 703 const char *hex, int snapshots)
701{ 704{
702 const struct cgit_snapshot_format* f; 705 const struct cgit_snapshot_format* f;
703 char *filename; 706 char *filename;
704 707
705 for (f = cgit_snapshot_formats; f->suffix; f++) { 708 for (f = cgit_snapshot_formats; f->suffix; f++) {
706 if (!(snapshots & f->bit)) 709 if (!(snapshots & f->bit))
707 continue; 710 continue;
708 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 711 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
709 f->suffix); 712 f->suffix);
710 cgit_snapshot_link(filename, NULL, NULL, (char *)head, 713 cgit_snapshot_link(filename, NULL, NULL, (char *)head,
711 (char *)hex, filename); 714 (char *)hex, filename);
712 html("<br/>"); 715 html("<br/>");
713 } 716 }
714} 717}
diff --git a/ui-stats.c b/ui-stats.c
new file mode 100644
index 0000000..9150840
--- a/dev/null
+++ b/ui-stats.c
@@ -0,0 +1,380 @@
1#include "cgit.h"
2#include "html.h"
3#include <string-list.h>
4
5#define MONTHS 6
6
7struct Period {
8 const char code;
9 const char *name;
10 int max_periods;
11 int count;
12
13 /* Convert a tm value to the first day in the period */
14 void (*trunc)(struct tm *tm);
15
16 /* Update tm value to start of next/previous period */
17 void (*dec)(struct tm *tm);
18 void (*inc)(struct tm *tm);
19
20 /* Pretty-print a tm value */
21 char *(*pretty)(struct tm *tm);
22};
23
24struct authorstat {
25 long total;
26 struct string_list list;
27};
28
29#define DAY_SECS (60 * 60 * 24)
30#define WEEK_SECS (DAY_SECS * 7)
31
32static void trunc_week(struct tm *tm)
33{
34 time_t t = timegm(tm);
35 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
36 gmtime_r(&t, tm);
37}
38
39static void dec_week(struct tm *tm)
40{
41 time_t t = timegm(tm);
42 t -= WEEK_SECS;
43 gmtime_r(&t, tm);
44}
45
46static void inc_week(struct tm *tm)
47{
48 time_t t = timegm(tm);
49 t += WEEK_SECS;
50 gmtime_r(&t, tm);
51}
52
53static char *pretty_week(struct tm *tm)
54{
55 static char buf[10];
56
57 strftime(buf, sizeof(buf), "W%V %G", tm);
58 return buf;
59}
60
61static void trunc_month(struct tm *tm)
62{
63 tm->tm_mday = 1;
64}
65
66static void dec_month(struct tm *tm)
67{
68 tm->tm_mon--;
69 if (tm->tm_mon < 0) {
70 tm->tm_year--;
71 tm->tm_mon = 11;
72 }
73}
74
75static void inc_month(struct tm *tm)
76{
77 tm->tm_mon++;
78 if (tm->tm_mon > 11) {
79 tm->tm_year++;
80 tm->tm_mon = 0;
81 }
82}
83
84static char *pretty_month(struct tm *tm)
85{
86 static const char *months[] = {
87 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
88 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
89 };
90 return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900);
91}
92
93static void trunc_quarter(struct tm *tm)
94{
95 trunc_month(tm);
96 while(tm->tm_mon % 3 != 0)
97 dec_month(tm);
98}
99
100static void dec_quarter(struct tm *tm)
101{
102 dec_month(tm);
103 dec_month(tm);
104 dec_month(tm);
105}
106
107static void inc_quarter(struct tm *tm)
108{
109 inc_month(tm);
110 inc_month(tm);
111 inc_month(tm);
112}
113
114static char *pretty_quarter(struct tm *tm)
115{
116 return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900);
117}
118
119static void trunc_year(struct tm *tm)
120{
121 trunc_month(tm);
122 tm->tm_mon = 0;
123}
124
125static void dec_year(struct tm *tm)
126{
127 tm->tm_year--;
128}
129
130static void inc_year(struct tm *tm)
131{
132 tm->tm_year++;
133}
134
135static char *pretty_year(struct tm *tm)
136{
137 return fmt("%d", tm->tm_year + 1900);
138}
139
140struct Period periods[] = {
141 {'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week},
142 {'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month},
143 {'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter},
144 {'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year},
145};
146
147static void add_commit(struct string_list *authors, struct commit *commit,
148 struct Period *period)
149{
150 struct commitinfo *info;
151 struct string_list_item *author, *item;
152 struct authorstat *authorstat;
153 struct string_list *items;
154 char *tmp;
155 struct tm *date;
156 time_t t;
157
158 info = cgit_parse_commit(commit);
159 tmp = xstrdup(info->author);
160 author = string_list_insert(tmp, authors);
161 if (!author->util)
162 author->util = xcalloc(1, sizeof(struct authorstat));
163 else
164 free(tmp);
165 authorstat = author->util;
166 items = &authorstat->list;
167 t = info->committer_date;
168 date = gmtime(&t);
169 period->trunc(date);
170 tmp = xstrdup(period->pretty(date));
171 item = string_list_insert(tmp, items);
172 if (item->util)
173 free(tmp);
174 item->util++;
175 authorstat->total++;
176 cgit_free_commitinfo(info);
177}
178
179static int cmp_total_commits(const void *a1, const void *a2)
180{
181 const struct string_list_item *i1 = a1;
182 const struct string_list_item *i2 = a2;
183 const struct authorstat *auth1 = i1->util;
184 const struct authorstat *auth2 = i2->util;
185
186 return auth2->total - auth1->total;
187}
188
189/* Walk the commit DAG and collect number of commits per author per
190 * timeperiod into a nested string_list collection.
191 */
192struct string_list collect_stats(struct cgit_context *ctx,
193 struct Period *period)
194{
195 struct string_list authors;
196 struct rev_info rev;
197 struct commit *commit;
198 const char *argv[] = {NULL, ctx->qry.head, NULL, NULL};
199 time_t now;
200 long i;
201 struct tm *tm;
202 char tmp[11];
203
204 time(&now);
205 tm = gmtime(&now);
206 period->trunc(tm);
207 for (i = 1; i < period->count; i++)
208 period->dec(tm);
209 strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm);
210 argv[2] = xstrdup(fmt("--since=%s", tmp));
211 init_revisions(&rev, NULL);
212 rev.abbrev = DEFAULT_ABBREV;
213 rev.commit_format = CMIT_FMT_DEFAULT;
214 rev.no_merges = 1;
215 rev.verbose_header = 1;
216 rev.show_root_diff = 0;
217 setup_revisions(3, argv, &rev, NULL);
218 prepare_revision_walk(&rev);
219 memset(&authors, 0, sizeof(authors));
220 while ((commit = get_revision(&rev)) != NULL) {
221 add_commit(&authors, commit, period);
222 free(commit->buffer);
223 free_commit_list(commit->parents);
224 }
225 return authors;
226}
227
228void print_combined_authorrow(struct string_list *authors, int from, int to,
229 const char *name, const char *leftclass, const char *centerclass,
230 const char *rightclass, struct Period *period)
231{
232 struct string_list_item *author;
233 struct authorstat *authorstat;
234 struct string_list *items;
235 struct string_list_item *date;
236 time_t now;
237 long i, j, total, subtotal;
238 struct tm *tm;
239 char *tmp;
240
241 time(&now);
242 tm = gmtime(&now);
243 period->trunc(tm);
244 for (i = 1; i < period->count; i++)
245 period->dec(tm);
246
247 total = 0;
248 htmlf("<tr><td class='%s'>%s</td>", leftclass,
249 fmt(name, to - from + 1));
250 for (j = 0; j < period->count; j++) {
251 tmp = period->pretty(tm);
252 period->inc(tm);
253 subtotal = 0;
254 for (i = from; i <= to; i++) {
255 author = &authors->items[i];
256 authorstat = author->util;
257 items = &authorstat->list;
258 date = string_list_lookup(tmp, items);
259 if (date)
260 subtotal += (size_t)date->util;
261 }
262 htmlf("<td class='%s'>%d</td>", centerclass, subtotal);
263 total += subtotal;
264 }
265 htmlf("<td class='%s'>%d</td></tr>", rightclass, total);
266}
267
268void print_authors(struct string_list *authors, int top, struct Period *period)
269{
270 struct string_list_item *author;
271 struct authorstat *authorstat;
272 struct string_list *items;
273 struct string_list_item *date;
274 time_t now;
275 long i, j, total;
276 struct tm *tm;
277 char *tmp;
278
279 time(&now);
280 tm = gmtime(&now);
281 period->trunc(tm);
282 for (i = 1; i < period->count; i++)
283 period->dec(tm);
284
285 html("<table class='stats'><tr><th>Author</th>");
286 for (j = 0; j < period->count; j++) {
287 tmp = period->pretty(tm);
288 htmlf("<th>%s</th>", tmp);
289 period->inc(tm);
290 }
291 html("<th>Total</th></tr>\n");
292
293 if (top <= 0 || top > authors->nr)
294 top = authors->nr;
295
296 for (i = 0; i < top; i++) {
297 author = &authors->items[i];
298 html("<tr><td class='left'>");
299 html_txt(author->string);
300 html("</td>");
301 authorstat = author->util;
302 items = &authorstat->list;
303 total = 0;
304 for (j = 0; j < period->count; j++)
305 period->dec(tm);
306 for (j = 0; j < period->count; j++) {
307 tmp = period->pretty(tm);
308 period->inc(tm);
309 date = string_list_lookup(tmp, items);
310 if (!date)
311 html("<td>0</td>");
312 else {
313 htmlf("<td>%d</td>", date->util);
314 total += (size_t)date->util;
315 }
316 }
317 htmlf("<td class='sum'>%d</td></tr>", total);
318 }
319
320 if (top < authors->nr)
321 print_combined_authorrow(authors, top, authors->nr - 1,
322 "Others (%d)", "left", "", "sum", period);
323
324 print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
325 "total", "sum", "sum", period);
326 html("</table>");
327}
328
329/* Create a sorted string_list with one entry per author. The util-field
330 * for each author is another string_list which is used to calculate the
331 * number of commits per time-interval.
332 */
333void cgit_show_stats(struct cgit_context *ctx)
334{
335 struct string_list authors;
336 struct Period *period;
337 int top, i;
338
339 period = &periods[0];
340 if (ctx->qry.period) {
341 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
342 if (periods[i].code == ctx->qry.period[0]) {
343 period = &periods[i];
344 break;
345 }
346 }
347 authors = collect_stats(ctx, period);
348 qsort(authors.items, authors.nr, sizeof(struct string_list_item),
349 cmp_total_commits);
350
351 top = ctx->qry.ofs;
352 if (!top)
353 top = 10;
354 htmlf("<h2>Commits per author per %s</h2>", period->name);
355
356 html("<form method='get' action='.' style='float: right; text-align: right;'>");
357 if (strcmp(ctx->qry.head, ctx->repo->defbranch))
358 htmlf("<input type='hidden' name='h' value='%s'/>", ctx->qry.head);
359 html("Period: ");
360 html("<select name='period' onchange='this.form.submit();'>");
361 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
362 htmlf("<option value='%c'%s>%s</option>",
363 periods[i].code,
364 period == &periods[i] ? " selected" : "",
365 periods[i].name);
366 html("</select><br/><br/>");
367 html("Authors: ");
368 html("");
369 html("<select name='ofs' onchange='this.form.submit();'>");
370 htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : "");
371 htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : "");
372 htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : "");
373 htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : "");
374 htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : "");
375 html("</select>");
376 html("<noscript>&nbsp;&nbsp;<input type='submit' value='Reload'/></noscript>");
377 html("</form>");
378 print_authors(&authors, top, period);
379}
380
diff --git a/ui-stats.h b/ui-stats.h
new file mode 100644
index 0000000..f1d744c
--- a/dev/null
+++ b/ui-stats.h
@@ -0,0 +1,8 @@
1#ifndef UI_STATS_H
2#define UI_STATS_H
3
4#include "cgit.h"
5
6extern void cgit_show_stats(struct cgit_context *ctx);
7
8#endif /* UI_STATS_H */