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
@@ -23,96 +23,97 @@ ifneq ($(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
diff --git a/cgit.c b/cgit.c
index c82587b..22b6d7c 100644
--- a/cgit.c
+++ b/cgit.c
@@ -9,196 +9,202 @@
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)
diff --git a/cgit.css b/cgit.css
index a37d218..ef30fbf 100644
--- a/cgit.css
+++ b/cgit.css
@@ -411,48 +411,125 @@ a.secondary {
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
@@ -16,186 +16,189 @@
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
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 7887b02..60d3ea4 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -29,96 +29,100 @@ cache-root
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
@@ -173,96 +177,100 @@ snapshots
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
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
@@ -13,96 +13,97 @@ struct 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';
diff --git a/ui-shared.c b/ui-shared.c
index 224e5f3..0e688a0 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -596,96 +596,99 @@ void cgit_print_pageheader(struct cgit_context *ctx)
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");
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 */