summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c5
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt4
-rw-r--r--ui-commit.c11
-rw-r--r--ui-diff.c22
-rw-r--r--ui-log.c4
-rw-r--r--ui-refs.c2
-rw-r--r--ui-shared.c34
-rw-r--r--ui-shared.h5
9 files changed, 73 insertions, 16 deletions
diff --git a/cgit.c b/cgit.c
index bd37788..ff678fb 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,665 +1,670 @@
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 "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
41} 41}
42 42
43static void process_cached_repolist(const char *path); 43static void process_cached_repolist(const char *path);
44 44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 46{
47 if (!strcmp(name, "name")) 47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "max-stats")) 63 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 64 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 65 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 66 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 67 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 68 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 69 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/') 70 if (*value == '/')
71 ctx.repo->readme = xstrdup(value); 71 ctx.repo->readme = xstrdup(value);
72 else 72 else
73 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 73 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) { 74 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter")) 75 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0); 76 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter")) 77 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0); 78 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter")) 79 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1); 80 repo->source_filter = new_filter(value, 1);
81 } 81 }
82} 82}
83 83
84void config_cb(const char *name, const char *value) 84void config_cb(const char *name, const char *value)
85{ 85{
86 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 86 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value); 87 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url")) 88 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value); 89 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path")) 90 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/'); 91 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo.")) 92 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value); 93 repo_config(ctx.repo, name + 5, value);
94 else if (!strcmp(name, "root-title")) 94 else if (!strcmp(name, "root-title"))
95 ctx.cfg.root_title = xstrdup(value); 95 ctx.cfg.root_title = xstrdup(value);
96 else if (!strcmp(name, "root-desc")) 96 else if (!strcmp(name, "root-desc"))
97 ctx.cfg.root_desc = xstrdup(value); 97 ctx.cfg.root_desc = xstrdup(value);
98 else if (!strcmp(name, "root-readme")) 98 else if (!strcmp(name, "root-readme"))
99 ctx.cfg.root_readme = xstrdup(value); 99 ctx.cfg.root_readme = xstrdup(value);
100 else if (!strcmp(name, "css")) 100 else if (!strcmp(name, "css"))
101 ctx.cfg.css = xstrdup(value); 101 ctx.cfg.css = xstrdup(value);
102 else if (!strcmp(name, "favicon")) 102 else if (!strcmp(name, "favicon"))
103 ctx.cfg.favicon = xstrdup(value); 103 ctx.cfg.favicon = xstrdup(value);
104 else if (!strcmp(name, "footer")) 104 else if (!strcmp(name, "footer"))
105 ctx.cfg.footer = xstrdup(value); 105 ctx.cfg.footer = xstrdup(value);
106 else if (!strcmp(name, "head-include")) 106 else if (!strcmp(name, "head-include"))
107 ctx.cfg.head_include = xstrdup(value); 107 ctx.cfg.head_include = xstrdup(value);
108 else if (!strcmp(name, "header")) 108 else if (!strcmp(name, "header"))
109 ctx.cfg.header = xstrdup(value); 109 ctx.cfg.header = xstrdup(value);
110 else if (!strcmp(name, "logo")) 110 else if (!strcmp(name, "logo"))
111 ctx.cfg.logo = xstrdup(value); 111 ctx.cfg.logo = xstrdup(value);
112 else if (!strcmp(name, "index-header")) 112 else if (!strcmp(name, "index-header"))
113 ctx.cfg.index_header = xstrdup(value); 113 ctx.cfg.index_header = xstrdup(value);
114 else if (!strcmp(name, "index-info")) 114 else if (!strcmp(name, "index-info"))
115 ctx.cfg.index_info = xstrdup(value); 115 ctx.cfg.index_info = xstrdup(value);
116 else if (!strcmp(name, "logo-link")) 116 else if (!strcmp(name, "logo-link"))
117 ctx.cfg.logo_link = xstrdup(value); 117 ctx.cfg.logo_link = xstrdup(value);
118 else if (!strcmp(name, "module-link")) 118 else if (!strcmp(name, "module-link"))
119 ctx.cfg.module_link = xstrdup(value); 119 ctx.cfg.module_link = xstrdup(value);
120 else if (!strcmp(name, "virtual-root")) { 120 else if (!strcmp(name, "virtual-root")) {
121 ctx.cfg.virtual_root = trim_end(value, '/'); 121 ctx.cfg.virtual_root = trim_end(value, '/');
122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
123 ctx.cfg.virtual_root = ""; 123 ctx.cfg.virtual_root = "";
124 } else if (!strcmp(name, "nocache")) 124 } else if (!strcmp(name, "nocache"))
125 ctx.cfg.nocache = atoi(value); 125 ctx.cfg.nocache = atoi(value);
126 else if (!strcmp(name, "noplainemail")) 126 else if (!strcmp(name, "noplainemail"))
127 ctx.cfg.noplainemail = atoi(value); 127 ctx.cfg.noplainemail = atoi(value);
128 else if (!strcmp(name, "noheader")) 128 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value); 129 ctx.cfg.noheader = atoi(value);
130 else if (!strcmp(name, "snapshots")) 130 else if (!strcmp(name, "snapshots"))
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 132 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 133 ctx.cfg.enable_filter_overrides = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 134 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 135 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 136 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 137 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 138 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 139 ctx.cfg.enable_log_linecount = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 140 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 141 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 142 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 144 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 145 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 146 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 147 ctx.cfg.cache_root = xstrdup(value);
148 else if (!strcmp(name, "cache-root-ttl")) 148 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 149 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 150 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 151 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 152 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 153 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 154 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 155 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 156 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 157 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 158 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 159 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 160 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 161 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 162 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 163 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 164 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 165 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 166 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 167 ctx.cfg.max_repodesc_len = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 168 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 169 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 170 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 171 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path")) 172 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 173 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 174 process_cached_repolist(value);
175 else 175 else
176 scan_tree(value, repo_config); 176 scan_tree(value, repo_config);
177 else if (!strcmp(name, "source-filter")) 177 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 178 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 179 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 180 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 181 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 182 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 183 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 184 ctx.cfg.summary_tags = atoi(value);
185 else if (!strcmp(name, "side-by-side-diffs"))
186 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 187 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 188 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 189 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 190 ctx.cfg.renamelimit = atoi(value);
189 else if (!strcmp(name, "robots")) 191 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 192 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 193 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 194 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 195 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 196 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 197 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 198 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 199 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 200 parse_configfile(value, config_cb);
199} 201}
200 202
201static void querystring_cb(const char *name, const char *value) 203static void querystring_cb(const char *name, const char *value)
202{ 204{
203 if (!value) 205 if (!value)
204 value = ""; 206 value = "";
205 207
206 if (!strcmp(name,"r")) { 208 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 209 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 210 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 211 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 212 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 213 } else if (!strcmp(name, "url")) {
212 ctx.qry.url = xstrdup(value); 214 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 215 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 216 } else if (!strcmp(name, "qt")) {
215 ctx.qry.grep = xstrdup(value); 217 ctx.qry.grep = xstrdup(value);
216 } else if (!strcmp(name, "q")) { 218 } else if (!strcmp(name, "q")) {
217 ctx.qry.search = xstrdup(value); 219 ctx.qry.search = xstrdup(value);
218 } else if (!strcmp(name, "h")) { 220 } else if (!strcmp(name, "h")) {
219 ctx.qry.head = xstrdup(value); 221 ctx.qry.head = xstrdup(value);
220 ctx.qry.has_symref = 1; 222 ctx.qry.has_symref = 1;
221 } else if (!strcmp(name, "id")) { 223 } else if (!strcmp(name, "id")) {
222 ctx.qry.sha1 = xstrdup(value); 224 ctx.qry.sha1 = xstrdup(value);
223 ctx.qry.has_sha1 = 1; 225 ctx.qry.has_sha1 = 1;
224 } else if (!strcmp(name, "id2")) { 226 } else if (!strcmp(name, "id2")) {
225 ctx.qry.sha2 = xstrdup(value); 227 ctx.qry.sha2 = xstrdup(value);
226 ctx.qry.has_sha1 = 1; 228 ctx.qry.has_sha1 = 1;
227 } else if (!strcmp(name, "ofs")) { 229 } else if (!strcmp(name, "ofs")) {
228 ctx.qry.ofs = atoi(value); 230 ctx.qry.ofs = atoi(value);
229 } else if (!strcmp(name, "path")) { 231 } else if (!strcmp(name, "path")) {
230 ctx.qry.path = trim_end(value, '/'); 232 ctx.qry.path = trim_end(value, '/');
231 } else if (!strcmp(name, "name")) { 233 } else if (!strcmp(name, "name")) {
232 ctx.qry.name = xstrdup(value); 234 ctx.qry.name = xstrdup(value);
233 } else if (!strcmp(name, "mimetype")) { 235 } else if (!strcmp(name, "mimetype")) {
234 ctx.qry.mimetype = xstrdup(value); 236 ctx.qry.mimetype = xstrdup(value);
235 } else if (!strcmp(name, "s")){ 237 } else if (!strcmp(name, "s")){
236 ctx.qry.sort = xstrdup(value); 238 ctx.qry.sort = xstrdup(value);
237 } else if (!strcmp(name, "showmsg")) { 239 } else if (!strcmp(name, "showmsg")) {
238 ctx.qry.showmsg = atoi(value); 240 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 241 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 242 ctx.qry.period = xstrdup(value);
243 } else if (!strcmp(name, "ss")) {
244 ctx.qry.ssdiff = atoi(value);
241 } 245 }
242} 246}
243 247
244char *xstrdupn(const char *str) 248char *xstrdupn(const char *str)
245{ 249{
246 return (str ? xstrdup(str) : NULL); 250 return (str ? xstrdup(str) : NULL);
247} 251}
248 252
249static void prepare_context(struct cgit_context *ctx) 253static void prepare_context(struct cgit_context *ctx)
250{ 254{
251 memset(ctx, 0, sizeof(ctx)); 255 memset(ctx, 0, sizeof(ctx));
252 ctx->cfg.agefile = "info/web/last-modified"; 256 ctx->cfg.agefile = "info/web/last-modified";
253 ctx->cfg.nocache = 0; 257 ctx->cfg.nocache = 0;
254 ctx->cfg.cache_size = 0; 258 ctx->cfg.cache_size = 0;
255 ctx->cfg.cache_dynamic_ttl = 5; 259 ctx->cfg.cache_dynamic_ttl = 5;
256 ctx->cfg.cache_max_create_time = 5; 260 ctx->cfg.cache_max_create_time = 5;
257 ctx->cfg.cache_repo_ttl = 5; 261 ctx->cfg.cache_repo_ttl = 5;
258 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 262 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
259 ctx->cfg.cache_root_ttl = 5; 263 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15; 264 ctx->cfg.cache_scanrc_ttl = 15;
261 ctx->cfg.cache_static_ttl = -1; 265 ctx->cfg.cache_static_ttl = -1;
262 ctx->cfg.css = "/cgit.css"; 266 ctx->cfg.css = "/cgit.css";
263 ctx->cfg.logo = "/cgit.png"; 267 ctx->cfg.logo = "/cgit.png";
264 ctx->cfg.local_time = 0; 268 ctx->cfg.local_time = 0;
265 ctx->cfg.enable_tree_linenumbers = 1; 269 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 270 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 271 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 272 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 273 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 274 ctx->cfg.max_repodesc_len = 80;
271 ctx->cfg.max_stats = 0; 275 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 276 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
273 ctx->cfg.renamelimit = -1; 277 ctx->cfg.renamelimit = -1;
274 ctx->cfg.robots = "index, nofollow"; 278 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 279 ctx->cfg.root_title = "Git repository browser";
276 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 280 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
277 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 281 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = ""; 282 ctx->cfg.section = "";
279 ctx->cfg.summary_branches = 10; 283 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 284 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 285 ctx->cfg.summary_tags = 10;
286 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 287 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 288 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 289 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 290 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 291 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 292 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
288 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 293 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
289 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 294 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
290 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 295 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
291 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 296 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
292 ctx->page.mimetype = "text/html"; 297 ctx->page.mimetype = "text/html";
293 ctx->page.charset = PAGE_ENCODING; 298 ctx->page.charset = PAGE_ENCODING;
294 ctx->page.filename = NULL; 299 ctx->page.filename = NULL;
295 ctx->page.size = 0; 300 ctx->page.size = 0;
296 ctx->page.modified = time(NULL); 301 ctx->page.modified = time(NULL);
297 ctx->page.expires = ctx->page.modified; 302 ctx->page.expires = ctx->page.modified;
298 ctx->page.etag = NULL; 303 ctx->page.etag = NULL;
299 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 304 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
300 if (ctx->env.script_name) 305 if (ctx->env.script_name)
301 ctx->cfg.script_name = ctx->env.script_name; 306 ctx->cfg.script_name = ctx->env.script_name;
302 if (ctx->env.query_string) 307 if (ctx->env.query_string)
303 ctx->qry.raw = ctx->env.query_string; 308 ctx->qry.raw = ctx->env.query_string;
304 if (!ctx->env.cgit_config) 309 if (!ctx->env.cgit_config)
305 ctx->env.cgit_config = CGIT_CONFIG; 310 ctx->env.cgit_config = CGIT_CONFIG;
306} 311}
307 312
308struct refmatch { 313struct refmatch {
309 char *req_ref; 314 char *req_ref;
310 char *first_ref; 315 char *first_ref;
311 int match; 316 int match;
312}; 317};
313 318
314int find_current_ref(const char *refname, const unsigned char *sha1, 319int find_current_ref(const char *refname, const unsigned char *sha1,
315 int flags, void *cb_data) 320 int flags, void *cb_data)
316{ 321{
317 struct refmatch *info; 322 struct refmatch *info;
318 323
319 info = (struct refmatch *)cb_data; 324 info = (struct refmatch *)cb_data;
320 if (!strcmp(refname, info->req_ref)) 325 if (!strcmp(refname, info->req_ref))
321 info->match = 1; 326 info->match = 1;
322 if (!info->first_ref) 327 if (!info->first_ref)
323 info->first_ref = xstrdup(refname); 328 info->first_ref = xstrdup(refname);
324 return info->match; 329 return info->match;
325} 330}
326 331
327char *find_default_branch(struct cgit_repo *repo) 332char *find_default_branch(struct cgit_repo *repo)
328{ 333{
329 struct refmatch info; 334 struct refmatch info;
330 char *ref; 335 char *ref;
331 336
332 info.req_ref = repo->defbranch; 337 info.req_ref = repo->defbranch;
333 info.first_ref = NULL; 338 info.first_ref = NULL;
334 info.match = 0; 339 info.match = 0;
335 for_each_branch_ref(find_current_ref, &info); 340 for_each_branch_ref(find_current_ref, &info);
336 if (info.match) 341 if (info.match)
337 ref = info.req_ref; 342 ref = info.req_ref;
338 else 343 else
339 ref = info.first_ref; 344 ref = info.first_ref;
340 if (ref) 345 if (ref)
341 ref = xstrdup(ref); 346 ref = xstrdup(ref);
342 return ref; 347 return ref;
343} 348}
344 349
345static int prepare_repo_cmd(struct cgit_context *ctx) 350static int prepare_repo_cmd(struct cgit_context *ctx)
346{ 351{
347 char *tmp; 352 char *tmp;
348 unsigned char sha1[20]; 353 unsigned char sha1[20];
349 int nongit = 0; 354 int nongit = 0;
350 355
351 setenv("GIT_DIR", ctx->repo->path, 1); 356 setenv("GIT_DIR", ctx->repo->path, 1);
352 setup_git_directory_gently(&nongit); 357 setup_git_directory_gently(&nongit);
353 if (nongit) { 358 if (nongit) {
354 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 359 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
355 "config error"); 360 "config error");
356 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 361 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
357 ctx->repo = NULL; 362 ctx->repo = NULL;
358 cgit_print_http_headers(ctx); 363 cgit_print_http_headers(ctx);
359 cgit_print_docstart(ctx); 364 cgit_print_docstart(ctx);
360 cgit_print_pageheader(ctx); 365 cgit_print_pageheader(ctx);
361 cgit_print_error(tmp); 366 cgit_print_error(tmp);
362 cgit_print_docend(); 367 cgit_print_docend();
363 return 1; 368 return 1;
364 } 369 }
365 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 370 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
366 371
367 if (!ctx->qry.head) { 372 if (!ctx->qry.head) {
368 ctx->qry.nohead = 1; 373 ctx->qry.nohead = 1;
369 ctx->qry.head = find_default_branch(ctx->repo); 374 ctx->qry.head = find_default_branch(ctx->repo);
370 ctx->repo->defbranch = ctx->qry.head; 375 ctx->repo->defbranch = ctx->qry.head;
371 } 376 }
372 377
373 if (!ctx->qry.head) { 378 if (!ctx->qry.head) {
374 cgit_print_http_headers(ctx); 379 cgit_print_http_headers(ctx);
375 cgit_print_docstart(ctx); 380 cgit_print_docstart(ctx);
376 cgit_print_pageheader(ctx); 381 cgit_print_pageheader(ctx);
377 cgit_print_error("Repository seems to be empty"); 382 cgit_print_error("Repository seems to be empty");
378 cgit_print_docend(); 383 cgit_print_docend();
379 return 1; 384 return 1;
380 } 385 }
381 386
382 if (get_sha1(ctx->qry.head, sha1)) { 387 if (get_sha1(ctx->qry.head, sha1)) {
383 tmp = xstrdup(ctx->qry.head); 388 tmp = xstrdup(ctx->qry.head);
384 ctx->qry.head = ctx->repo->defbranch; 389 ctx->qry.head = ctx->repo->defbranch;
385 ctx->page.status = 404; 390 ctx->page.status = 404;
386 ctx->page.statusmsg = "not found"; 391 ctx->page.statusmsg = "not found";
387 cgit_print_http_headers(ctx); 392 cgit_print_http_headers(ctx);
388 cgit_print_docstart(ctx); 393 cgit_print_docstart(ctx);
389 cgit_print_pageheader(ctx); 394 cgit_print_pageheader(ctx);
390 cgit_print_error(fmt("Invalid branch: %s", tmp)); 395 cgit_print_error(fmt("Invalid branch: %s", tmp));
391 cgit_print_docend(); 396 cgit_print_docend();
392 return 1; 397 return 1;
393 } 398 }
394 return 0; 399 return 0;
395} 400}
396 401
397static void process_request(void *cbdata) 402static void process_request(void *cbdata)
398{ 403{
399 struct cgit_context *ctx = cbdata; 404 struct cgit_context *ctx = cbdata;
400 struct cgit_cmd *cmd; 405 struct cgit_cmd *cmd;
401 406
402 cmd = cgit_get_cmd(ctx); 407 cmd = cgit_get_cmd(ctx);
403 if (!cmd) { 408 if (!cmd) {
404 ctx->page.title = "cgit error"; 409 ctx->page.title = "cgit error";
405 cgit_print_http_headers(ctx); 410 cgit_print_http_headers(ctx);
406 cgit_print_docstart(ctx); 411 cgit_print_docstart(ctx);
407 cgit_print_pageheader(ctx); 412 cgit_print_pageheader(ctx);
408 cgit_print_error("Invalid request"); 413 cgit_print_error("Invalid request");
409 cgit_print_docend(); 414 cgit_print_docend();
410 return; 415 return;
411 } 416 }
412 417
413 if (cmd->want_repo && !ctx->repo) { 418 if (cmd->want_repo && !ctx->repo) {
414 cgit_print_http_headers(ctx); 419 cgit_print_http_headers(ctx);
415 cgit_print_docstart(ctx); 420 cgit_print_docstart(ctx);
416 cgit_print_pageheader(ctx); 421 cgit_print_pageheader(ctx);
417 cgit_print_error(fmt("No repository selected")); 422 cgit_print_error(fmt("No repository selected"));
418 cgit_print_docend(); 423 cgit_print_docend();
419 return; 424 return;
420 } 425 }
421 426
422 if (ctx->repo && prepare_repo_cmd(ctx)) 427 if (ctx->repo && prepare_repo_cmd(ctx))
423 return; 428 return;
424 429
425 if (cmd->want_layout) { 430 if (cmd->want_layout) {
426 cgit_print_http_headers(ctx); 431 cgit_print_http_headers(ctx);
427 cgit_print_docstart(ctx); 432 cgit_print_docstart(ctx);
428 cgit_print_pageheader(ctx); 433 cgit_print_pageheader(ctx);
429 } 434 }
430 435
431 cmd->fn(ctx); 436 cmd->fn(ctx);
432 437
433 if (cmd->want_layout) 438 if (cmd->want_layout)
434 cgit_print_docend(); 439 cgit_print_docend();
435} 440}
436 441
437int cmp_repos(const void *a, const void *b) 442int cmp_repos(const void *a, const void *b)
438{ 443{
439 const struct cgit_repo *ra = a, *rb = b; 444 const struct cgit_repo *ra = a, *rb = b;
440 return strcmp(ra->url, rb->url); 445 return strcmp(ra->url, rb->url);
441} 446}
442 447
443char *build_snapshot_setting(int bitmap) 448char *build_snapshot_setting(int bitmap)
444{ 449{
445 const struct cgit_snapshot_format *f; 450 const struct cgit_snapshot_format *f;
446 char *result = xstrdup(""); 451 char *result = xstrdup("");
447 char *tmp; 452 char *tmp;
448 int len; 453 int len;
449 454
450 for (f = cgit_snapshot_formats; f->suffix; f++) { 455 for (f = cgit_snapshot_formats; f->suffix; f++) {
451 if (f->bit & bitmap) { 456 if (f->bit & bitmap) {
452 tmp = result; 457 tmp = result;
453 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 458 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
454 free(tmp); 459 free(tmp);
455 } 460 }
456 } 461 }
457 len = strlen(result); 462 len = strlen(result);
458 if (len) 463 if (len)
459 result[len - 1] = '\0'; 464 result[len - 1] = '\0';
460 return result; 465 return result;
461} 466}
462 467
463char *get_first_line(char *txt) 468char *get_first_line(char *txt)
464{ 469{
465 char *t = xstrdup(txt); 470 char *t = xstrdup(txt);
466 char *p = strchr(t, '\n'); 471 char *p = strchr(t, '\n');
467 if (p) 472 if (p)
468 *p = '\0'; 473 *p = '\0';
469 return t; 474 return t;
470} 475}
471 476
472void print_repo(FILE *f, struct cgit_repo *repo) 477void print_repo(FILE *f, struct cgit_repo *repo)
473{ 478{
474 fprintf(f, "repo.url=%s\n", repo->url); 479 fprintf(f, "repo.url=%s\n", repo->url);
475 fprintf(f, "repo.name=%s\n", repo->name); 480 fprintf(f, "repo.name=%s\n", repo->name);
476 fprintf(f, "repo.path=%s\n", repo->path); 481 fprintf(f, "repo.path=%s\n", repo->path);
477 if (repo->owner) 482 if (repo->owner)
478 fprintf(f, "repo.owner=%s\n", repo->owner); 483 fprintf(f, "repo.owner=%s\n", repo->owner);
479 if (repo->desc) { 484 if (repo->desc) {
480 char *tmp = get_first_line(repo->desc); 485 char *tmp = get_first_line(repo->desc);
481 fprintf(f, "repo.desc=%s\n", tmp); 486 fprintf(f, "repo.desc=%s\n", tmp);
482 free(tmp); 487 free(tmp);
483 } 488 }
484 if (repo->readme) 489 if (repo->readme)
485 fprintf(f, "repo.readme=%s\n", repo->readme); 490 fprintf(f, "repo.readme=%s\n", repo->readme);
486 if (repo->defbranch) 491 if (repo->defbranch)
487 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 492 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
488 if (repo->module_link) 493 if (repo->module_link)
489 fprintf(f, "repo.module-link=%s\n", repo->module_link); 494 fprintf(f, "repo.module-link=%s\n", repo->module_link);
490 if (repo->section) 495 if (repo->section)
491 fprintf(f, "repo.section=%s\n", repo->section); 496 fprintf(f, "repo.section=%s\n", repo->section);
492 if (repo->clone_url) 497 if (repo->clone_url)
493 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 498 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
494 fprintf(f, "repo.enable-log-filecount=%d\n", 499 fprintf(f, "repo.enable-log-filecount=%d\n",
495 repo->enable_log_filecount); 500 repo->enable_log_filecount);
496 fprintf(f, "repo.enable-log-linecount=%d\n", 501 fprintf(f, "repo.enable-log-linecount=%d\n",
497 repo->enable_log_linecount); 502 repo->enable_log_linecount);
498 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 503 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
499 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 504 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
500 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 505 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
501 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 506 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
502 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 507 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
503 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 508 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
504 if (repo->snapshots != ctx.cfg.snapshots) { 509 if (repo->snapshots != ctx.cfg.snapshots) {
505 char *tmp = build_snapshot_setting(repo->snapshots); 510 char *tmp = build_snapshot_setting(repo->snapshots);
506 fprintf(f, "repo.snapshots=%s\n", tmp); 511 fprintf(f, "repo.snapshots=%s\n", tmp);
507 free(tmp); 512 free(tmp);
508 } 513 }
509 if (repo->max_stats != ctx.cfg.max_stats) 514 if (repo->max_stats != ctx.cfg.max_stats)
510 fprintf(f, "repo.max-stats=%s\n", 515 fprintf(f, "repo.max-stats=%s\n",
511 cgit_find_stats_periodname(repo->max_stats)); 516 cgit_find_stats_periodname(repo->max_stats));
512 fprintf(f, "\n"); 517 fprintf(f, "\n");
513} 518}
514 519
515void print_repolist(FILE *f, struct cgit_repolist *list, int start) 520void print_repolist(FILE *f, struct cgit_repolist *list, int start)
516{ 521{
517 int i; 522 int i;
518 523
519 for(i = start; i < list->count; i++) 524 for(i = start; i < list->count; i++)
520 print_repo(f, &list->repos[i]); 525 print_repo(f, &list->repos[i]);
521} 526}
522 527
523/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 528/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
524 * and return 0 on success. 529 * and return 0 on success.
525 */ 530 */
526static int generate_cached_repolist(const char *path, const char *cached_rc) 531static int generate_cached_repolist(const char *path, const char *cached_rc)
527{ 532{
528 char *locked_rc; 533 char *locked_rc;
529 int idx; 534 int idx;
530 FILE *f; 535 FILE *f;
531 536
532 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 537 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
533 f = fopen(locked_rc, "wx"); 538 f = fopen(locked_rc, "wx");
534 if (!f) { 539 if (!f) {
535 /* Inform about the error unless the lockfile already existed, 540 /* Inform about the error unless the lockfile already existed,
536 * since that only means we've got concurrent requests. 541 * since that only means we've got concurrent requests.
537 */ 542 */
538 if (errno != EEXIST) 543 if (errno != EEXIST)
539 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 544 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
540 locked_rc, strerror(errno), errno); 545 locked_rc, strerror(errno), errno);
541 return errno; 546 return errno;
542 } 547 }
543 idx = cgit_repolist.count; 548 idx = cgit_repolist.count;
544 scan_tree(path, repo_config); 549 scan_tree(path, repo_config);
545 print_repolist(f, &cgit_repolist, idx); 550 print_repolist(f, &cgit_repolist, idx);
546 if (rename(locked_rc, cached_rc)) 551 if (rename(locked_rc, cached_rc))
547 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 552 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
548 locked_rc, cached_rc, strerror(errno), errno); 553 locked_rc, cached_rc, strerror(errno), errno);
549 fclose(f); 554 fclose(f);
550 return 0; 555 return 0;
551} 556}
552 557
553static void process_cached_repolist(const char *path) 558static void process_cached_repolist(const char *path)
554{ 559{
555 struct stat st; 560 struct stat st;
556 char *cached_rc; 561 char *cached_rc;
557 time_t age; 562 time_t age;
558 563
559 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 564 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
560 hash_str(path))); 565 hash_str(path)));
561 566
562 if (stat(cached_rc, &st)) { 567 if (stat(cached_rc, &st)) {
563 /* Nothing is cached, we need to scan without forking. And 568 /* Nothing is cached, we need to scan without forking. And
564 * if we fail to generate a cached repolist, we need to 569 * if we fail to generate a cached repolist, we need to
565 * invoke scan_tree manually. 570 * invoke scan_tree manually.
566 */ 571 */
567 if (generate_cached_repolist(path, cached_rc)) 572 if (generate_cached_repolist(path, cached_rc))
568 scan_tree(path, repo_config); 573 scan_tree(path, repo_config);
569 return; 574 return;
570 } 575 }
571 576
572 parse_configfile(cached_rc, config_cb); 577 parse_configfile(cached_rc, config_cb);
573 578
574 /* If the cached configfile hasn't expired, lets exit now */ 579 /* If the cached configfile hasn't expired, lets exit now */
575 age = time(NULL) - st.st_mtime; 580 age = time(NULL) - st.st_mtime;
576 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 581 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
577 return; 582 return;
578 583
579 /* The cached repolist has been parsed, but it was old. So lets 584 /* The cached repolist has been parsed, but it was old. So lets
580 * rescan the specified path and generate a new cached repolist 585 * rescan the specified path and generate a new cached repolist
581 * in a child-process to avoid latency for the current request. 586 * in a child-process to avoid latency for the current request.
582 */ 587 */
583 if (fork()) 588 if (fork())
584 return; 589 return;
585 590
586 exit(generate_cached_repolist(path, cached_rc)); 591 exit(generate_cached_repolist(path, cached_rc));
587} 592}
588 593
589static void cgit_parse_args(int argc, const char **argv) 594static void cgit_parse_args(int argc, const char **argv)
590{ 595{
591 int i; 596 int i;
592 int scan = 0; 597 int scan = 0;
593 598
594 for (i = 1; i < argc; i++) { 599 for (i = 1; i < argc; i++) {
595 if (!strncmp(argv[i], "--cache=", 8)) { 600 if (!strncmp(argv[i], "--cache=", 8)) {
596 ctx.cfg.cache_root = xstrdup(argv[i]+8); 601 ctx.cfg.cache_root = xstrdup(argv[i]+8);
597 } 602 }
598 if (!strcmp(argv[i], "--nocache")) { 603 if (!strcmp(argv[i], "--nocache")) {
599 ctx.cfg.nocache = 1; 604 ctx.cfg.nocache = 1;
600 } 605 }
601 if (!strcmp(argv[i], "--nohttp")) { 606 if (!strcmp(argv[i], "--nohttp")) {
602 ctx.env.no_http = "1"; 607 ctx.env.no_http = "1";
603 } 608 }
604 if (!strncmp(argv[i], "--query=", 8)) { 609 if (!strncmp(argv[i], "--query=", 8)) {
605 ctx.qry.raw = xstrdup(argv[i]+8); 610 ctx.qry.raw = xstrdup(argv[i]+8);
606 } 611 }
607 if (!strncmp(argv[i], "--repo=", 7)) { 612 if (!strncmp(argv[i], "--repo=", 7)) {
608 ctx.qry.repo = xstrdup(argv[i]+7); 613 ctx.qry.repo = xstrdup(argv[i]+7);
609 } 614 }
610 if (!strncmp(argv[i], "--page=", 7)) { 615 if (!strncmp(argv[i], "--page=", 7)) {
611 ctx.qry.page = xstrdup(argv[i]+7); 616 ctx.qry.page = xstrdup(argv[i]+7);
612 } 617 }
613 if (!strncmp(argv[i], "--head=", 7)) { 618 if (!strncmp(argv[i], "--head=", 7)) {
614 ctx.qry.head = xstrdup(argv[i]+7); 619 ctx.qry.head = xstrdup(argv[i]+7);
615 ctx.qry.has_symref = 1; 620 ctx.qry.has_symref = 1;
616 } 621 }
617 if (!strncmp(argv[i], "--sha1=", 7)) { 622 if (!strncmp(argv[i], "--sha1=", 7)) {
618 ctx.qry.sha1 = xstrdup(argv[i]+7); 623 ctx.qry.sha1 = xstrdup(argv[i]+7);
619 ctx.qry.has_sha1 = 1; 624 ctx.qry.has_sha1 = 1;
620 } 625 }
621 if (!strncmp(argv[i], "--ofs=", 6)) { 626 if (!strncmp(argv[i], "--ofs=", 6)) {
622 ctx.qry.ofs = atoi(argv[i]+6); 627 ctx.qry.ofs = atoi(argv[i]+6);
623 } 628 }
624 if (!strncmp(argv[i], "--scan-tree=", 12) || 629 if (!strncmp(argv[i], "--scan-tree=", 12) ||
625 !strncmp(argv[i], "--scan-path=", 12)) { 630 !strncmp(argv[i], "--scan-path=", 12)) {
626 /* HACK: the global snapshot bitmask defines the 631 /* HACK: the global snapshot bitmask defines the
627 * set of allowed snapshot formats, but the config 632 * set of allowed snapshot formats, but the config
628 * file hasn't been parsed yet so the mask is 633 * file hasn't been parsed yet so the mask is
629 * currently 0. By setting all bits high before 634 * currently 0. By setting all bits high before
630 * scanning we make sure that any in-repo cgitrc 635 * scanning we make sure that any in-repo cgitrc
631 * snapshot setting is respected by scan_tree(). 636 * snapshot setting is respected by scan_tree().
632 * BTW: we assume that there'll never be more than 637 * BTW: we assume that there'll never be more than
633 * 255 different snapshot formats supported by cgit... 638 * 255 different snapshot formats supported by cgit...
634 */ 639 */
635 ctx.cfg.snapshots = 0xFF; 640 ctx.cfg.snapshots = 0xFF;
636 scan++; 641 scan++;
637 scan_tree(argv[i] + 12, repo_config); 642 scan_tree(argv[i] + 12, repo_config);
638 } 643 }
639 } 644 }
640 if (scan) { 645 if (scan) {
641 qsort(cgit_repolist.repos, cgit_repolist.count, 646 qsort(cgit_repolist.repos, cgit_repolist.count,
642 sizeof(struct cgit_repo), cmp_repos); 647 sizeof(struct cgit_repo), cmp_repos);
643 print_repolist(stdout, &cgit_repolist, 0); 648 print_repolist(stdout, &cgit_repolist, 0);
644 exit(0); 649 exit(0);
645 } 650 }
646} 651}
647 652
648static int calc_ttl() 653static int calc_ttl()
649{ 654{
650 if (!ctx.repo) 655 if (!ctx.repo)
651 return ctx.cfg.cache_root_ttl; 656 return ctx.cfg.cache_root_ttl;
652 657
653 if (!ctx.qry.page) 658 if (!ctx.qry.page)
654 return ctx.cfg.cache_repo_ttl; 659 return ctx.cfg.cache_repo_ttl;
655 660
656 if (ctx.qry.has_symref) 661 if (ctx.qry.has_symref)
657 return ctx.cfg.cache_dynamic_ttl; 662 return ctx.cfg.cache_dynamic_ttl;
658 663
659 if (ctx.qry.has_sha1) 664 if (ctx.qry.has_sha1)
660 return ctx.cfg.cache_static_ttl; 665 return ctx.cfg.cache_static_ttl;
661 666
662 return ctx.cfg.cache_repo_ttl; 667 return ctx.cfg.cache_repo_ttl;
663} 668}
664 669
665int main(int argc, const char **argv) 670int main(int argc, const char **argv)
diff --git a/cgit.h b/cgit.h
index 6c6c460..b7b0adb 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,294 +1,296 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int max_stats; 75 int max_stats;
76 time_t mtime; 76 time_t mtime;
77 struct cgit_filter *about_filter; 77 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 78 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 79 struct cgit_filter *source_filter;
80}; 80};
81 81
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 83 const char *value);
84 84
85struct cgit_repolist { 85struct cgit_repolist {
86 int length; 86 int length;
87 int count; 87 int count;
88 struct cgit_repo *repos; 88 struct cgit_repo *repos;
89}; 89};
90 90
91struct commitinfo { 91struct commitinfo {
92 struct commit *commit; 92 struct commit *commit;
93 char *author; 93 char *author;
94 char *author_email; 94 char *author_email;
95 unsigned long author_date; 95 unsigned long author_date;
96 char *committer; 96 char *committer;
97 char *committer_email; 97 char *committer_email;
98 unsigned long committer_date; 98 unsigned long committer_date;
99 char *subject; 99 char *subject;
100 char *msg; 100 char *msg;
101 char *msg_encoding; 101 char *msg_encoding;
102}; 102};
103 103
104struct taginfo { 104struct taginfo {
105 char *tagger; 105 char *tagger;
106 char *tagger_email; 106 char *tagger_email;
107 unsigned long tagger_date; 107 unsigned long tagger_date;
108 char *msg; 108 char *msg;
109}; 109};
110 110
111struct refinfo { 111struct refinfo {
112 const char *refname; 112 const char *refname;
113 struct object *object; 113 struct object *object;
114 union { 114 union {
115 struct taginfo *tag; 115 struct taginfo *tag;
116 struct commitinfo *commit; 116 struct commitinfo *commit;
117 }; 117 };
118}; 118};
119 119
120struct reflist { 120struct reflist {
121 struct refinfo **refs; 121 struct refinfo **refs;
122 int alloc; 122 int alloc;
123 int count; 123 int count;
124}; 124};
125 125
126struct cgit_query { 126struct cgit_query {
127 int has_symref; 127 int has_symref;
128 int has_sha1; 128 int has_sha1;
129 char *raw; 129 char *raw;
130 char *repo; 130 char *repo;
131 char *page; 131 char *page;
132 char *search; 132 char *search;
133 char *grep; 133 char *grep;
134 char *head; 134 char *head;
135 char *sha1; 135 char *sha1;
136 char *sha2; 136 char *sha2;
137 char *path; 137 char *path;
138 char *name; 138 char *name;
139 char *mimetype; 139 char *mimetype;
140 char *url; 140 char *url;
141 char *period; 141 char *period;
142 int ofs; 142 int ofs;
143 int nohead; 143 int nohead;
144 char *sort; 144 char *sort;
145 int showmsg; 145 int showmsg;
146 int ssdiff;
146}; 147};
147 148
148struct cgit_config { 149struct cgit_config {
149 char *agefile; 150 char *agefile;
150 char *cache_root; 151 char *cache_root;
151 char *clone_prefix; 152 char *clone_prefix;
152 char *css; 153 char *css;
153 char *favicon; 154 char *favicon;
154 char *footer; 155 char *footer;
155 char *head_include; 156 char *head_include;
156 char *header; 157 char *header;
157 char *index_header; 158 char *index_header;
158 char *index_info; 159 char *index_info;
159 char *logo; 160 char *logo;
160 char *logo_link; 161 char *logo_link;
161 char *module_link; 162 char *module_link;
162 char *robots; 163 char *robots;
163 char *root_title; 164 char *root_title;
164 char *root_desc; 165 char *root_desc;
165 char *root_readme; 166 char *root_readme;
166 char *script_name; 167 char *script_name;
167 char *section; 168 char *section;
168 char *virtual_root; 169 char *virtual_root;
169 int cache_size; 170 int cache_size;
170 int cache_dynamic_ttl; 171 int cache_dynamic_ttl;
171 int cache_max_create_time; 172 int cache_max_create_time;
172 int cache_repo_ttl; 173 int cache_repo_ttl;
173 int cache_root_ttl; 174 int cache_root_ttl;
174 int cache_scanrc_ttl; 175 int cache_scanrc_ttl;
175 int cache_static_ttl; 176 int cache_static_ttl;
176 int embedded; 177 int embedded;
177 int enable_filter_overrides; 178 int enable_filter_overrides;
178 int enable_index_links; 179 int enable_index_links;
179 int enable_log_filecount; 180 int enable_log_filecount;
180 int enable_log_linecount; 181 int enable_log_linecount;
181 int enable_tree_linenumbers; 182 int enable_tree_linenumbers;
182 int local_time; 183 int local_time;
183 int max_repo_count; 184 int max_repo_count;
184 int max_commit_count; 185 int max_commit_count;
185 int max_lock_attempts; 186 int max_lock_attempts;
186 int max_msg_len; 187 int max_msg_len;
187 int max_repodesc_len; 188 int max_repodesc_len;
188 int max_stats; 189 int max_stats;
189 int nocache; 190 int nocache;
190 int noplainemail; 191 int noplainemail;
191 int noheader; 192 int noheader;
192 int renamelimit; 193 int renamelimit;
193 int snapshots; 194 int snapshots;
194 int summary_branches; 195 int summary_branches;
195 int summary_log; 196 int summary_log;
196 int summary_tags; 197 int summary_tags;
198 int ssdiff;
197 struct string_list mimetypes; 199 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 200 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 201 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 202 struct cgit_filter *source_filter;
201}; 203};
202 204
203struct cgit_page { 205struct cgit_page {
204 time_t modified; 206 time_t modified;
205 time_t expires; 207 time_t expires;
206 size_t size; 208 size_t size;
207 char *mimetype; 209 char *mimetype;
208 char *charset; 210 char *charset;
209 char *filename; 211 char *filename;
210 char *etag; 212 char *etag;
211 char *title; 213 char *title;
212 int status; 214 int status;
213 char *statusmsg; 215 char *statusmsg;
214}; 216};
215 217
216struct cgit_environment { 218struct cgit_environment {
217 char *cgit_config; 219 char *cgit_config;
218 char *http_host; 220 char *http_host;
219 char *https; 221 char *https;
220 char *no_http; 222 char *no_http;
221 char *path_info; 223 char *path_info;
222 char *query_string; 224 char *query_string;
223 char *request_method; 225 char *request_method;
224 char *script_name; 226 char *script_name;
225 char *server_name; 227 char *server_name;
226 char *server_port; 228 char *server_port;
227}; 229};
228 230
229struct cgit_context { 231struct cgit_context {
230 struct cgit_environment env; 232 struct cgit_environment env;
231 struct cgit_query qry; 233 struct cgit_query qry;
232 struct cgit_config cfg; 234 struct cgit_config cfg;
233 struct cgit_repo *repo; 235 struct cgit_repo *repo;
234 struct cgit_page page; 236 struct cgit_page page;
235}; 237};
236 238
237struct cgit_snapshot_format { 239struct cgit_snapshot_format {
238 const char *suffix; 240 const char *suffix;
239 const char *mimetype; 241 const char *mimetype;
240 write_archive_fn_t write_func; 242 write_archive_fn_t write_func;
241 int bit; 243 int bit;
242}; 244};
243 245
244extern const char *cgit_version; 246extern const char *cgit_version;
245 247
246extern struct cgit_repolist cgit_repolist; 248extern struct cgit_repolist cgit_repolist;
247extern struct cgit_context ctx; 249extern struct cgit_context ctx;
248extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 250extern const struct cgit_snapshot_format cgit_snapshot_formats[];
249 251
250extern struct cgit_repo *cgit_add_repo(const char *url); 252extern struct cgit_repo *cgit_add_repo(const char *url);
251extern struct cgit_repo *cgit_get_repoinfo(const char *url); 253extern struct cgit_repo *cgit_get_repoinfo(const char *url);
252extern void cgit_repo_config_cb(const char *name, const char *value); 254extern void cgit_repo_config_cb(const char *name, const char *value);
253 255
254extern int chk_zero(int result, char *msg); 256extern int chk_zero(int result, char *msg);
255extern int chk_positive(int result, char *msg); 257extern int chk_positive(int result, char *msg);
256extern int chk_non_negative(int result, char *msg); 258extern int chk_non_negative(int result, char *msg);
257 259
258extern char *trim_end(const char *str, char c); 260extern char *trim_end(const char *str, char c);
259extern char *strlpart(char *txt, int maxlen); 261extern char *strlpart(char *txt, int maxlen);
260extern char *strrpart(char *txt, int maxlen); 262extern char *strrpart(char *txt, int maxlen);
261 263
262extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 264extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
263extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 265extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
264 int flags, void *cb_data); 266 int flags, void *cb_data);
265 267
266extern void *cgit_free_commitinfo(struct commitinfo *info); 268extern void *cgit_free_commitinfo(struct commitinfo *info);
267 269
268extern int cgit_diff_files(const unsigned char *old_sha1, 270extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 271 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 272 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 273 int *binary, linediff_fn fn);
272 274
273extern void cgit_diff_tree(const unsigned char *old_sha1, 275extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 276 const unsigned char *new_sha1,
275 filepair_fn fn, const char *prefix); 277 filepair_fn fn, const char *prefix);
276 278
277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 279extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
278 280
279extern char *fmt(const char *format,...); 281extern char *fmt(const char *format,...);
280 282
281extern struct commitinfo *cgit_parse_commit(struct commit *commit); 283extern struct commitinfo *cgit_parse_commit(struct commit *commit);
282extern struct taginfo *cgit_parse_tag(struct tag *tag); 284extern struct taginfo *cgit_parse_tag(struct tag *tag);
283extern void cgit_parse_url(const char *url); 285extern void cgit_parse_url(const char *url);
284 286
285extern const char *cgit_repobasename(const char *reponame); 287extern const char *cgit_repobasename(const char *reponame);
286 288
287extern int cgit_parse_snapshots_mask(const char *str); 289extern int cgit_parse_snapshots_mask(const char *str);
288 290
289extern int cgit_open_filter(struct cgit_filter *filter); 291extern int cgit_open_filter(struct cgit_filter *filter);
290extern int cgit_close_filter(struct cgit_filter *filter); 292extern int cgit_close_filter(struct cgit_filter *filter);
291 293
292extern int readfile(const char *path, char **buf, size_t *size); 294extern int readfile(const char *path, char **buf, size_t *size);
293 295
294#endif /* CGIT_H */ 296#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 4dc383d..252d546 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,498 +1,502 @@
1CGITRC(5) 1CGITRC(5)
2======== 2========
3 3
4 4
5NAME 5NAME
6---- 6----
7cgitrc - runtime configuration for cgit 7cgitrc - runtime configuration for cgit
8 8
9 9
10SYNOPSIS 10SYNOPSIS
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
17LOCATION 17LOCATION
18-------- 18--------
19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
20runtime, cgit will consult the environment variable CGIT_CONFIG and, if 20runtime, cgit will consult the environment variable CGIT_CONFIG and, if
21defined, use its value instead. 21defined, use its value instead.
22 22
23 23
24GLOBAL SETTINGS 24GLOBAL SETTINGS
25--------------- 25---------------
26about-filter:: 26about-filter::
27 Specifies a command which will be invoked to format the content of 27 Specifies a command which will be invoked to format the content of
28 about pages (both top-level and for each repository). The command will 28 about pages (both top-level and for each repository). The command will
29 get the content of the about-file on its STDIN, and the STDOUT from the 29 get the content of the about-file on its STDIN, and the STDOUT from the
30 command will be included verbatim on the about page. Default value: 30 command will be included verbatim on the about page. Default value:
31 none. 31 none.
32 32
33agefile:: 33agefile::
34 Specifies a path, relative to each repository path, which can be used 34 Specifies a path, relative to each repository path, which can be used
35 to specify the date and time of the youngest commit in the repository. 35 to specify the date and time of the youngest commit in the repository.
36 The first line in the file is used as input to the "parse_date" 36 The first line in the file is used as input to the "parse_date"
37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
38 hh:mm:ss". Default value: "info/web/last-modified". 38 hh:mm:ss". Default value: "info/web/last-modified".
39 39
40cache-root:: 40cache-root::
41 Path used to store the cgit cache entries. Default value: 41 Path used to store the cgit cache entries. Default value:
42 "/var/cache/cgit". 42 "/var/cache/cgit".
43 43
44cache-dynamic-ttl:: 44cache-dynamic-ttl::
45 Number which specifies the time-to-live, in minutes, for the cached 45 Number which specifies the time-to-live, in minutes, for the cached
46 version of repository pages accessed without a fixed SHA1. Default 46 version of repository pages accessed without a fixed SHA1. Default
47 value: "5". 47 value: "5".
48 48
49cache-repo-ttl:: 49cache-repo-ttl::
50 Number which specifies the time-to-live, in minutes, for the cached 50 Number which specifies the time-to-live, in minutes, for the cached
51 version of the repository summary page. Default value: "5". 51 version of the repository summary page. Default value: "5".
52 52
53cache-root-ttl:: 53cache-root-ttl::
54 Number which specifies the time-to-live, in minutes, for the cached 54 Number which specifies the time-to-live, in minutes, for the cached
55 version of the repository index page. Default value: "5". 55 version of the repository index page. Default value: "5".
56 56
57cache-scanrc-ttl:: 57cache-scanrc-ttl::
58 Number which specifies the time-to-live, in minutes, for the result 58 Number which specifies the time-to-live, in minutes, for the result
59 of scanning a path for git repositories. Default value: "15". 59 of scanning a path for git repositories. Default value: "15".
60 60
61cache-size:: 61cache-size::
62 The maximum number of entries in the cgit cache. Default value: "0" 62 The maximum number of entries in the cgit cache. Default value: "0"
63 (i.e. caching is disabled). 63 (i.e. caching is disabled).
64 64
65cache-static-ttl:: 65cache-static-ttl::
66 Number which specifies the time-to-live, in minutes, for the cached 66 Number which specifies the time-to-live, in minutes, for the cached
67 version of repository pages accessed with a fixed SHA1. Default value: 67 version of repository pages accessed with a fixed SHA1. Default value:
68 "5". 68 "5".
69 69
70clone-prefix:: 70clone-prefix::
71 Space-separated list of common prefixes which, when combined with a 71 Space-separated list of common prefixes which, when combined with a
72 repository url, generates valid clone urls for the repository. This 72 repository url, generates valid clone urls for the repository. This
73 setting is only used if `repo.clone-url` is unspecified. Default value: 73 setting is only used if `repo.clone-url` is unspecified. Default value:
74 none. 74 none.
75 75
76commit-filter:: 76commit-filter::
77 Specifies a command which will be invoked to format commit messages. 77 Specifies a command which will be invoked to format commit messages.
78 The command will get the message on its STDIN, and the STDOUT from the 78 The command will get the message on its STDIN, and the STDOUT from the
79 command will be included verbatim as the commit message, i.e. this can 79 command will be included verbatim as the commit message, i.e. this can
80 be used to implement bugtracker integration. Default value: none. 80 be used to implement bugtracker integration. Default value: none.
81 81
82css:: 82css::
83 Url which specifies the css document to include in all cgit pages. 83 Url which specifies the css document to include in all cgit pages.
84 Default value: "/cgit.css". 84 Default value: "/cgit.css".
85 85
86embedded:: 86embedded::
87 Flag which, when set to "1", will make cgit generate a html fragment 87 Flag which, when set to "1", will make cgit generate a html fragment
88 suitable for embedding in other html pages. Default value: none. See 88 suitable for embedding in other html pages. Default value: none. See
89 also: "noheader". 89 also: "noheader".
90 90
91enable-filter-overrides:: 91enable-filter-overrides::
92 Flag which, when set to "1", allows all filter settings to be 92 Flag which, when set to "1", allows all filter settings to be
93 overridden in repository-specific cgitrc files. Default value: none. 93 overridden in repository-specific cgitrc files. Default value: none.
94 94
95enable-index-links:: 95enable-index-links::
96 Flag which, when set to "1", will make cgit generate extra links for 96 Flag which, when set to "1", will make cgit generate extra links for
97 each repo in the repository index (specifically, to the "summary", 97 each repo in the repository index (specifically, to the "summary",
98 "commit" and "tree" pages). Default value: "0". 98 "commit" and "tree" pages). Default value: "0".
99 99
100enable-log-filecount:: 100enable-log-filecount::
101 Flag which, when set to "1", will make cgit print the number of 101 Flag which, when set to "1", will make cgit print the number of
102 modified files for each commit on the repository log page. Default 102 modified files for each commit on the repository log page. Default
103 value: "0". 103 value: "0".
104 104
105enable-log-linecount:: 105enable-log-linecount::
106 Flag which, when set to "1", will make cgit print the number of added 106 Flag which, when set to "1", will make cgit print the number of added
107 and removed lines for each commit on the repository log page. Default 107 and removed lines for each commit on the repository log page. Default
108 value: "0". 108 value: "0".
109 109
110enable-tree-linenumbers:: 110enable-tree-linenumbers::
111 Flag which, when set to "1", will make cgit generate linenumber links 111 Flag which, when set to "1", will make cgit generate linenumber links
112 for plaintext blobs printed in the tree view. Default value: "1". 112 for plaintext blobs printed in the tree view. Default value: "1".
113 113
114favicon:: 114favicon::
115 Url used as link to a shortcut icon for cgit. If specified, it is 115 Url used as link to a shortcut icon for cgit. If specified, it is
116 suggested to use the value "/favicon.ico" since certain browsers will 116 suggested to use the value "/favicon.ico" since certain browsers will
117 ignore other values. Default value: none. 117 ignore other values. Default value: none.
118 118
119footer:: 119footer::
120 The content of the file specified with this option will be included 120 The content of the file specified with this option will be included
121 verbatim at the bottom of all pages (i.e. it replaces the standard 121 verbatim at the bottom of all pages (i.e. it replaces the standard
122 "generated by..." message. Default value: none. 122 "generated by..." message. Default value: none.
123 123
124head-include:: 124head-include::
125 The content of the file specified with this option will be included 125 The content of the file specified with this option will be included
126 verbatim in the html HEAD section on all pages. Default value: none. 126 verbatim in the html HEAD section on all pages. Default value: none.
127 127
128header:: 128header::
129 The content of the file specified with this option will be included 129 The content of the file specified with this option will be included
130 verbatim at the top of all pages. Default value: none. 130 verbatim at the top of all pages. Default value: none.
131 131
132include:: 132include::
133 Name of a configfile to include before the rest of the current config- 133 Name of a configfile to include before the rest of the current config-
134 file is parsed. Default value: none. 134 file is parsed. Default value: none.
135 135
136index-header:: 136index-header::
137 The content of the file specified with this option will be included 137 The content of the file specified with this option will be included
138 verbatim above the repository index. This setting is deprecated, and 138 verbatim above the repository index. This setting is deprecated, and
139 will not be supported by cgit-1.0 (use root-readme instead). Default 139 will not be supported by cgit-1.0 (use root-readme instead). Default
140 value: none. 140 value: none.
141 141
142index-info:: 142index-info::
143 The content of the file specified with this option will be included 143 The content of the file specified with this option will be included
144 verbatim below the heading on the repository index page. This setting 144 verbatim below the heading on the repository index page. This setting
145 is deprecated, and will not be supported by cgit-1.0 (use root-desc 145 is deprecated, and will not be supported by cgit-1.0 (use root-desc
146 instead). Default value: none. 146 instead). Default value: none.
147 147
148local-time:: 148local-time::
149 Flag which, if set to "1", makes cgit print commit and tag times in the 149 Flag which, if set to "1", makes cgit print commit and tag times in the
150 servers timezone. Default value: "0". 150 servers timezone. Default value: "0".
151 151
152logo:: 152logo::
153 Url which specifies the source of an image which will be used as a logo 153 Url which specifies the source of an image which will be used as a logo
154 on all cgit pages. Default value: "/cgit.png". 154 on all cgit pages. Default value: "/cgit.png".
155 155
156logo-link:: 156logo-link::
157 Url loaded when clicking on the cgit logo image. If unspecified the 157 Url loaded when clicking on the cgit logo image. If unspecified the
158 calculated url of the repository index page will be used. Default 158 calculated url of the repository index page will be used. Default
159 value: none. 159 value: none.
160 160
161max-commit-count:: 161max-commit-count::
162 Specifies the number of entries to list per page in "log" view. Default 162 Specifies the number of entries to list per page in "log" view. Default
163 value: "50". 163 value: "50".
164 164
165max-message-length:: 165max-message-length::
166 Specifies the maximum number of commit message characters to display in 166 Specifies the maximum number of commit message characters to display in
167 "log" view. Default value: "80". 167 "log" view. Default value: "80".
168 168
169max-repo-count:: 169max-repo-count::
170 Specifies the number of entries to list per page on therepository 170 Specifies the number of entries to list per page on therepository
171 index page. Default value: "50". 171 index page. Default value: "50".
172 172
173max-repodesc-length:: 173max-repodesc-length::
174 Specifies the maximum number of repo description characters to display 174 Specifies the maximum number of repo description characters to display
175 on the repository index page. Default value: "80". 175 on the repository index page. Default value: "80".
176 176
177max-stats:: 177max-stats::
178 Set the default maximum statistics period. Valid values are "week", 178 Set the default maximum statistics period. Valid values are "week",
179 "month", "quarter" and "year". If unspecified, statistics are 179 "month", "quarter" and "year". If unspecified, statistics are
180 disabled. Default value: none. See also: "repo.max-stats". 180 disabled. Default value: none. See also: "repo.max-stats".
181 181
182mimetype.<ext>:: 182mimetype.<ext>::
183 Set the mimetype for the specified filename extension. This is used 183 Set the mimetype for the specified filename extension. This is used
184 by the `plain` command when returning blob content. 184 by the `plain` command when returning blob content.
185 185
186module-link:: 186module-link::
187 Text which will be used as the formatstring for a hyperlink when a 187 Text which will be used as the formatstring for a hyperlink when a
188 submodule is printed in a directory listing. The arguments for the 188 submodule is printed in a directory listing. The arguments for the
189 formatstring are the path and SHA1 of the submodule commit. Default 189 formatstring are the path and SHA1 of the submodule commit. Default
190 value: "./?repo=%s&page=commit&id=%s" 190 value: "./?repo=%s&page=commit&id=%s"
191 191
192nocache:: 192nocache::
193 If set to the value "1" caching will be disabled. This settings is 193 If set to the value "1" caching will be disabled. This settings is
194 deprecated, and will not be honored starting with cgit-1.0. Default 194 deprecated, and will not be honored starting with cgit-1.0. Default
195 value: "0". 195 value: "0".
196 196
197noplainemail:: 197noplainemail::
198 If set to "1" showing full author email adresses will be disabled. 198 If set to "1" showing full author email adresses will be disabled.
199 Default value: "0". 199 Default value: "0".
200 200
201noheader:: 201noheader::
202 Flag which, when set to "1", will make cgit omit the standard header 202 Flag which, when set to "1", will make cgit omit the standard header
203 on all pages. Default value: none. See also: "embedded". 203 on all pages. Default value: none. See also: "embedded".
204 204
205renamelimit:: 205renamelimit::
206 Maximum number of files to consider when detecting renames. The value 206 Maximum number of files to consider when detecting renames. The value
207 "-1" uses the compiletime value in git (for further info, look at 207 "-1" uses the compiletime value in git (for further info, look at
208 `man git-diff`). Default value: "-1". 208 `man git-diff`). Default value: "-1".
209 209
210repo.group:: 210repo.group::
211 Legacy alias for "section". This option is deprecated and will not be 211 Legacy alias for "section". This option is deprecated and will not be
212 supported in cgit-1.0. 212 supported in cgit-1.0.
213 213
214robots:: 214robots::
215 Text used as content for the "robots" meta-tag. Default value: 215 Text used as content for the "robots" meta-tag. Default value:
216 "index, nofollow". 216 "index, nofollow".
217 217
218root-desc:: 218root-desc::
219 Text printed below the heading on the repository index page. Default 219 Text printed below the heading on the repository index page. Default
220 value: "a fast webinterface for the git dscm". 220 value: "a fast webinterface for the git dscm".
221 221
222root-readme:: 222root-readme::
223 The content of the file specified with this option will be included 223 The content of the file specified with this option will be included
224 verbatim below the "about" link on the repository index page. Default 224 verbatim below the "about" link on the repository index page. Default
225 value: none. 225 value: none.
226 226
227root-title:: 227root-title::
228 Text printed as heading on the repository index page. Default value: 228 Text printed as heading on the repository index page. Default value:
229 "Git Repository Browser". 229 "Git Repository Browser".
230 230
231scan-path:: 231scan-path::
232 A path which will be scanned for repositories. If caching is enabled, 232 A path which will be scanned for repositories. If caching is enabled,
233 the result will be cached as a cgitrc include-file in the cache 233 the result will be cached as a cgitrc include-file in the cache
234 directory. Default value: none. See also: cache-scanrc-ttl. 234 directory. Default value: none. See also: cache-scanrc-ttl.
235 235
236section:: 236section::
237 The name of the current repository section - all repositories defined 237 The name of the current repository section - all repositories defined
238 after this option will inherit the current section name. Default value: 238 after this option will inherit the current section name. Default value:
239 none. 239 none.
240 240
241side-by-side-diffs::
242 If set to "1" shows side-by-side diffs instead of unidiffs per
243 default. Default value: "0".
244
241snapshots:: 245snapshots::
242 Text which specifies the default set of snapshot formats generated by 246 Text which specifies the default set of snapshot formats generated by
243 cgit. The value is a space-separated list of zero or more of the 247 cgit. The value is a space-separated list of zero or more of the
244 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 248 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
245 249
246source-filter:: 250source-filter::
247 Specifies a command which will be invoked to format plaintext blobs 251 Specifies a command which will be invoked to format plaintext blobs
248 in the tree view. The command will get the blob content on its STDIN 252 in the tree view. The command will get the blob content on its STDIN
249 and the name of the blob as its only command line argument. The STDOUT 253 and the name of the blob as its only command line argument. The STDOUT
250 from the command will be included verbatim as the blob contents, i.e. 254 from the command will be included verbatim as the blob contents, i.e.
251 this can be used to implement e.g. syntax highlighting. Default value: 255 this can be used to implement e.g. syntax highlighting. Default value:
252 none. 256 none.
253 257
254summary-branches:: 258summary-branches::
255 Specifies the number of branches to display in the repository "summary" 259 Specifies the number of branches to display in the repository "summary"
256 view. Default value: "10". 260 view. Default value: "10".
257 261
258summary-log:: 262summary-log::
259 Specifies the number of log entries to display in the repository 263 Specifies the number of log entries to display in the repository
260 "summary" view. Default value: "10". 264 "summary" view. Default value: "10".
261 265
262summary-tags:: 266summary-tags::
263 Specifies the number of tags to display in the repository "summary" 267 Specifies the number of tags to display in the repository "summary"
264 view. Default value: "10". 268 view. Default value: "10".
265 269
266virtual-root:: 270virtual-root::
267 Url which, if specified, will be used as root for all cgit links. It 271 Url which, if specified, will be used as root for all cgit links. It
268 will also cause cgit to generate 'virtual urls', i.e. urls like 272 will also cause cgit to generate 'virtual urls', i.e. urls like
269 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 273 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
270 value: none. 274 value: none.
271 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 275 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
272 same kind of virtual urls, so this option will probably be deprecated. 276 same kind of virtual urls, so this option will probably be deprecated.
273 277
274REPOSITORY SETTINGS 278REPOSITORY SETTINGS
275------------------- 279-------------------
276repo.about-filter:: 280repo.about-filter::
277 Override the default about-filter. Default value: none. See also: 281 Override the default about-filter. Default value: none. See also:
278 "enable-filter-overrides". 282 "enable-filter-overrides".
279 283
280repo.clone-url:: 284repo.clone-url::
281 A list of space-separated urls which can be used to clone this repo. 285 A list of space-separated urls which can be used to clone this repo.
282 Default value: none. 286 Default value: none.
283 287
284repo.commit-filter:: 288repo.commit-filter::
285 Override the default commit-filter. Default value: none. See also: 289 Override the default commit-filter. Default value: none. See also:
286 "enable-filter-overrides". 290 "enable-filter-overrides".
287 291
288repo.defbranch:: 292repo.defbranch::
289 The name of the default branch for this repository. If no such branch 293 The name of the default branch for this repository. If no such branch
290 exists in the repository, the first branch name (when sorted) is used 294 exists in the repository, the first branch name (when sorted) is used
291 as default instead. Default value: "master". 295 as default instead. Default value: "master".
292 296
293repo.desc:: 297repo.desc::
294 The value to show as repository description. Default value: none. 298 The value to show as repository description. Default value: none.
295 299
296repo.enable-log-filecount:: 300repo.enable-log-filecount::
297 A flag which can be used to disable the global setting 301 A flag which can be used to disable the global setting
298 `enable-log-filecount'. Default value: none. 302 `enable-log-filecount'. Default value: none.
299 303
300repo.enable-log-linecount:: 304repo.enable-log-linecount::
301 A flag which can be used to disable the global setting 305 A flag which can be used to disable the global setting
302 `enable-log-linecount'. Default value: none. 306 `enable-log-linecount'. Default value: none.
303 307
304repo.max-stats:: 308repo.max-stats::
305 Override the default maximum statistics period. Valid values are equal 309 Override the default maximum statistics period. Valid values are equal
306 to the values specified for the global "max-stats" setting. Default 310 to the values specified for the global "max-stats" setting. Default
307 value: none. 311 value: none.
308 312
309repo.name:: 313repo.name::
310 The value to show as repository name. Default value: <repo.url>. 314 The value to show as repository name. Default value: <repo.url>.
311 315
312repo.owner:: 316repo.owner::
313 A value used to identify the owner of the repository. Default value: 317 A value used to identify the owner of the repository. Default value:
314 none. 318 none.
315 319
316repo.path:: 320repo.path::
317 An absolute path to the repository directory. For non-bare repositories 321 An absolute path to the repository directory. For non-bare repositories
318 this is the .git-directory. Default value: none. 322 this is the .git-directory. Default value: none.
319 323
320repo.readme:: 324repo.readme::
321 A path (relative to <repo.path>) which specifies a file to include 325 A path (relative to <repo.path>) which specifies a file to include
322 verbatim as the "About" page for this repo. Default value: none. 326 verbatim as the "About" page for this repo. Default value: none.
323 327
324repo.snapshots:: 328repo.snapshots::
325 A mask of allowed snapshot-formats for this repo, restricted by the 329 A mask of allowed snapshot-formats for this repo, restricted by the
326 "snapshots" global setting. Default value: <snapshots>. 330 "snapshots" global setting. Default value: <snapshots>.
327 331
328repo.section:: 332repo.section::
329 Override the current section name for this repository. Default value: 333 Override the current section name for this repository. Default value:
330 none. 334 none.
331 335
332repo.source-filter:: 336repo.source-filter::
333 Override the default source-filter. Default value: none. See also: 337 Override the default source-filter. Default value: none. See also:
334 "enable-filter-overrides". 338 "enable-filter-overrides".
335 339
336repo.url:: 340repo.url::
337 The relative url used to access the repository. This must be the first 341 The relative url used to access the repository. This must be the first
338 setting specified for each repo. Default value: none. 342 setting specified for each repo. Default value: none.
339 343
340 344
341REPOSITORY-SPECIFIC CGITRC FILE 345REPOSITORY-SPECIFIC CGITRC FILE
342------------------------------- 346-------------------------------
343When the option "scan-path" is used to auto-discover git repositories, cgit 347When the option "scan-path" is used to auto-discover git repositories, cgit
344will try to parse the file "cgitrc" within any found repository. Such a 348will try to parse the file "cgitrc" within any found repository. Such a
345repo-specific config file may contain any of the repo-specific options 349repo-specific config file may contain any of the repo-specific options
346described above, except "repo.url" and "repo.path". Additionally, the "filter" 350described above, except "repo.url" and "repo.path". Additionally, the "filter"
347options are only acknowledged in repo-specific config files when 351options are only acknowledged in repo-specific config files when
348"enable-filter-overrides" is set to "1". 352"enable-filter-overrides" is set to "1".
349 353
350Note: the "repo." prefix is dropped from the option names in repo-specific 354Note: the "repo." prefix is dropped from the option names in repo-specific
351config files, e.g. "repo.desc" becomes "desc". 355config files, e.g. "repo.desc" becomes "desc".
352 356
353 357
354EXAMPLE CGITRC FILE 358EXAMPLE CGITRC FILE
355------------------- 359-------------------
356 360
357.... 361....
358# Enable caching of up to 1000 output entriess 362# Enable caching of up to 1000 output entriess
359cache-size=1000 363cache-size=1000
360 364
361 365
362# Specify some default clone prefixes 366# Specify some default clone prefixes
363clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 367clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
364 368
365# Specify the css url 369# Specify the css url
366css=/css/cgit.css 370css=/css/cgit.css
367 371
368 372
369# Show extra links for each repository on the index page 373# Show extra links for each repository on the index page
370enable-index-links=1 374enable-index-links=1
371 375
372 376
373# Show number of affected files per commit on the log pages 377# Show number of affected files per commit on the log pages
374enable-log-filecount=1 378enable-log-filecount=1
375 379
376 380
377# Show number of added/removed lines per commit on the log pages 381# Show number of added/removed lines per commit on the log pages
378enable-log-linecount=1 382enable-log-linecount=1
379 383
380 384
381# Add a cgit favicon 385# Add a cgit favicon
382favicon=/favicon.ico 386favicon=/favicon.ico
383 387
384 388
385# Use a custom logo 389# Use a custom logo
386logo=/img/mylogo.png 390logo=/img/mylogo.png
387 391
388 392
389# Enable statistics per week, month and quarter 393# Enable statistics per week, month and quarter
390max-stats=quarter 394max-stats=quarter
391 395
392 396
393# Set the title and heading of the repository index page 397# Set the title and heading of the repository index page
394root-title=foobar.com git repositories 398root-title=foobar.com git repositories
395 399
396 400
397# Set a subheading for the repository index page 401# Set a subheading for the repository index page
398root-desc=tracking the foobar development 402root-desc=tracking the foobar development
399 403
400 404
401# Include some more info about foobar.com on the index page 405# Include some more info about foobar.com on the index page
402root-readme=/var/www/htdocs/about.html 406root-readme=/var/www/htdocs/about.html
403 407
404 408
405# Allow download of tar.gz, tar.bz2 and zip-files 409# Allow download of tar.gz, tar.bz2 and zip-files
406snapshots=tar.gz tar.bz2 zip 410snapshots=tar.gz tar.bz2 zip
407 411
408 412
409## 413##
410## List of common mimetypes 414## List of common mimetypes
411## 415##
412 416
413mimetype.git=image/git 417mimetype.git=image/git
414mimetype.html=text/html 418mimetype.html=text/html
415mimetype.jpg=image/jpeg 419mimetype.jpg=image/jpeg
416mimetype.jpeg=image/jpeg 420mimetype.jpeg=image/jpeg
417mimetype.pdf=application/pdf 421mimetype.pdf=application/pdf
418mimetype.png=image/png 422mimetype.png=image/png
419mimetype.svg=image/svg+xml 423mimetype.svg=image/svg+xml
420 424
421 425
422## 426##
423## List of repositories. 427## List of repositories.
424## PS: Any repositories listed when repo.group is unset will not be 428## PS: Any repositories listed when repo.group is unset will not be
425## displayed under a group heading 429## displayed under a group heading
426## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 430## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
427## and included like this: 431## and included like this:
428## include=/etc/cgitrepos 432## include=/etc/cgitrepos
429## 433##
430 434
431 435
432repo.url=foo 436repo.url=foo
433repo.path=/pub/git/foo.git 437repo.path=/pub/git/foo.git
434repo.desc=the master foo repository 438repo.desc=the master foo repository
435repo.owner=fooman@foobar.com 439repo.owner=fooman@foobar.com
436repo.readme=info/web/about.html 440repo.readme=info/web/about.html
437 441
438 442
439repo.url=bar 443repo.url=bar
440repo.path=/pub/git/bar.git 444repo.path=/pub/git/bar.git
441repo.desc=the bars for your foo 445repo.desc=the bars for your foo
442repo.owner=barman@foobar.com 446repo.owner=barman@foobar.com
443repo.readme=info/web/about.html 447repo.readme=info/web/about.html
444 448
445 449
446# The next repositories will be displayed under the 'extras' heading 450# The next repositories will be displayed under the 'extras' heading
447repo.group=extras 451repo.group=extras
448 452
449 453
450repo.url=baz 454repo.url=baz
451repo.path=/pub/git/baz.git 455repo.path=/pub/git/baz.git
452repo.desc=a set of extensions for bar users 456repo.desc=a set of extensions for bar users
453 457
454repo.url=wiz 458repo.url=wiz
455repo.path=/pub/git/wiz.git 459repo.path=/pub/git/wiz.git
456repo.desc=the wizard of foo 460repo.desc=the wizard of foo
457 461
458 462
459# Add some mirrored repositories 463# Add some mirrored repositories
460repo.group=mirrors 464repo.group=mirrors
461 465
462 466
463repo.url=git 467repo.url=git
464repo.path=/pub/git/git.git 468repo.path=/pub/git/git.git
465repo.desc=the dscm 469repo.desc=the dscm
466 470
467 471
468repo.url=linux 472repo.url=linux
469repo.path=/pub/git/linux.git 473repo.path=/pub/git/linux.git
470repo.desc=the kernel 474repo.desc=the kernel
471 475
472# Disable adhoc downloads of this repo 476# Disable adhoc downloads of this repo
473repo.snapshots=0 477repo.snapshots=0
474 478
475# Disable line-counts for this repo 479# Disable line-counts for this repo
476repo.enable-log-linecount=0 480repo.enable-log-linecount=0
477 481
478# Restrict the max statistics period for this repo 482# Restrict the max statistics period for this repo
479repo.max-stats=month 483repo.max-stats=month
480.... 484....
481 485
482 486
483BUGS 487BUGS
484---- 488----
485Comments currently cannot appear on the same line as a setting; the comment 489Comments currently cannot appear on the same line as a setting; the comment
486will be included as part of the value. E.g. this line: 490will be included as part of the value. E.g. this line:
487 491
488 robots=index # allow indexing 492 robots=index # allow indexing
489 493
490will generate the following html element: 494will generate the following html element:
491 495
492 <meta name='robots' content='index # allow indexing'/> 496 <meta name='robots' content='index # allow indexing'/>
493 497
494 498
495 499
496AUTHOR 500AUTHOR
497------ 501------
498Lars Hjemli <hjemli@gmail.com> 502Lars Hjemli <hjemli@gmail.com>
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..b5e3c01 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,118 +1,123 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1);
67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1);
64 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
69 html("</td></tr>\n"); 74 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 75 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 76 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 77 if (!parent) {
73 html("<tr><td colspan='3'>"); 78 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 79 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 80 html("</td></tr>");
76 continue; 81 continue;
77 } 82 }
78 html("<tr><th>parent</th>" 83 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 84 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 85 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 86 ctx.qry.head, sha1_to_hex(p->item->object.sha1), 0);
82 html(" ("); 87 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 88 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 89 sha1_to_hex(p->item->object.sha1), NULL, 0);
85 html(")</td></tr>"); 90 html(")</td></tr>");
86 parents++; 91 parents++;
87 } 92 }
88 if (ctx.repo->snapshots) { 93 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 94 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 95 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
91 hex, ctx.repo->snapshots); 96 hex, ctx.repo->snapshots);
92 html("</td></tr>"); 97 html("</td></tr>");
93 } 98 }
94 html("</table>\n"); 99 html("</table>\n");
95 html("<div class='commit-subject'>"); 100 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter) 101 if (ctx.repo->commit_filter)
97 cgit_open_filter(ctx.repo->commit_filter); 102 cgit_open_filter(ctx.repo->commit_filter);
98 html_txt(info->subject); 103 html_txt(info->subject);
99 if (ctx.repo->commit_filter) 104 if (ctx.repo->commit_filter)
100 cgit_close_filter(ctx.repo->commit_filter); 105 cgit_close_filter(ctx.repo->commit_filter);
101 show_commit_decorations(commit); 106 show_commit_decorations(commit);
102 html("</div>"); 107 html("</div>");
103 html("<div class='commit-msg'>"); 108 html("<div class='commit-msg'>");
104 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
106 html_txt(info->msg); 111 html_txt(info->msg);
107 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
109 html("</div>"); 114 html("</div>");
110 if (parents < 3) { 115 if (parents < 3) {
111 if (parents) 116 if (parents)
112 tmp = sha1_to_hex(commit->parents->item->object.sha1); 117 tmp = sha1_to_hex(commit->parents->item->object.sha1);
113 else 118 else
114 tmp = NULL; 119 tmp = NULL;
115 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 120 cgit_print_diff(ctx.qry.sha1, tmp, NULL);
116 } 121 }
117 cgit_free_commitinfo(info); 122 cgit_free_commitinfo(info);
118} 123}
diff --git a/ui-diff.c b/ui-diff.c
index 0c6f8d7..42e81ac 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,324 +1,342 @@
1/* ui-diff.c: show diff between two blobs 1/* ui-diff.c: show diff between two blobs
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-ssdiff.h" 12#include "ui-ssdiff.h"
13 13
14unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
15unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
16 16
17static int files, slots; 17static int files, slots;
18static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
19static int lines_added, lines_removed; 19static int lines_added, lines_removed;
20 20
21static struct fileinfo { 21static struct fileinfo {
22 char status; 22 char status;
23 unsigned char old_sha1[20]; 23 unsigned char old_sha1[20];
24 unsigned char new_sha1[20]; 24 unsigned char new_sha1[20];
25 unsigned short old_mode; 25 unsigned short old_mode;
26 unsigned short new_mode; 26 unsigned short new_mode;
27 char *old_path; 27 char *old_path;
28 char *new_path; 28 char *new_path;
29 unsigned int added; 29 unsigned int added;
30 unsigned int removed; 30 unsigned int removed;
31 unsigned long old_size; 31 unsigned long old_size;
32 unsigned long new_size; 32 unsigned long new_size;
33 int binary:1; 33 int binary:1;
34} *items; 34} *items;
35 35
36static int use_ssdiff = 0; 36static int use_ssdiff = 0;
37 37
38static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
39{ 39{
40 char *class; 40 char *class;
41 41
42 switch (info->status) { 42 switch (info->status) {
43 case DIFF_STATUS_ADDED: 43 case DIFF_STATUS_ADDED:
44 class = "add"; 44 class = "add";
45 break; 45 break;
46 case DIFF_STATUS_COPIED: 46 case DIFF_STATUS_COPIED:
47 class = "cpy"; 47 class = "cpy";
48 break; 48 break;
49 case DIFF_STATUS_DELETED: 49 case DIFF_STATUS_DELETED:
50 class = "del"; 50 class = "del";
51 break; 51 break;
52 case DIFF_STATUS_MODIFIED: 52 case DIFF_STATUS_MODIFIED:
53 class = "upd"; 53 class = "upd";
54 break; 54 break;
55 case DIFF_STATUS_RENAMED: 55 case DIFF_STATUS_RENAMED:
56 class = "mov"; 56 class = "mov";
57 break; 57 break;
58 case DIFF_STATUS_TYPE_CHANGED: 58 case DIFF_STATUS_TYPE_CHANGED:
59 class = "typ"; 59 class = "typ";
60 break; 60 break;
61 case DIFF_STATUS_UNKNOWN: 61 case DIFF_STATUS_UNKNOWN:
62 class = "unk"; 62 class = "unk";
63 break; 63 break;
64 case DIFF_STATUS_UNMERGED: 64 case DIFF_STATUS_UNMERGED:
65 class = "stg"; 65 class = "stg";
66 break; 66 break;
67 default: 67 default:
68 die("bug: unhandled diff status %c", info->status); 68 die("bug: unhandled diff status %c", info->status);
69 } 69 }
70 70
71 html("<tr>"); 71 html("<tr>");
72 htmlf("<td class='mode'>"); 72 htmlf("<td class='mode'>");
73 if (is_null_sha1(info->new_sha1)) { 73 if (is_null_sha1(info->new_sha1)) {
74 cgit_print_filemode(info->old_mode); 74 cgit_print_filemode(info->old_mode);
75 } else { 75 } else {
76 cgit_print_filemode(info->new_mode); 76 cgit_print_filemode(info->new_mode);
77 } 77 }
78 78
79 if (info->old_mode != info->new_mode && 79 if (info->old_mode != info->new_mode &&
80 !is_null_sha1(info->old_sha1) && 80 !is_null_sha1(info->old_sha1) &&
81 !is_null_sha1(info->new_sha1)) { 81 !is_null_sha1(info->new_sha1)) {
82 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
83 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
84 html("]</span>"); 84 html("]</span>");
85 } 85 }
86 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
88 ctx.qry.sha2, info->new_path); 88 ctx.qry.sha2, info->new_path, 0);
89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
90 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
92 info->old_path); 92 info->old_path);
93 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
94 if (info->binary) { 94 if (info->binary) {
95 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%d -> %d bytes",
96 info->old_size, info->new_size); 96 info->old_size, info->new_size);
97 return; 97 return;
98 } 98 }
99 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
100 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
102 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
103 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
104 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
105 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
106 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
107 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
108 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
109} 109}
110 110
111static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
112{ 112{
113 if (line && (len > 0)) { 113 if (line && (len > 0)) {
114 if (line[0] == '+') 114 if (line[0] == '+')
115 lines_added++; 115 lines_added++;
116 else if (line[0] == '-') 116 else if (line[0] == '-')
117 lines_removed++; 117 lines_removed++;
118 } 118 }
119} 119}
120 120
121static void inspect_filepair(struct diff_filepair *pair) 121static void inspect_filepair(struct diff_filepair *pair)
122{ 122{
123 int binary = 0; 123 int binary = 0;
124 unsigned long old_size = 0; 124 unsigned long old_size = 0;
125 unsigned long new_size = 0; 125 unsigned long new_size = 0;
126 files++; 126 files++;
127 lines_added = 0; 127 lines_added = 0;
128 lines_removed = 0; 128 lines_removed = 0;
129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
130 &binary, count_diff_lines); 130 &binary, count_diff_lines);
131 if (files >= slots) { 131 if (files >= slots) {
132 if (slots == 0) 132 if (slots == 0)
133 slots = 4; 133 slots = 4;
134 else 134 else
135 slots = slots * 2; 135 slots = slots * 2;
136 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
137 } 137 }
138 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
139 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
140 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
141 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
142 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
143 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
144 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
145 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
146 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
147 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
148 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
149 items[files-1].binary = binary; 149 items[files-1].binary = binary;
150 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
151 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
152 total_adds += lines_added; 152 total_adds += lines_added;
153 total_rems += lines_removed; 153 total_rems += lines_removed;
154} 154}
155 155
156void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
157 const unsigned char *new_sha1) 157 const unsigned char *new_sha1)
158{ 158{
159 int i; 159 int i;
160 160
161 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
163 ctx.qry.sha2, NULL); 163 ctx.qry.sha2, NULL, 0);
164 html("</div>"); 164 html("</div>");
165 html("<table summary='diffstat' class='diffstat'>"); 165 html("<table summary='diffstat' class='diffstat'>");
166 max_changes = 0; 166 max_changes = 0;
167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
168 for(i = 0; i<files; i++) 168 for(i = 0; i<files; i++)
169 print_fileinfo(&items[i]); 169 print_fileinfo(&items[i]);
170 html("</table>"); 170 html("</table>");
171 html("<div class='diffstat-summary'>"); 171 html("<div class='diffstat-summary'>");
172 htmlf("%d files changed, %d insertions, %d deletions", 172 htmlf("%d files changed, %d insertions, %d deletions",
173 files, total_adds, total_rems); 173 files, total_adds, total_rems);
174 html("</div>"); 174 html("</div>");
175} 175}
176 176
177 177
178/* 178/*
179 * print a single line returned from xdiff 179 * print a single line returned from xdiff
180 */ 180 */
181static void print_line(char *line, int len) 181static void print_line(char *line, int len)
182{ 182{
183 char *class = "ctx"; 183 char *class = "ctx";
184 char c = line[len-1]; 184 char c = line[len-1];
185 185
186 if (line[0] == '+') 186 if (line[0] == '+')
187 class = "add"; 187 class = "add";
188 else if (line[0] == '-') 188 else if (line[0] == '-')
189 class = "del"; 189 class = "del";
190 else if (line[0] == '@') 190 else if (line[0] == '@')
191 class = "hunk"; 191 class = "hunk";
192 192
193 htmlf("<div class='%s'>", class); 193 htmlf("<div class='%s'>", class);
194 line[len-1] = '\0'; 194 line[len-1] = '\0';
195 html_txt(line); 195 html_txt(line);
196 html("</div>"); 196 html("</div>");
197 line[len-1] = c; 197 line[len-1] = c;
198} 198}
199 199
200static void header(unsigned char *sha1, char *path1, int mode1, 200static void header(unsigned char *sha1, char *path1, int mode1,
201 unsigned char *sha2, char *path2, int mode2) 201 unsigned char *sha2, char *path2, int mode2)
202{ 202{
203 char *abbrev1, *abbrev2; 203 char *abbrev1, *abbrev2;
204 int subproject; 204 int subproject;
205 205
206 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 206 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
207 html("<div class='head'>"); 207 html("<div class='head'>");
208 html("diff --git a/"); 208 html("diff --git a/");
209 html_txt(path1); 209 html_txt(path1);
210 html(" b/"); 210 html(" b/");
211 html_txt(path2); 211 html_txt(path2);
212 212
213 if (is_null_sha1(sha1)) 213 if (is_null_sha1(sha1))
214 path1 = "dev/null"; 214 path1 = "dev/null";
215 if (is_null_sha1(sha2)) 215 if (is_null_sha1(sha2))
216 path2 = "dev/null"; 216 path2 = "dev/null";
217 217
218 if (mode1 == 0) 218 if (mode1 == 0)
219 htmlf("<br/>new file mode %.6o", mode2); 219 htmlf("<br/>new file mode %.6o", mode2);
220 220
221 if (mode2 == 0) 221 if (mode2 == 0)
222 htmlf("<br/>deleted file mode %.6o", mode1); 222 htmlf("<br/>deleted file mode %.6o", mode1);
223 223
224 if (!subproject) { 224 if (!subproject) {
225 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 225 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
226 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 226 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
227 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 227 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
228 free(abbrev1); 228 free(abbrev1);
229 free(abbrev2); 229 free(abbrev2);
230 if (mode1 != 0 && mode2 != 0) { 230 if (mode1 != 0 && mode2 != 0) {
231 htmlf(" %.6o", mode1); 231 htmlf(" %.6o", mode1);
232 if (mode2 != mode1) 232 if (mode2 != mode1)
233 htmlf("..%.6o", mode2); 233 htmlf("..%.6o", mode2);
234 } 234 }
235 html("<br/>--- a/"); 235 html("<br/>--- a/");
236 if (mode1 != 0) 236 if (mode1 != 0)
237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
238 sha1_to_hex(old_rev_sha1), path1); 238 sha1_to_hex(old_rev_sha1), path1);
239 else 239 else
240 html_txt(path1); 240 html_txt(path1);
241 html("<br/>+++ b/"); 241 html("<br/>+++ b/");
242 if (mode2 != 0) 242 if (mode2 != 0)
243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
244 sha1_to_hex(new_rev_sha1), path2); 244 sha1_to_hex(new_rev_sha1), path2);
245 else 245 else
246 html_txt(path2); 246 html_txt(path2);
247 } 247 }
248 html("</div>"); 248 html("</div>");
249 if (use_ssdiff) 249 if (use_ssdiff)
250 cgit_ssdiff_header(); 250 cgit_ssdiff_header();
251} 251}
252 252
253static void print_ssdiff_link()
254{
255 if (!strcmp(ctx.qry.page, "diff")) {
256 if (use_ssdiff)
257 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
258 ctx.qry.sha1, ctx.qry.sha2, NULL, 1);
259 else
260 cgit_diff_link("Side-by-side diff", NULL, NULL,
261 ctx.qry.head, ctx.qry.sha1,
262 ctx.qry.sha2, NULL, 1);
263 }
264}
265
253static void filepair_cb(struct diff_filepair *pair) 266static void filepair_cb(struct diff_filepair *pair)
254{ 267{
255 unsigned long old_size = 0; 268 unsigned long old_size = 0;
256 unsigned long new_size = 0; 269 unsigned long new_size = 0;
257 int binary = 0; 270 int binary = 0;
258 linediff_fn print_line_fn = print_line; 271 linediff_fn print_line_fn = print_line;
259 272
260 header(pair->one->sha1, pair->one->path, pair->one->mode, 273 header(pair->one->sha1, pair->one->path, pair->one->mode,
261 pair->two->sha1, pair->two->path, pair->two->mode); 274 pair->two->sha1, pair->two->path, pair->two->mode);
262 if (use_ssdiff) { 275 if (use_ssdiff) {
263 cgit_ssdiff_header(); 276 cgit_ssdiff_header();
264 print_line_fn = cgit_ssdiff_line_cb; 277 print_line_fn = cgit_ssdiff_line_cb;
265 } 278 }
266 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
267 if (S_ISGITLINK(pair->one->mode)) 280 if (S_ISGITLINK(pair->one->mode))
268 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 281 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
269 if (S_ISGITLINK(pair->two->mode)) 282 if (S_ISGITLINK(pair->two->mode))
270 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 283 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
271 return; 284 return;
272 } 285 }
273 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 286 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
274 &new_size, &binary, print_line_fn)) 287 &new_size, &binary, print_line_fn))
275 cgit_print_error("Error running diff"); 288 cgit_print_error("Error running diff");
276 if (binary) 289 if (binary)
277 html("Binary files differ"); 290 html("Binary files differ");
278 if (use_ssdiff) 291 if (use_ssdiff)
279 cgit_ssdiff_footer(); 292 cgit_ssdiff_footer();
280} 293}
281 294
282void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 295void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
283{ 296{
284 enum object_type type; 297 enum object_type type;
285 unsigned long size; 298 unsigned long size;
286 struct commit *commit, *commit2; 299 struct commit *commit, *commit2;
287 300
288 if (!new_rev) 301 if (!new_rev)
289 new_rev = ctx.qry.head; 302 new_rev = ctx.qry.head;
290 get_sha1(new_rev, new_rev_sha1); 303 get_sha1(new_rev, new_rev_sha1);
291 type = sha1_object_info(new_rev_sha1, &size); 304 type = sha1_object_info(new_rev_sha1, &size);
292 if (type == OBJ_BAD) { 305 if (type == OBJ_BAD) {
293 cgit_print_error(fmt("Bad object name: %s", new_rev)); 306 cgit_print_error(fmt("Bad object name: %s", new_rev));
294 return; 307 return;
295 } 308 }
296 commit = lookup_commit_reference(new_rev_sha1); 309 commit = lookup_commit_reference(new_rev_sha1);
297 if (!commit || parse_commit(commit)) 310 if (!commit || parse_commit(commit))
298 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 311 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
299 312
300 if (old_rev) 313 if (old_rev)
301 get_sha1(old_rev, old_rev_sha1); 314 get_sha1(old_rev, old_rev_sha1);
302 else if (commit->parents && commit->parents->item) 315 else if (commit->parents && commit->parents->item)
303 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 316 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
304 else 317 else
305 hashclr(old_rev_sha1); 318 hashclr(old_rev_sha1);
306 319
307 if (!is_null_sha1(old_rev_sha1)) { 320 if (!is_null_sha1(old_rev_sha1)) {
308 type = sha1_object_info(old_rev_sha1, &size); 321 type = sha1_object_info(old_rev_sha1, &size);
309 if (type == OBJ_BAD) { 322 if (type == OBJ_BAD) {
310 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 323 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
311 return; 324 return;
312 } 325 }
313 commit2 = lookup_commit_reference(old_rev_sha1); 326 commit2 = lookup_commit_reference(old_rev_sha1);
314 if (!commit2 || parse_commit(commit2)) 327 if (!commit2 || parse_commit(commit2))
315 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 328 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
316 } 329 }
330
331 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
332 use_ssdiff = 1;
333
334 print_ssdiff_link();
317 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 335 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
318 336
319 html("<table summary='diff' class='diff'>"); 337 html("<table summary='diff' class='diff'>");
320 html("<tr><td>"); 338 html("<tr><td>");
321 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 339 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
322 html("</td></tr>"); 340 html("</td></tr>");
323 html("</table>"); 341 html("</table>");
324} 342}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..0947604 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,234 +1,234 @@
1/* ui-log.c: functions for log output 1/* ui-log.c: functions for log output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13int files, add_lines, rem_lines; 13int files, add_lines, rem_lines;
14 14
15void count_lines(char *line, int size) 15void count_lines(char *line, int size)
16{ 16{
17 if (size <= 0) 17 if (size <= 0)
18 return; 18 return;
19 19
20 if (line[0] == '+') 20 if (line[0] == '+')
21 add_lines++; 21 add_lines++;
22 22
23 else if (line[0] == '-') 23 else if (line[0] == '-')
24 rem_lines++; 24 rem_lines++;
25} 25}
26 26
27void inspect_files(struct diff_filepair *pair) 27void inspect_files(struct diff_filepair *pair)
28{ 28{
29 unsigned long old_size = 0; 29 unsigned long old_size = 0;
30 unsigned long new_size = 0; 30 unsigned long new_size = 0;
31 int binary = 0; 31 int binary = 0;
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
40{ 40{
41 struct name_decoration *deco; 41 struct name_decoration *deco;
42 static char buf[1024]; 42 static char buf[1024];
43 43
44 buf[sizeof(buf) - 1] = 0; 44 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 0, NULL, NULL, ctx.qry.showmsg);
51 } 51 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 53 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 55 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 56 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 57 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 59 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 60 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 61 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 62 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 63 sha1_to_hex(commit->object.sha1), NULL,
64 0, NULL, NULL, ctx.qry.showmsg); 64 0, NULL, NULL, ctx.qry.showmsg);
65 } 65 }
66 else { 66 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 67 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 69 sha1_to_hex(commit->object.sha1), 0);
70 } 70 }
71 deco = deco->next; 71 deco = deco->next;
72 } 72 }
73} 73}
74 74
75void print_commit(struct commit *commit) 75void print_commit(struct commit *commit)
76{ 76{
77 struct commitinfo *info; 77 struct commitinfo *info;
78 char *tmp; 78 char *tmp;
79 int cols = 2; 79 int cols = 2;
80 80
81 info = cgit_parse_commit(commit); 81 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 82 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 83 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
86 html_link_open(tmp, NULL, NULL); 86 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 88 html_link_close();
89 htmlf("</td><td%s>", 89 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 90 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 92 sha1_to_hex(commit->object.sha1), 0);
93 show_commit_decorations(commit); 93 show_commit_decorations(commit);
94 html("</td><td>"); 94 html("</td><td>");
95 html_txt(info->author); 95 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 96 if (ctx.repo->enable_log_filecount) {
97 files = 0; 97 files = 0;
98 add_lines = 0; 98 add_lines = 0;
99 rem_lines = 0; 99 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 100 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 101 html("</td><td>");
102 htmlf("%d", files); 102 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 103 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 104 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 105 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 106 }
107 } 107 }
108 html("</td></tr>\n"); 108 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 109 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 110 if (ctx.repo->enable_log_filecount) {
111 cols++; 111 cols++;
112 if (ctx.repo->enable_log_linecount) 112 if (ctx.repo->enable_log_linecount)
113 cols++; 113 cols++;
114 } 114 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 116 cols);
117 html_txt(info->msg); 117 html_txt(info->msg);
118 html("</td></tr>\n"); 118 html("</td></tr>\n");
119 } 119 }
120 cgit_free_commitinfo(info); 120 cgit_free_commitinfo(info);
121} 121}
122 122
123static const char *disambiguate_ref(const char *ref) 123static const char *disambiguate_ref(const char *ref)
124{ 124{
125 unsigned char sha1[20]; 125 unsigned char sha1[20];
126 const char *longref; 126 const char *longref;
127 127
128 longref = fmt("refs/heads/%s", ref); 128 longref = fmt("refs/heads/%s", ref);
129 if (get_sha1(longref, sha1) == 0) 129 if (get_sha1(longref, sha1) == 0)
130 return longref; 130 return longref;
131 131
132 return ref; 132 return ref;
133} 133}
134 134
135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
136 char *path, int pager) 136 char *path, int pager)
137{ 137{
138 struct rev_info rev; 138 struct rev_info rev;
139 struct commit *commit; 139 struct commit *commit;
140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
141 int argc = 2; 141 int argc = 2;
142 int i, columns = 3; 142 int i, columns = 3;
143 143
144 if (!tip) 144 if (!tip)
145 tip = ctx.qry.head; 145 tip = ctx.qry.head;
146 146
147 argv[1] = disambiguate_ref(tip); 147 argv[1] = disambiguate_ref(tip);
148 148
149 if (grep && pattern && (!strcmp(grep, "grep") || 149 if (grep && pattern && (!strcmp(grep, "grep") ||
150 !strcmp(grep, "author") || 150 !strcmp(grep, "author") ||
151 !strcmp(grep, "committer"))) 151 !strcmp(grep, "committer")))
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 152 argv[argc++] = fmt("--%s=%s", grep, pattern);
153 153
154 if (path) { 154 if (path) {
155 argv[argc++] = "--"; 155 argv[argc++] = "--";
156 argv[argc++] = path; 156 argv[argc++] = path;
157 } 157 }
158 init_revisions(&rev, NULL); 158 init_revisions(&rev, NULL);
159 rev.abbrev = DEFAULT_ABBREV; 159 rev.abbrev = DEFAULT_ABBREV;
160 rev.commit_format = CMIT_FMT_DEFAULT; 160 rev.commit_format = CMIT_FMT_DEFAULT;
161 rev.verbose_header = 1; 161 rev.verbose_header = 1;
162 rev.show_root_diff = 0; 162 rev.show_root_diff = 0;
163 setup_revisions(argc, argv, &rev, NULL); 163 setup_revisions(argc, argv, &rev, NULL);
164 load_ref_decorations(DECORATE_FULL_REFS); 164 load_ref_decorations(DECORATE_FULL_REFS);
165 rev.show_decorations = 1; 165 rev.show_decorations = 1;
166 rev.grep_filter.regflags |= REG_ICASE; 166 rev.grep_filter.regflags |= REG_ICASE;
167 compile_grep_patterns(&rev.grep_filter); 167 compile_grep_patterns(&rev.grep_filter);
168 prepare_revision_walk(&rev); 168 prepare_revision_walk(&rev);
169 169
170 if (pager) 170 if (pager)
171 html("<table class='list nowrap'>"); 171 html("<table class='list nowrap'>");
172 172
173 html("<tr class='nohover'><th class='left'>Age</th>" 173 html("<tr class='nohover'><th class='left'>Age</th>"
174 "<th class='left'>Commit message"); 174 "<th class='left'>Commit message");
175 if (pager) { 175 if (pager) {
176 html(" ("); 176 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 178 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 181 html(")");
182 } 182 }
183 html("</th><th class='left'>Author</th>"); 183 html("</th><th class='left'>Author</th>");
184 if (ctx.repo->enable_log_filecount) { 184 if (ctx.repo->enable_log_filecount) {
185 html("<th class='left'>Files</th>"); 185 html("<th class='left'>Files</th>");
186 columns++; 186 columns++;
187 if (ctx.repo->enable_log_linecount) { 187 if (ctx.repo->enable_log_linecount) {
188 html("<th class='left'>Lines</th>"); 188 html("<th class='left'>Lines</th>");
189 columns++; 189 columns++;
190 } 190 }
191 } 191 }
192 html("</tr>\n"); 192 html("</tr>\n");
193 193
194 if (ofs<0) 194 if (ofs<0)
195 ofs = 0; 195 ofs = 0;
196 196
197 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 197 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
198 free(commit->buffer); 198 free(commit->buffer);
199 commit->buffer = NULL; 199 commit->buffer = NULL;
200 free_commit_list(commit->parents); 200 free_commit_list(commit->parents);
201 commit->parents = NULL; 201 commit->parents = NULL;
202 } 202 }
203 203
204 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 204 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
205 print_commit(commit); 205 print_commit(commit);
206 free(commit->buffer); 206 free(commit->buffer);
207 commit->buffer = NULL; 207 commit->buffer = NULL;
208 free_commit_list(commit->parents); 208 free_commit_list(commit->parents);
209 commit->parents = NULL; 209 commit->parents = NULL;
210 } 210 }
211 if (pager) { 211 if (pager) {
212 htmlf("</table><div class='pager'>", 212 htmlf("</table><div class='pager'>",
213 columns); 213 columns);
214 if (ofs > 0) { 214 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 216 ctx.qry.sha1, ctx.qry.path,
217 ofs - cnt, ctx.qry.grep, 217 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 218 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 219 html("&nbsp;");
220 } 220 }
221 if ((commit = get_revision(&rev)) != NULL) { 221 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 223 ctx.qry.sha1, ctx.qry.path,
224 ofs + cnt, ctx.qry.grep, 224 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 225 ctx.qry.search, ctx.qry.showmsg);
226 } 226 }
227 html("</div>"); 227 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 228 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 229 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0,
231 NULL, NULL, ctx.qry.showmsg); 231 NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 232 html("</td></tr>\n");
233 } 233 }
234} 234}
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..33d9bec 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,245 +1,245 @@
1/* ui-refs.c: browse symbolic refs 1/* ui-refs.c: browse symbolic refs
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13static int header; 13static int header;
14 14
15static int cmp_age(int age1, int age2) 15static int cmp_age(int age1, int age2)
16{ 16{
17 if (age1 != 0 && age2 != 0) 17 if (age1 != 0 && age2 != 0)
18 return age2 - age1; 18 return age2 - age1;
19 19
20 if (age1 == 0 && age2 == 0) 20 if (age1 == 0 && age2 == 0)
21 return 0; 21 return 0;
22 22
23 if (age1 == 0) 23 if (age1 == 0)
24 return +1; 24 return +1;
25 25
26 return -1; 26 return -1;
27} 27}
28 28
29static int cmp_ref_name(const void *a, const void *b) 29static int cmp_ref_name(const void *a, const void *b)
30{ 30{
31 struct refinfo *r1 = *(struct refinfo **)a; 31 struct refinfo *r1 = *(struct refinfo **)a;
32 struct refinfo *r2 = *(struct refinfo **)b; 32 struct refinfo *r2 = *(struct refinfo **)b;
33 33
34 return strcmp(r1->refname, r2->refname); 34 return strcmp(r1->refname, r2->refname);
35} 35}
36 36
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 int r1date, r2date; 49 int r1date, r2date;
50 50
51 if (r1->object->type != OBJ_COMMIT) 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date; 52 r1date = r1->tag->tagger_date;
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
102 char *filename; 102 char *filename;
103 const char *basename; 103 const char *basename;
104 104
105 if (!ref || strlen(ref) < 2) 105 if (!ref || strlen(ref) < 2)
106 return; 106 return;
107 107
108 basename = cgit_repobasename(repo->url); 108 basename = cgit_repobasename(repo->url);
109 if (prefixcmp(ref, basename) != 0) { 109 if (prefixcmp(ref, basename) != 0) {
110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
111 ref++; 111 ref++;
112 if (isdigit(ref[0])) 112 if (isdigit(ref[0]))
113 ref = xstrdup(fmt("%s-%s", basename, ref)); 113 ref = xstrdup(fmt("%s-%s", basename, ref));
114 } 114 }
115 115
116 for (f = cgit_snapshot_formats; f->suffix; f++) { 116 for (f = cgit_snapshot_formats; f->suffix; f++) {
117 if (!(repo->snapshots & f->bit)) 117 if (!(repo->snapshots & f->bit))
118 continue; 118 continue;
119 filename = fmt("%s%s", ref, f->suffix); 119 filename = fmt("%s%s", ref, f->suffix);
120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
121 html("&nbsp;&nbsp;"); 121 html("&nbsp;&nbsp;");
122 } 122 }
123} 123}
124static int print_tag(struct refinfo *ref) 124static int print_tag(struct refinfo *ref)
125{ 125{
126 struct tag *tag; 126 struct tag *tag;
127 struct taginfo *info; 127 struct taginfo *info;
128 char *name = (char *)ref->refname; 128 char *name = (char *)ref->refname;
129 129
130 if (ref->object->type == OBJ_TAG) { 130 if (ref->object->type == OBJ_TAG) {
131 tag = (struct tag *)ref->object; 131 tag = (struct tag *)ref->object;
132 info = ref->tag; 132 info = ref->tag;
133 if (!tag || !info) 133 if (!tag || !info)
134 return 1; 134 return 1;
135 html("<tr><td>"); 135 html("<tr><td>");
136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
137 html("</td><td>"); 137 html("</td><td>");
138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
139 print_tag_downloads(ctx.repo, name); 139 print_tag_downloads(ctx.repo, name);
140 else 140 else
141 cgit_object_link(tag->tagged); 141 cgit_object_link(tag->tagged);
142 html("</td><td>"); 142 html("</td><td>");
143 if (info->tagger) 143 if (info->tagger)
144 html(info->tagger); 144 html(info->tagger);
145 html("</td><td colspan='2'>"); 145 html("</td><td colspan='2'>");
146 if (info->tagger_date > 0) 146 if (info->tagger_date > 0)
147 cgit_print_age(info->tagger_date, -1, NULL); 147 cgit_print_age(info->tagger_date, -1, NULL);
148 html("</td></tr>\n"); 148 html("</td></tr>\n");
149 } else { 149 } else {
150 if (!header) 150 if (!header)
151 print_tag_header(); 151 print_tag_header();
152 html("<tr><td>"); 152 html("<tr><td>");
153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
154 html("</td><td>"); 154 html("</td><td>");
155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) 155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
156 print_tag_downloads(ctx.repo, name); 156 print_tag_downloads(ctx.repo, name);
157 else 157 else
158 cgit_object_link(ref->object); 158 cgit_object_link(ref->object);
159 html("</td><td>"); 159 html("</td><td>");
160 if (ref->object->type == OBJ_COMMIT) 160 if (ref->object->type == OBJ_COMMIT)
161 html(ref->commit->author); 161 html(ref->commit->author);
162 html("</td><td colspan='2'>"); 162 html("</td><td colspan='2'>");
163 if (ref->object->type == OBJ_COMMIT) 163 if (ref->object->type == OBJ_COMMIT)
164 cgit_print_age(ref->commit->commit->date, -1, NULL); 164 cgit_print_age(ref->commit->commit->date, -1, NULL);
165 html("</td></tr>\n"); 165 html("</td></tr>\n");
166 } 166 }
167 return 0; 167 return 0;
168} 168}
169 169
170static void print_refs_link(char *path) 170static void print_refs_link(char *path)
171{ 171{
172 html("<tr class='nohover'><td colspan='4'>"); 172 html("<tr class='nohover'><td colspan='4'>");
173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
174 html("</td></tr>"); 174 html("</td></tr>");
175} 175}
176 176
177void cgit_print_branches(int maxcount) 177void cgit_print_branches(int maxcount)
178{ 178{
179 struct reflist list; 179 struct reflist list;
180 int i; 180 int i;
181 181
182 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
183 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 190
191 if (maxcount == 0 || maxcount > list.count) 191 if (maxcount == 0 || maxcount > list.count)
192 maxcount = list.count; 192 maxcount = list.count;
193 193
194 if (maxcount < list.count) { 194 if (maxcount < list.count) {
195 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 195 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
196 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 196 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
197 } 197 }
198 198
199 for(i=0; i<maxcount; i++) 199 for(i=0; i<maxcount; i++)
200 print_branch(list.refs[i]); 200 print_branch(list.refs[i]);
201 201
202 if (maxcount < list.count) 202 if (maxcount < list.count)
203 print_refs_link("heads"); 203 print_refs_link("heads");
204} 204}
205 205
206void cgit_print_tags(int maxcount) 206void cgit_print_tags(int maxcount)
207{ 207{
208 struct reflist list; 208 struct reflist list;
209 int i; 209 int i;
210 210
211 header = 0; 211 header = 0;
212 list.refs = NULL; 212 list.refs = NULL;
213 list.alloc = list.count = 0; 213 list.alloc = list.count = 0;
214 for_each_tag_ref(cgit_refs_cb, &list); 214 for_each_tag_ref(cgit_refs_cb, &list);
215 if (list.count == 0) 215 if (list.count == 0)
216 return; 216 return;
217 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); 217 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
218 if (!maxcount) 218 if (!maxcount)
219 maxcount = list.count; 219 maxcount = list.count;
220 else if (maxcount > list.count) 220 else if (maxcount > list.count)
221 maxcount = list.count; 221 maxcount = list.count;
222 print_tag_header(); 222 print_tag_header();
223 for(i=0; i<maxcount; i++) 223 for(i=0; i<maxcount; i++)
224 print_tag(list.refs[i]); 224 print_tag(list.refs[i]);
225 225
226 if (maxcount < list.count) 226 if (maxcount < list.count)
227 print_refs_link("tags"); 227 print_refs_link("tags");
228} 228}
229 229
230void cgit_print_refs() 230void cgit_print_refs()
231{ 231{
232 232
233 html("<table class='list nowrap'>"); 233 html("<table class='list nowrap'>");
234 234
235 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5)) 235 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5))
236 cgit_print_branches(0); 236 cgit_print_branches(0);
237 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4)) 237 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4))
238 cgit_print_tags(0); 238 cgit_print_tags(0);
239 else { 239 else {
240 cgit_print_branches(0); 240 cgit_print_branches(0);
241 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 241 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
242 cgit_print_tags(0); 242 cgit_print_tags(0);
243 } 243 }
244 html("</table>"); 244 html("</table>");
245} 245}
diff --git a/ui-shared.c b/ui-shared.c
index 07d5dd4..de55eff 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,773 +1,795 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output 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#include "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_httpscheme() 37char *cgit_httpscheme()
38{ 38{
39 if (ctx.env.https && !strcmp(ctx.env.https, "on")) 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://"; 40 return "https://";
41 else 41 else
42 return "http://"; 42 return "http://";
43} 43}
44 44
45char *cgit_hosturl() 45char *cgit_hosturl()
46{ 46{
47 if (ctx.env.http_host) 47 if (ctx.env.http_host)
48 return ctx.env.http_host; 48 return ctx.env.http_host;
49 if (!ctx.env.server_name) 49 if (!ctx.env.server_name)
50 return NULL; 50 return NULL;
51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
52 return ctx.env.server_name; 52 return ctx.env.server_name;
53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
54} 54}
55 55
56char *cgit_rooturl() 56char *cgit_rooturl()
57{ 57{
58 if (ctx.cfg.virtual_root) 58 if (ctx.cfg.virtual_root)
59 return fmt("%s/", ctx.cfg.virtual_root); 59 return fmt("%s/", ctx.cfg.virtual_root);
60 else 60 else
61 return ctx.cfg.script_name; 61 return ctx.cfg.script_name;
62} 62}
63 63
64char *cgit_repourl(const char *reponame) 64char *cgit_repourl(const char *reponame)
65{ 65{
66 if (ctx.cfg.virtual_root) { 66 if (ctx.cfg.virtual_root) {
67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
68 } else { 68 } else {
69 return fmt("?r=%s", reponame); 69 return fmt("?r=%s", reponame);
70 } 70 }
71} 71}
72 72
73char *cgit_fileurl(const char *reponame, const char *pagename, 73char *cgit_fileurl(const char *reponame, const char *pagename,
74 const char *filename, const char *query) 74 const char *filename, const char *query)
75{ 75{
76 char *tmp; 76 char *tmp;
77 char *delim; 77 char *delim;
78 78
79 if (ctx.cfg.virtual_root) { 79 if (ctx.cfg.virtual_root) {
80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
81 pagename, (filename ? filename:"")); 81 pagename, (filename ? filename:""));
82 delim = "?"; 82 delim = "?";
83 } else { 83 } else {
84 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 84 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
85 (filename ? filename : "")); 85 (filename ? filename : ""));
86 delim = "&"; 86 delim = "&";
87 } 87 }
88 if (query) 88 if (query)
89 tmp = fmt("%s%s%s", tmp, delim, query); 89 tmp = fmt("%s%s%s", tmp, delim, query);
90 return tmp; 90 return tmp;
91} 91}
92 92
93char *cgit_pageurl(const char *reponame, const char *pagename, 93char *cgit_pageurl(const char *reponame, const char *pagename,
94 const char *query) 94 const char *query)
95{ 95{
96 return cgit_fileurl(reponame,pagename,0,query); 96 return cgit_fileurl(reponame,pagename,0,query);
97} 97}
98 98
99const char *cgit_repobasename(const char *reponame) 99const char *cgit_repobasename(const char *reponame)
100{ 100{
101 /* I assume we don't need to store more than one repo basename */ 101 /* I assume we don't need to store more than one repo basename */
102 static char rvbuf[1024]; 102 static char rvbuf[1024];
103 int p; 103 int p;
104 const char *rv; 104 const char *rv;
105 strncpy(rvbuf,reponame,sizeof(rvbuf)); 105 strncpy(rvbuf,reponame,sizeof(rvbuf));
106 if(rvbuf[sizeof(rvbuf)-1]) 106 if(rvbuf[sizeof(rvbuf)-1])
107 die("cgit_repobasename: truncated repository name '%s'", reponame); 107 die("cgit_repobasename: truncated repository name '%s'", reponame);
108 p = strlen(rvbuf)-1; 108 p = strlen(rvbuf)-1;
109 /* strip trailing slashes */ 109 /* strip trailing slashes */
110 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 110 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
111 /* strip trailing .git */ 111 /* strip trailing .git */
112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
113 p -= 3; rvbuf[p--] = 0; 113 p -= 3; rvbuf[p--] = 0;
114 } 114 }
115 /* strip more trailing slashes if any */ 115 /* strip more trailing slashes if any */
116 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 116 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
117 /* find last slash in the remaining string */ 117 /* find last slash in the remaining string */
118 rv = strrchr(rvbuf,'/'); 118 rv = strrchr(rvbuf,'/');
119 if(rv) 119 if(rv)
120 return ++rv; 120 return ++rv;
121 return rvbuf; 121 return rvbuf;
122} 122}
123 123
124char *cgit_currurl() 124char *cgit_currurl()
125{ 125{
126 if (!ctx.cfg.virtual_root) 126 if (!ctx.cfg.virtual_root)
127 return ctx.cfg.script_name; 127 return ctx.cfg.script_name;
128 else if (ctx.qry.page) 128 else if (ctx.qry.page)
129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(char *page, char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
143 html("/"); 143 html("/");
144 } else 144 } else
145 html(ctx.cfg.script_name); 145 html(ctx.cfg.script_name);
146 146
147 if (page) { 147 if (page) {
148 htmlf("?p=%s", page); 148 htmlf("?p=%s", page);
149 delim = "&"; 149 delim = "&";
150 } 150 }
151 if (search) { 151 if (search) {
152 html(delim); 152 html(delim);
153 html("q="); 153 html("q=");
154 html_attr(search); 154 html_attr(search);
155 delim = "&"; 155 delim = "&";
156 } 156 }
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(char *page, char *name, char *title, char *class, 163static void site_link(char *page, char *name, char *title, char *class,
164 char *search, int ofs) 164 char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
171 } 171 }
172 if (class) { 172 if (class) {
173 html(" class='"); 173 html(" class='");
174 html_attr(class); 174 html_attr(class);
175 html("'"); 175 html("'");
176 } 176 }
177 html(" href='"); 177 html(" href='");
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(char *name, char *title, char *class, char *pattern, 184void cgit_index_link(char *name, char *title, char *class, char *pattern,
185 int ofs) 185 int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(char *title, char *class, char *page, char *head, 190static char *repolink(char *title, char *class, char *page, char *head,
191 char *path) 191 char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
198 html_attr(title); 198 html_attr(title);
199 html("'"); 199 html("'");
200 } 200 }
201 if (class) { 201 if (class) {
202 html(" class='"); 202 html(" class='");
203 html_attr(class); 203 html_attr(class);
204 html("'"); 204 html("'");
205 } 205 }
206 html(" href='"); 206 html(" href='");
207 if (ctx.cfg.virtual_root) { 207 if (ctx.cfg.virtual_root) {
208 html_url_path(ctx.cfg.virtual_root); 208 html_url_path(ctx.cfg.virtual_root);
209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
210 html("/"); 210 html("/");
211 html_url_path(ctx.repo->url); 211 html_url_path(ctx.repo->url);
212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
213 html("/"); 213 html("/");
214 if (page) { 214 if (page) {
215 html_url_path(page); 215 html_url_path(page);
216 html("/"); 216 html("/");
217 if (path) 217 if (path)
218 html_url_path(path); 218 html_url_path(path);
219 } 219 }
220 } else { 220 } else {
221 html(ctx.cfg.script_name); 221 html(ctx.cfg.script_name);
222 html("?url="); 222 html("?url=");
223 html_url_arg(ctx.repo->url); 223 html_url_arg(ctx.repo->url);
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(char *page, char *name, char *title, char *class,
244 char *head, char *rev, char *path) 244 char *head, char *rev, char *path)
245{ 245{
246 char *delim; 246 char *delim;
247 247
248 delim = repolink(title, class, page, head, path); 248 delim = repolink(title, class, page, head, path);
249 if (rev && strcmp(rev, ctx.qry.head)) { 249 if (rev && strcmp(rev, ctx.qry.head)) {
250 html(delim); 250 html(delim);
251 html("id="); 251 html("id=");
252 html_url_arg(rev); 252 html_url_arg(rev);
253 } 253 }
254 html("'>"); 254 html("'>");
255 html_txt(name); 255 html_txt(name);
256 html("</a>"); 256 html("</a>");
257} 257}
258 258
259void cgit_summary_link(char *name, char *title, char *class, char *head) 259void cgit_summary_link(char *name, char *title, char *class, char *head)
260{ 260{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 261 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 262}
263 263
264void cgit_tag_link(char *name, char *title, char *class, char *head, 264void cgit_tag_link(char *name, char *title, char *class, char *head,
265 char *rev) 265 char *rev)
266{ 266{
267 reporevlink("tag", name, title, class, head, rev, NULL); 267 reporevlink("tag", name, title, class, head, rev, NULL);
268} 268}
269 269
270void cgit_tree_link(char *name, char *title, char *class, char *head, 270void cgit_tree_link(char *name, char *title, char *class, char *head,
271 char *rev, char *path) 271 char *rev, char *path)
272{ 272{
273 reporevlink("tree", name, title, class, head, rev, path); 273 reporevlink("tree", name, title, class, head, rev, path);
274} 274}
275 275
276void cgit_plain_link(char *name, char *title, char *class, char *head, 276void cgit_plain_link(char *name, char *title, char *class, char *head,
277 char *rev, char *path) 277 char *rev, char *path)
278{ 278{
279 reporevlink("plain", name, title, class, head, rev, path); 279 reporevlink("plain", name, title, class, head, rev, path);
280} 280}
281 281
282void cgit_log_link(char *name, char *title, char *class, char *head, 282void cgit_log_link(char *name, char *title, char *class, char *head,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 283 char *rev, char *path, int ofs, char *grep, char *pattern,
284 int showmsg) 284 int showmsg)
285{ 285{
286 char *delim; 286 char *delim;
287 287
288 delim = repolink(title, class, "log", head, path); 288 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 289 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 290 html(delim);
291 html("id="); 291 html("id=");
292 html_url_arg(rev); 292 html_url_arg(rev);
293 delim = "&"; 293 delim = "&";
294 } 294 }
295 if (grep && pattern) { 295 if (grep && pattern) {
296 html(delim); 296 html(delim);
297 html("qt="); 297 html("qt=");
298 html_url_arg(grep); 298 html_url_arg(grep);
299 delim = "&"; 299 delim = "&";
300 html(delim); 300 html(delim);
301 html("q="); 301 html("q=");
302 html_url_arg(pattern); 302 html_url_arg(pattern);
303 } 303 }
304 if (ofs > 0) { 304 if (ofs > 0) {
305 html(delim); 305 html(delim);
306 html("ofs="); 306 html("ofs=");
307 htmlf("%d", ofs); 307 htmlf("%d", ofs);
308 delim = "&"; 308 delim = "&";
309 } 309 }
310 if (showmsg) { 310 if (showmsg) {
311 html(delim); 311 html(delim);
312 html("showmsg=1"); 312 html("showmsg=1");
313 } 313 }
314 html("'>"); 314 html("'>");
315 html_txt(name); 315 html_txt(name);
316 html("</a>"); 316 html("</a>");
317} 317}
318 318
319void cgit_commit_link(char *name, char *title, char *class, char *head, 319void cgit_commit_link(char *name, char *title, char *class, char *head,
320 char *rev) 320 char *rev, int toggle_ssdiff)
321{ 321{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 323 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 324 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 325 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 326 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 327 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 328
329 char *delim;
330
331 delim = repolink(title, class, "commit", head, NULL);
332 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim);
334 html("id=");
335 html_url_arg(rev);
336 delim = "&amp;";
337 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim);
340 html("ss=1");
341 }
342 html("'>");
343 html_txt(name);
344 html("</a>");
329} 345}
330 346
331void cgit_refs_link(char *name, char *title, char *class, char *head, 347void cgit_refs_link(char *name, char *title, char *class, char *head,
332 char *rev, char *path) 348 char *rev, char *path)
333{ 349{
334 reporevlink("refs", name, title, class, head, rev, path); 350 reporevlink("refs", name, title, class, head, rev, path);
335} 351}
336 352
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 353void cgit_snapshot_link(char *name, char *title, char *class, char *head,
338 char *rev, char *archivename) 354 char *rev, char *archivename)
339{ 355{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 356 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 357}
342 358
343void cgit_diff_link(char *name, char *title, char *class, char *head, 359void cgit_diff_link(char *name, char *title, char *class, char *head,
344 char *new_rev, char *old_rev, char *path) 360 char *new_rev, char *old_rev, char *path,
361 int toggle_ssdiff)
345{ 362{
346 char *delim; 363 char *delim;
347 364
348 delim = repolink(title, class, "diff", head, path); 365 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 366 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 367 html(delim);
351 html("id="); 368 html("id=");
352 html_url_arg(new_rev); 369 html_url_arg(new_rev);
353 delim = "&amp;"; 370 delim = "&amp;";
354 } 371 }
355 if (old_rev) { 372 if (old_rev) {
356 html(delim); 373 html(delim);
357 html("id2="); 374 html("id2=");
358 html_url_arg(old_rev); 375 html_url_arg(old_rev);
376 delim = "&amp;";
377 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim);
380 html("ss=1");
359 } 381 }
360 html("'>"); 382 html("'>");
361 html_txt(name); 383 html_txt(name);
362 html("</a>"); 384 html("</a>");
363} 385}
364 386
365void cgit_patch_link(char *name, char *title, char *class, char *head, 387void cgit_patch_link(char *name, char *title, char *class, char *head,
366 char *rev) 388 char *rev)
367{ 389{
368 reporevlink("patch", name, title, class, head, rev, NULL); 390 reporevlink("patch", name, title, class, head, rev, NULL);
369} 391}
370 392
371void cgit_stats_link(char *name, char *title, char *class, char *head, 393void cgit_stats_link(char *name, char *title, char *class, char *head,
372 char *path) 394 char *path)
373{ 395{
374 reporevlink("stats", name, title, class, head, NULL, path); 396 reporevlink("stats", name, title, class, head, NULL, path);
375} 397}
376 398
377void cgit_object_link(struct object *obj) 399void cgit_object_link(struct object *obj)
378{ 400{
379 char *page, *shortrev, *fullrev, *name; 401 char *page, *shortrev, *fullrev, *name;
380 402
381 fullrev = sha1_to_hex(obj->sha1); 403 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 404 shortrev = xstrdup(fullrev);
383 shortrev[10] = '\0'; 405 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 406 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 408 ctx.qry.head, fullrev, 0);
387 return; 409 return;
388 } else if (obj->type == OBJ_TREE) 410 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 411 page = "tree";
390 else if (obj->type == OBJ_TAG) 412 else if (obj->type == OBJ_TAG)
391 page = "tag"; 413 page = "tag";
392 else 414 else
393 page = "blob"; 415 page = "blob";
394 name = fmt("%s %s...", typename(obj->type), shortrev); 416 name = fmt("%s %s...", typename(obj->type), shortrev);
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 418}
397 419
398void cgit_print_date(time_t secs, char *format, int local_time) 420void cgit_print_date(time_t secs, char *format, int local_time)
399{ 421{
400 char buf[64]; 422 char buf[64];
401 struct tm *time; 423 struct tm *time;
402 424
403 if (!secs) 425 if (!secs)
404 return; 426 return;
405 if(local_time) 427 if(local_time)
406 time = localtime(&secs); 428 time = localtime(&secs);
407 else 429 else
408 time = gmtime(&secs); 430 time = gmtime(&secs);
409 strftime(buf, sizeof(buf)-1, format, time); 431 strftime(buf, sizeof(buf)-1, format, time);
410 html_txt(buf); 432 html_txt(buf);
411} 433}
412 434
413void cgit_print_age(time_t t, time_t max_relative, char *format) 435void cgit_print_age(time_t t, time_t max_relative, char *format)
414{ 436{
415 time_t now, secs; 437 time_t now, secs;
416 438
417 if (!t) 439 if (!t)
418 return; 440 return;
419 time(&now); 441 time(&now);
420 secs = now - t; 442 secs = now - t;
421 443
422 if (secs > max_relative && max_relative >= 0) { 444 if (secs > max_relative && max_relative >= 0) {
423 cgit_print_date(t, format, ctx.cfg.local_time); 445 cgit_print_date(t, format, ctx.cfg.local_time);
424 return; 446 return;
425 } 447 }
426 448
427 if (secs < TM_HOUR * 2) { 449 if (secs < TM_HOUR * 2) {
428 htmlf("<span class='age-mins'>%.0f min.</span>", 450 htmlf("<span class='age-mins'>%.0f min.</span>",
429 secs * 1.0 / TM_MIN); 451 secs * 1.0 / TM_MIN);
430 return; 452 return;
431 } 453 }
432 if (secs < TM_DAY * 2) { 454 if (secs < TM_DAY * 2) {
433 htmlf("<span class='age-hours'>%.0f hours</span>", 455 htmlf("<span class='age-hours'>%.0f hours</span>",
434 secs * 1.0 / TM_HOUR); 456 secs * 1.0 / TM_HOUR);
435 return; 457 return;
436 } 458 }
437 if (secs < TM_WEEK * 2) { 459 if (secs < TM_WEEK * 2) {
438 htmlf("<span class='age-days'>%.0f days</span>", 460 htmlf("<span class='age-days'>%.0f days</span>",
439 secs * 1.0 / TM_DAY); 461 secs * 1.0 / TM_DAY);
440 return; 462 return;
441 } 463 }
442 if (secs < TM_MONTH * 2) { 464 if (secs < TM_MONTH * 2) {
443 htmlf("<span class='age-weeks'>%.0f weeks</span>", 465 htmlf("<span class='age-weeks'>%.0f weeks</span>",
444 secs * 1.0 / TM_WEEK); 466 secs * 1.0 / TM_WEEK);
445 return; 467 return;
446 } 468 }
447 if (secs < TM_YEAR * 2) { 469 if (secs < TM_YEAR * 2) {
448 htmlf("<span class='age-months'>%.0f months</span>", 470 htmlf("<span class='age-months'>%.0f months</span>",
449 secs * 1.0 / TM_MONTH); 471 secs * 1.0 / TM_MONTH);
450 return; 472 return;
451 } 473 }
452 htmlf("<span class='age-years'>%.0f years</span>", 474 htmlf("<span class='age-years'>%.0f years</span>",
453 secs * 1.0 / TM_YEAR); 475 secs * 1.0 / TM_YEAR);
454} 476}
455 477
456void cgit_print_http_headers(struct cgit_context *ctx) 478void cgit_print_http_headers(struct cgit_context *ctx)
457{ 479{
458 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1")) 480 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
459 return; 481 return;
460 482
461 if (ctx->page.status) 483 if (ctx->page.status)
462 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); 484 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
463 if (ctx->page.mimetype && ctx->page.charset) 485 if (ctx->page.mimetype && ctx->page.charset)
464 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 486 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
465 ctx->page.charset); 487 ctx->page.charset);
466 else if (ctx->page.mimetype) 488 else if (ctx->page.mimetype)
467 htmlf("Content-Type: %s\n", ctx->page.mimetype); 489 htmlf("Content-Type: %s\n", ctx->page.mimetype);
468 if (ctx->page.size) 490 if (ctx->page.size)
469 htmlf("Content-Length: %ld\n", ctx->page.size); 491 htmlf("Content-Length: %ld\n", ctx->page.size);
470 if (ctx->page.filename) 492 if (ctx->page.filename)
471 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 493 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
472 ctx->page.filename); 494 ctx->page.filename);
473 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 495 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
474 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 496 htmlf("Expires: %s\n", http_date(ctx->page.expires));
475 if (ctx->page.etag) 497 if (ctx->page.etag)
476 htmlf("ETag: \"%s\"\n", ctx->page.etag); 498 htmlf("ETag: \"%s\"\n", ctx->page.etag);
477 html("\n"); 499 html("\n");
478 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) 500 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
479 exit(0); 501 exit(0);
480} 502}
481 503
482void cgit_print_docstart(struct cgit_context *ctx) 504void cgit_print_docstart(struct cgit_context *ctx)
483{ 505{
484 if (ctx->cfg.embedded) { 506 if (ctx->cfg.embedded) {
485 if (ctx->cfg.header) 507 if (ctx->cfg.header)
486 html_include(ctx->cfg.header); 508 html_include(ctx->cfg.header);
487 return; 509 return;
488 } 510 }
489 511
490 char *host = cgit_hosturl(); 512 char *host = cgit_hosturl();
491 html(cgit_doctype); 513 html(cgit_doctype);
492 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 514 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
493 html("<head>\n"); 515 html("<head>\n");
494 html("<title>"); 516 html("<title>");
495 html_txt(ctx->page.title); 517 html_txt(ctx->page.title);
496 html("</title>\n"); 518 html("</title>\n");
497 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 519 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
498 if (ctx->cfg.robots && *ctx->cfg.robots) 520 if (ctx->cfg.robots && *ctx->cfg.robots)
499 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 521 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
500 html("<link rel='stylesheet' type='text/css' href='"); 522 html("<link rel='stylesheet' type='text/css' href='");
501 html_attr(ctx->cfg.css); 523 html_attr(ctx->cfg.css);
502 html("'/>\n"); 524 html("'/>\n");
503 if (ctx->cfg.favicon) { 525 if (ctx->cfg.favicon) {
504 html("<link rel='shortcut icon' href='"); 526 html("<link rel='shortcut icon' href='");
505 html_attr(ctx->cfg.favicon); 527 html_attr(ctx->cfg.favicon);
506 html("'/>\n"); 528 html("'/>\n");
507 } 529 }
508 if (host && ctx->repo) { 530 if (host && ctx->repo) {
509 html("<link rel='alternate' title='Atom feed' href='"); 531 html("<link rel='alternate' title='Atom feed' href='");
510 html(cgit_httpscheme()); 532 html(cgit_httpscheme());
511 html_attr(cgit_hosturl()); 533 html_attr(cgit_hosturl());
512 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 534 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
513 fmt("h=%s", ctx->qry.head))); 535 fmt("h=%s", ctx->qry.head)));
514 html("' type='application/atom+xml'/>\n"); 536 html("' type='application/atom+xml'/>\n");
515 } 537 }
516 if (ctx->cfg.head_include) 538 if (ctx->cfg.head_include)
517 html_include(ctx->cfg.head_include); 539 html_include(ctx->cfg.head_include);
518 html("</head>\n"); 540 html("</head>\n");
519 html("<body>\n"); 541 html("<body>\n");
520 if (ctx->cfg.header) 542 if (ctx->cfg.header)
521 html_include(ctx->cfg.header); 543 html_include(ctx->cfg.header);
522} 544}
523 545
524void cgit_print_docend() 546void cgit_print_docend()
525{ 547{
526 html("</div> <!-- class=content -->\n"); 548 html("</div> <!-- class=content -->\n");
527 if (ctx.cfg.embedded) { 549 if (ctx.cfg.embedded) {
528 html("</div> <!-- id=cgit -->\n"); 550 html("</div> <!-- id=cgit -->\n");
529 if (ctx.cfg.footer) 551 if (ctx.cfg.footer)
530 html_include(ctx.cfg.footer); 552 html_include(ctx.cfg.footer);
531 return; 553 return;
532 } 554 }
533 if (ctx.cfg.footer) 555 if (ctx.cfg.footer)
534 html_include(ctx.cfg.footer); 556 html_include(ctx.cfg.footer);
535 else { 557 else {
536 htmlf("<div class='footer'>generated by cgit %s at ", 558 htmlf("<div class='footer'>generated by cgit %s at ",
537 cgit_version); 559 cgit_version);
538 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 560 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
539 html("</div>\n"); 561 html("</div>\n");
540 } 562 }
541 html("</div> <!-- id=cgit -->\n"); 563 html("</div> <!-- id=cgit -->\n");
542 html("</body>\n</html>\n"); 564 html("</body>\n</html>\n");
543} 565}
544 566
545int print_branch_option(const char *refname, const unsigned char *sha1, 567int print_branch_option(const char *refname, const unsigned char *sha1,
546 int flags, void *cb_data) 568 int flags, void *cb_data)
547{ 569{
548 char *name = (char *)refname; 570 char *name = (char *)refname;
549 html_option(name, name, ctx.qry.head); 571 html_option(name, name, ctx.qry.head);
550 return 0; 572 return 0;
551} 573}
552 574
553int print_archive_ref(const char *refname, const unsigned char *sha1, 575int print_archive_ref(const char *refname, const unsigned char *sha1,
554 int flags, void *cb_data) 576 int flags, void *cb_data)
555{ 577{
556 struct tag *tag; 578 struct tag *tag;
557 struct taginfo *info; 579 struct taginfo *info;
558 struct object *obj; 580 struct object *obj;
559 char buf[256], *url; 581 char buf[256], *url;
560 unsigned char fileid[20]; 582 unsigned char fileid[20];
561 int *header = (int *)cb_data; 583 int *header = (int *)cb_data;
562 584
563 if (prefixcmp(refname, "refs/archives")) 585 if (prefixcmp(refname, "refs/archives"))
564 return 0; 586 return 0;
565 strncpy(buf, refname+14, sizeof(buf)); 587 strncpy(buf, refname+14, sizeof(buf));
566 obj = parse_object(sha1); 588 obj = parse_object(sha1);
567 if (!obj) 589 if (!obj)
568 return 1; 590 return 1;
569 if (obj->type == OBJ_TAG) { 591 if (obj->type == OBJ_TAG) {
570 tag = lookup_tag(sha1); 592 tag = lookup_tag(sha1);
571 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 593 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
572 return 0; 594 return 0;
573 hashcpy(fileid, tag->tagged->sha1); 595 hashcpy(fileid, tag->tagged->sha1);
574 } else if (obj->type != OBJ_BLOB) { 596 } else if (obj->type != OBJ_BLOB) {
575 return 0; 597 return 0;
576 } else { 598 } else {
577 hashcpy(fileid, sha1); 599 hashcpy(fileid, sha1);
578 } 600 }
579 if (!*header) { 601 if (!*header) {
580 html("<h1>download</h1>\n"); 602 html("<h1>download</h1>\n");
581 *header = 1; 603 *header = 1;
582 } 604 }
583 url = cgit_pageurl(ctx.qry.repo, "blob", 605 url = cgit_pageurl(ctx.qry.repo, "blob",
584 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 606 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
585 buf)); 607 buf));
586 html_link_open(url, NULL, "menu"); 608 html_link_open(url, NULL, "menu");
587 html_txt(strlpart(buf, 20)); 609 html_txt(strlpart(buf, 20));
588 html_link_close(); 610 html_link_close();
589 return 0; 611 return 0;
590} 612}
591 613
592void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 614void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
593{ 615{
594 char *url; 616 char *url;
595 617
596 if (!ctx.cfg.virtual_root) { 618 if (!ctx.cfg.virtual_root) {
597 url = fmt("%s/%s", ctx.qry.repo, page); 619 url = fmt("%s/%s", ctx.qry.repo, page);
598 if (ctx.qry.path) 620 if (ctx.qry.path)
599 url = fmt("%s/%s", url, ctx.qry.path); 621 url = fmt("%s/%s", url, ctx.qry.path);
600 html_hidden("url", url); 622 html_hidden("url", url);
601 } 623 }
602 624
603 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 625 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
604 strcmp(ctx.qry.head, ctx.repo->defbranch)) 626 strcmp(ctx.qry.head, ctx.repo->defbranch))
605 html_hidden("h", ctx.qry.head); 627 html_hidden("h", ctx.qry.head);
606 628
607 if (ctx.qry.sha1) 629 if (ctx.qry.sha1)
608 html_hidden("id", ctx.qry.sha1); 630 html_hidden("id", ctx.qry.sha1);
609 if (ctx.qry.sha2) 631 if (ctx.qry.sha2)
610 html_hidden("id2", ctx.qry.sha2); 632 html_hidden("id2", ctx.qry.sha2);
611 if (ctx.qry.showmsg) 633 if (ctx.qry.showmsg)
612 html_hidden("showmsg", "1"); 634 html_hidden("showmsg", "1");
613 635
614 if (incl_search) { 636 if (incl_search) {
615 if (ctx.qry.grep) 637 if (ctx.qry.grep)
616 html_hidden("qt", ctx.qry.grep); 638 html_hidden("qt", ctx.qry.grep);
617 if (ctx.qry.search) 639 if (ctx.qry.search)
618 html_hidden("q", ctx.qry.search); 640 html_hidden("q", ctx.qry.search);
619 } 641 }
620} 642}
621 643
622const char *fallback_cmd = "repolist"; 644const char *fallback_cmd = "repolist";
623 645
624char *hc(struct cgit_cmd *cmd, const char *page) 646char *hc(struct cgit_cmd *cmd, const char *page)
625{ 647{
626 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 648 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
627} 649}
628 650
629static void print_header(struct cgit_context *ctx) 651static void print_header(struct cgit_context *ctx)
630{ 652{
631 html("<table id='header'>\n"); 653 html("<table id='header'>\n");
632 html("<tr>\n"); 654 html("<tr>\n");
633 655
634 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 656 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
635 html("<td class='logo' rowspan='2'><a href='"); 657 html("<td class='logo' rowspan='2'><a href='");
636 if (ctx->cfg.logo_link) 658 if (ctx->cfg.logo_link)
637 html_attr(ctx->cfg.logo_link); 659 html_attr(ctx->cfg.logo_link);
638 else 660 else
639 html_attr(cgit_rooturl()); 661 html_attr(cgit_rooturl());
640 html("'><img src='"); 662 html("'><img src='");
641 html_attr(ctx->cfg.logo); 663 html_attr(ctx->cfg.logo);
642 html("' alt='cgit logo'/></a></td>\n"); 664 html("' alt='cgit logo'/></a></td>\n");
643 } 665 }
644 666
645 html("<td class='main'>"); 667 html("<td class='main'>");
646 if (ctx->repo) { 668 if (ctx->repo) {
647 cgit_index_link("index", NULL, NULL, NULL, 0); 669 cgit_index_link("index", NULL, NULL, NULL, 0);
648 html(" : "); 670 html(" : ");
649 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 671 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
650 html("</td><td class='form'>"); 672 html("</td><td class='form'>");
651 html("<form method='get' action=''>\n"); 673 html("<form method='get' action=''>\n");
652 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 674 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
653 html("<select name='h' onchange='this.form.submit();'>\n"); 675 html("<select name='h' onchange='this.form.submit();'>\n");
654 for_each_branch_ref(print_branch_option, ctx->qry.head); 676 for_each_branch_ref(print_branch_option, ctx->qry.head);
655 html("</select> "); 677 html("</select> ");
656 html("<input type='submit' name='' value='switch'/>"); 678 html("<input type='submit' name='' value='switch'/>");
657 html("</form>"); 679 html("</form>");
658 } else 680 } else
659 html_txt(ctx->cfg.root_title); 681 html_txt(ctx->cfg.root_title);
660 html("</td></tr>\n"); 682 html("</td></tr>\n");
661 683
662 html("<tr><td class='sub'>"); 684 html("<tr><td class='sub'>");
663 if (ctx->repo) { 685 if (ctx->repo) {
664 html_txt(ctx->repo->desc); 686 html_txt(ctx->repo->desc);
665 html("</td><td class='sub right'>"); 687 html("</td><td class='sub right'>");
666 html_txt(ctx->repo->owner); 688 html_txt(ctx->repo->owner);
667 } else { 689 } else {
668 if (ctx->cfg.root_desc) 690 if (ctx->cfg.root_desc)
669 html_txt(ctx->cfg.root_desc); 691 html_txt(ctx->cfg.root_desc);
670 else if (ctx->cfg.index_info) 692 else if (ctx->cfg.index_info)
671 html_include(ctx->cfg.index_info); 693 html_include(ctx->cfg.index_info);
672 } 694 }
673 html("</td></tr></table>\n"); 695 html("</td></tr></table>\n");
674} 696}
675 697
676void cgit_print_pageheader(struct cgit_context *ctx) 698void cgit_print_pageheader(struct cgit_context *ctx)
677{ 699{
678 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 700 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
679 701
680 if (!cmd && ctx->repo) 702 if (!cmd && ctx->repo)
681 fallback_cmd = "summary"; 703 fallback_cmd = "summary";
682 704
683 html("<div id='cgit'>"); 705 html("<div id='cgit'>");
684 if (!ctx->cfg.noheader) 706 if (!ctx->cfg.noheader)
685 print_header(ctx); 707 print_header(ctx);
686 708
687 html("<table class='tabs'><tr><td>\n"); 709 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 710 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 711 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
690 ctx->qry.head); 712 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 714 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
696 ctx->qry.sha1, NULL); 718 ctx->qry.sha1, NULL);
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 719 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
698 ctx->qry.head, ctx->qry.sha1); 720 ctx->qry.head, ctx->qry.sha1, 0);
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0);
701 if (ctx->repo->max_stats) 723 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 724 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
703 ctx->qry.head, NULL); 725 ctx->qry.head, NULL);
704 if (ctx->repo->readme) 726 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 727 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 728 hc(cmd, "about"), ctx->qry.head, NULL,
707 NULL); 729 NULL);
708 html("</td><td class='form'>"); 730 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 731 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 732 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 733 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 734 ctx->qry.path, NULL));
713 html("'>\n"); 735 html("'>\n");
714 cgit_add_hidden_formfields(1, 0, "log"); 736 cgit_add_hidden_formfields(1, 0, "log");
715 html("<select name='qt'>\n"); 737 html("<select name='qt'>\n");
716 html_option("grep", "log msg", ctx->qry.grep); 738 html_option("grep", "log msg", ctx->qry.grep);
717 html_option("author", "author", ctx->qry.grep); 739 html_option("author", "author", ctx->qry.grep);
718 html_option("committer", "committer", ctx->qry.grep); 740 html_option("committer", "committer", ctx->qry.grep);
719 html("</select>\n"); 741 html("</select>\n");
720 html("<input class='txt' type='text' size='10' name='q' value='"); 742 html("<input class='txt' type='text' size='10' name='q' value='");
721 html_attr(ctx->qry.search); 743 html_attr(ctx->qry.search);
722 html("'/>\n"); 744 html("'/>\n");
723 html("<input type='submit' value='search'/>\n"); 745 html("<input type='submit' value='search'/>\n");
724 html("</form>\n"); 746 html("</form>\n");
725 } else { 747 } else {
726 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 748 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
727 if (ctx->cfg.root_readme) 749 if (ctx->cfg.root_readme)
728 site_link("about", "about", NULL, hc(cmd, "about"), 750 site_link("about", "about", NULL, hc(cmd, "about"),
729 NULL, 0); 751 NULL, 0);
730 html("</td><td class='form'>"); 752 html("</td><td class='form'>");
731 html("<form method='get' action='"); 753 html("<form method='get' action='");
732 html_attr(cgit_rooturl()); 754 html_attr(cgit_rooturl());
733 html("'>\n"); 755 html("'>\n");
734 html("<input type='text' name='q' size='10' value='"); 756 html("<input type='text' name='q' size='10' value='");
735 html_attr(ctx->qry.search); 757 html_attr(ctx->qry.search);
736 html("'/>\n"); 758 html("'/>\n");
737 html("<input type='submit' value='search'/>\n"); 759 html("<input type='submit' value='search'/>\n");
738 html("</form>"); 760 html("</form>");
739 } 761 }
740 html("</td></tr></table>\n"); 762 html("</td></tr></table>\n");
741 html("<div class='content'>"); 763 html("<div class='content'>");
742} 764}
743 765
744void cgit_print_filemode(unsigned short mode) 766void cgit_print_filemode(unsigned short mode)
745{ 767{
746 if (S_ISDIR(mode)) 768 if (S_ISDIR(mode))
747 html("d"); 769 html("d");
748 else if (S_ISLNK(mode)) 770 else if (S_ISLNK(mode))
749 html("l"); 771 html("l");
750 else if (S_ISGITLINK(mode)) 772 else if (S_ISGITLINK(mode))
751 html("m"); 773 html("m");
752 else 774 else
753 html("-"); 775 html("-");
754 html_fileperm(mode >> 6); 776 html_fileperm(mode >> 6);
755 html_fileperm(mode >> 3); 777 html_fileperm(mode >> 3);
756 html_fileperm(mode); 778 html_fileperm(mode);
757} 779}
758 780
759void cgit_print_snapshot_links(const char *repo, const char *head, 781void cgit_print_snapshot_links(const char *repo, const char *head,
760 const char *hex, int snapshots) 782 const char *hex, int snapshots)
761{ 783{
762 const struct cgit_snapshot_format* f; 784 const struct cgit_snapshot_format* f;
763 char *filename; 785 char *filename;
764 786
765 for (f = cgit_snapshot_formats; f->suffix; f++) { 787 for (f = cgit_snapshot_formats; f->suffix; f++) {
766 if (!(snapshots & f->bit)) 788 if (!(snapshots & f->bit))
767 continue; 789 continue;
768 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 790 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
769 f->suffix); 791 f->suffix);
770 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 792 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
771 html("<br/>"); 793 html("<br/>");
772 } 794 }
773} 795}
diff --git a/ui-shared.h b/ui-shared.h
index bff4826..166246d 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,50 +1,51 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_repourl(const char *reponame); 6extern char *cgit_repourl(const char *reponame);
7extern char *cgit_fileurl(const char *reponame, const char *pagename, 7extern char *cgit_fileurl(const char *reponame, const char *pagename,
8 const char *filename, const char *query); 8 const char *filename, const char *query);
9extern char *cgit_pageurl(const char *reponame, const char *pagename, 9extern char *cgit_pageurl(const char *reponame, const char *pagename,
10 const char *query); 10 const char *query);
11 11
12extern void cgit_index_link(char *name, char *title, char *class, 12extern void cgit_index_link(char *name, char *title, char *class,
13 char *pattern, int ofs); 13 char *pattern, int ofs);
14extern void cgit_summary_link(char *name, char *title, char *class, char *head); 14extern void cgit_summary_link(char *name, char *title, char *class, char *head);
15extern void cgit_tag_link(char *name, char *title, char *class, char *head, 15extern void cgit_tag_link(char *name, char *title, char *class, char *head,
16 char *rev); 16 char *rev);
17extern void cgit_tree_link(char *name, char *title, char *class, char *head, 17extern void cgit_tree_link(char *name, char *title, char *class, char *head,
18 char *rev, char *path); 18 char *rev, char *path);
19extern void cgit_plain_link(char *name, char *title, char *class, char *head, 19extern void cgit_plain_link(char *name, char *title, char *class, char *head,
20 char *rev, char *path); 20 char *rev, char *path);
21extern void cgit_log_link(char *name, char *title, char *class, char *head, 21extern void cgit_log_link(char *name, char *title, char *class, char *head,
22 char *rev, char *path, int ofs, char *grep, 22 char *rev, char *path, int ofs, char *grep,
23 char *pattern, int showmsg); 23 char *pattern, int showmsg);
24extern void cgit_commit_link(char *name, char *title, char *class, char *head, 24extern void cgit_commit_link(char *name, char *title, char *class, char *head,
25 char *rev); 25 char *rev, int toggle_ssdiff);
26extern void cgit_patch_link(char *name, char *title, char *class, char *head, 26extern void cgit_patch_link(char *name, char *title, char *class, char *head,
27 char *rev); 27 char *rev);
28extern void cgit_refs_link(char *name, char *title, char *class, char *head, 28extern void cgit_refs_link(char *name, char *title, char *class, char *head,
29 char *rev, char *path); 29 char *rev, char *path);
30extern void cgit_snapshot_link(char *name, char *title, char *class, 30extern void cgit_snapshot_link(char *name, char *title, char *class,
31 char *head, char *rev, char *archivename); 31 char *head, char *rev, char *archivename);
32extern void cgit_diff_link(char *name, char *title, char *class, char *head, 32extern void cgit_diff_link(char *name, char *title, char *class, char *head,
33 char *new_rev, char *old_rev, char *path); 33 char *new_rev, char *old_rev, char *path,
34 int toggle_ssdiff);
34extern void cgit_stats_link(char *name, char *title, char *class, char *head, 35extern void cgit_stats_link(char *name, char *title, char *class, char *head,
35 char *path); 36 char *path);
36extern void cgit_object_link(struct object *obj); 37extern void cgit_object_link(struct object *obj);
37 38
38extern void cgit_print_error(char *msg); 39extern void cgit_print_error(char *msg);
39extern void cgit_print_date(time_t secs, char *format, int local_time); 40extern void cgit_print_date(time_t secs, char *format, int local_time);
40extern void cgit_print_age(time_t t, time_t max_relative, char *format); 41extern void cgit_print_age(time_t t, time_t max_relative, char *format);
41extern void cgit_print_http_headers(struct cgit_context *ctx); 42extern void cgit_print_http_headers(struct cgit_context *ctx);
42extern void cgit_print_docstart(struct cgit_context *ctx); 43extern void cgit_print_docstart(struct cgit_context *ctx);
43extern void cgit_print_docend(); 44extern void cgit_print_docend();
44extern void cgit_print_pageheader(struct cgit_context *ctx); 45extern void cgit_print_pageheader(struct cgit_context *ctx);
45extern void cgit_print_filemode(unsigned short mode); 46extern void cgit_print_filemode(unsigned short mode);
46extern void cgit_print_snapshot_links(const char *repo, const char *head, 47extern void cgit_print_snapshot_links(const char *repo, const char *head,
47 const char *hex, int snapshots); 48 const char *hex, int snapshots);
48extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 49extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
49 char *page); 50 char *page);
50#endif /* UI_SHARED_H */ 51#endif /* UI_SHARED_H */