summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2011-02-19 13:01:59 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2011-02-19 13:01:59 (UTC)
commit31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63 (patch) (unidiff)
tree26200ad3c376ea1208d2a870ebb3b2bc6d7abee6
parente66a16cebcdac53b63e77876acef1ca9e4877038 (diff)
parentc2bfd40f8aaaa69a66c6eb729c202e42a43ec166 (diff)
downloadcgit-31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63.zip
cgit-31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63.tar.gz
cgit-31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63.tar.bz2
Merge branch 'jh/graph'
* jh/graph: ui-log: Move 'Age' column when commit graph is present ui-log: Line-wrap long commit subjects when showmsg is enabled ui-log: Colorize commit graph ui-log: Implement support for commit graphs ui-log: Change display of full commit messages (and notes) Conflicts: cgit.css
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c6
-rw-r--r--cgit.css40
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt15
-rw-r--r--cmd.c3
-rw-r--r--shared.c1
-rw-r--r--ui-log.c195
-rw-r--r--ui-log.h3
-rw-r--r--ui-summary.c2
9 files changed, 219 insertions, 49 deletions
diff --git a/cgit.c b/cgit.c
index 412fbf0..53ab68d 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,778 +1,784 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> 4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "cache.h" 11#include "cache.h"
12#include "cmd.h" 12#include "cmd.h"
13#include "configfile.h" 13#include "configfile.h"
14#include "html.h" 14#include "html.h"
15#include "ui-shared.h" 15#include "ui-shared.h"
16#include "ui-stats.h" 16#include "ui-stats.h"
17#include "scan-tree.h" 17#include "scan-tree.h"
18 18
19const char *cgit_version = CGIT_VERSION; 19const char *cgit_version = CGIT_VERSION;
20 20
21void add_mimetype(const char *name, const char *value) 21void add_mimetype(const char *name, const char *value)
22{ 22{
23 struct string_list_item *item; 23 struct string_list_item *item;
24 24
25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); 25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
26 item->util = xstrdup(value); 26 item->util = xstrdup(value);
27} 27}
28 28
29struct cgit_filter *new_filter(const char *cmd, int extra_args) 29struct cgit_filter *new_filter(const char *cmd, int extra_args)
30{ 30{
31 struct cgit_filter *f; 31 struct cgit_filter *f;
32 32
33 if (!cmd || !cmd[0]) 33 if (!cmd || !cmd[0])
34 return NULL; 34 return NULL;
35 35
36 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
39 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
40 f->argv[1] = NULL; 40 f->argv[1] = NULL;
41 return f; 41 return f;
42} 42}
43 43
44static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
45 45
46void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
47{ 47{
48 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
49 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
50 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
51 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
52 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
53 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
54 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
55 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
56 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
57 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
60 else if (!strcmp(name, "enable-commit-graph"))
61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
60 else if (!strcmp(name, "enable-log-filecount")) 62 else if (!strcmp(name, "enable-log-filecount"))
61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
62 else if (!strcmp(name, "enable-log-linecount")) 64 else if (!strcmp(name, "enable-log-linecount"))
63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
64 else if (!strcmp(name, "enable-remote-branches")) 66 else if (!strcmp(name, "enable-remote-branches"))
65 repo->enable_remote_branches = atoi(value); 67 repo->enable_remote_branches = atoi(value);
66 else if (!strcmp(name, "enable-subject-links")) 68 else if (!strcmp(name, "enable-subject-links"))
67 repo->enable_subject_links = atoi(value); 69 repo->enable_subject_links = atoi(value);
68 else if (!strcmp(name, "max-stats")) 70 else if (!strcmp(name, "max-stats"))
69 repo->max_stats = cgit_find_stats_period(value, NULL); 71 repo->max_stats = cgit_find_stats_period(value, NULL);
70 else if (!strcmp(name, "module-link")) 72 else if (!strcmp(name, "module-link"))
71 repo->module_link= xstrdup(value); 73 repo->module_link= xstrdup(value);
72 else if (!strcmp(name, "section")) 74 else if (!strcmp(name, "section"))
73 repo->section = xstrdup(value); 75 repo->section = xstrdup(value);
74 else if (!strcmp(name, "readme") && value != NULL) { 76 else if (!strcmp(name, "readme") && value != NULL) {
75 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
76 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 79 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 80 repo->about_filter = new_filter(value, 0);
79 else if (!strcmp(name, "commit-filter")) 81 else if (!strcmp(name, "commit-filter"))
80 repo->commit_filter = new_filter(value, 0); 82 repo->commit_filter = new_filter(value, 0);
81 else if (!strcmp(name, "source-filter")) 83 else if (!strcmp(name, "source-filter"))
82 repo->source_filter = new_filter(value, 1); 84 repo->source_filter = new_filter(value, 1);
83 } 85 }
84} 86}
85 87
86void config_cb(const char *name, const char *value) 88void config_cb(const char *name, const char *value)
87{ 89{
88 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 90 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
89 ctx.cfg.section = xstrdup(value); 91 ctx.cfg.section = xstrdup(value);
90 else if (!strcmp(name, "repo.url")) 92 else if (!strcmp(name, "repo.url"))
91 ctx.repo = cgit_add_repo(value); 93 ctx.repo = cgit_add_repo(value);
92 else if (ctx.repo && !strcmp(name, "repo.path")) 94 else if (ctx.repo && !strcmp(name, "repo.path"))
93 ctx.repo->path = trim_end(value, '/'); 95 ctx.repo->path = trim_end(value, '/');
94 else if (ctx.repo && !prefixcmp(name, "repo.")) 96 else if (ctx.repo && !prefixcmp(name, "repo."))
95 repo_config(ctx.repo, name + 5, value); 97 repo_config(ctx.repo, name + 5, value);
96 else if (!strcmp(name, "readme")) 98 else if (!strcmp(name, "readme"))
97 ctx.cfg.readme = xstrdup(value); 99 ctx.cfg.readme = xstrdup(value);
98 else if (!strcmp(name, "root-title")) 100 else if (!strcmp(name, "root-title"))
99 ctx.cfg.root_title = xstrdup(value); 101 ctx.cfg.root_title = xstrdup(value);
100 else if (!strcmp(name, "root-desc")) 102 else if (!strcmp(name, "root-desc"))
101 ctx.cfg.root_desc = xstrdup(value); 103 ctx.cfg.root_desc = xstrdup(value);
102 else if (!strcmp(name, "root-readme")) 104 else if (!strcmp(name, "root-readme"))
103 ctx.cfg.root_readme = xstrdup(value); 105 ctx.cfg.root_readme = xstrdup(value);
104 else if (!strcmp(name, "css")) 106 else if (!strcmp(name, "css"))
105 ctx.cfg.css = xstrdup(value); 107 ctx.cfg.css = xstrdup(value);
106 else if (!strcmp(name, "favicon")) 108 else if (!strcmp(name, "favicon"))
107 ctx.cfg.favicon = xstrdup(value); 109 ctx.cfg.favicon = xstrdup(value);
108 else if (!strcmp(name, "footer")) 110 else if (!strcmp(name, "footer"))
109 ctx.cfg.footer = xstrdup(value); 111 ctx.cfg.footer = xstrdup(value);
110 else if (!strcmp(name, "head-include")) 112 else if (!strcmp(name, "head-include"))
111 ctx.cfg.head_include = xstrdup(value); 113 ctx.cfg.head_include = xstrdup(value);
112 else if (!strcmp(name, "header")) 114 else if (!strcmp(name, "header"))
113 ctx.cfg.header = xstrdup(value); 115 ctx.cfg.header = xstrdup(value);
114 else if (!strcmp(name, "logo")) 116 else if (!strcmp(name, "logo"))
115 ctx.cfg.logo = xstrdup(value); 117 ctx.cfg.logo = xstrdup(value);
116 else if (!strcmp(name, "index-header")) 118 else if (!strcmp(name, "index-header"))
117 ctx.cfg.index_header = xstrdup(value); 119 ctx.cfg.index_header = xstrdup(value);
118 else if (!strcmp(name, "index-info")) 120 else if (!strcmp(name, "index-info"))
119 ctx.cfg.index_info = xstrdup(value); 121 ctx.cfg.index_info = xstrdup(value);
120 else if (!strcmp(name, "logo-link")) 122 else if (!strcmp(name, "logo-link"))
121 ctx.cfg.logo_link = xstrdup(value); 123 ctx.cfg.logo_link = xstrdup(value);
122 else if (!strcmp(name, "module-link")) 124 else if (!strcmp(name, "module-link"))
123 ctx.cfg.module_link = xstrdup(value); 125 ctx.cfg.module_link = xstrdup(value);
124 else if (!strcmp(name, "strict-export")) 126 else if (!strcmp(name, "strict-export"))
125 ctx.cfg.strict_export = xstrdup(value); 127 ctx.cfg.strict_export = xstrdup(value);
126 else if (!strcmp(name, "virtual-root")) { 128 else if (!strcmp(name, "virtual-root")) {
127 ctx.cfg.virtual_root = trim_end(value, '/'); 129 ctx.cfg.virtual_root = trim_end(value, '/');
128 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 130 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
129 ctx.cfg.virtual_root = ""; 131 ctx.cfg.virtual_root = "";
130 } else if (!strcmp(name, "nocache")) 132 } else if (!strcmp(name, "nocache"))
131 ctx.cfg.nocache = atoi(value); 133 ctx.cfg.nocache = atoi(value);
132 else if (!strcmp(name, "noplainemail")) 134 else if (!strcmp(name, "noplainemail"))
133 ctx.cfg.noplainemail = atoi(value); 135 ctx.cfg.noplainemail = atoi(value);
134 else if (!strcmp(name, "noheader")) 136 else if (!strcmp(name, "noheader"))
135 ctx.cfg.noheader = atoi(value); 137 ctx.cfg.noheader = atoi(value);
136 else if (!strcmp(name, "snapshots")) 138 else if (!strcmp(name, "snapshots"))
137 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 139 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
138 else if (!strcmp(name, "enable-filter-overrides")) 140 else if (!strcmp(name, "enable-filter-overrides"))
139 ctx.cfg.enable_filter_overrides = atoi(value); 141 ctx.cfg.enable_filter_overrides = atoi(value);
140 else if (!strcmp(name, "enable-gitweb-owner")) 142 else if (!strcmp(name, "enable-gitweb-owner"))
141 ctx.cfg.enable_gitweb_owner = atoi(value); 143 ctx.cfg.enable_gitweb_owner = atoi(value);
142 else if (!strcmp(name, "enable-index-links")) 144 else if (!strcmp(name, "enable-index-links"))
143 ctx.cfg.enable_index_links = atoi(value); 145 ctx.cfg.enable_index_links = atoi(value);
146 else if (!strcmp(name, "enable-commit-graph"))
147 ctx.cfg.enable_commit_graph = atoi(value);
144 else if (!strcmp(name, "enable-log-filecount")) 148 else if (!strcmp(name, "enable-log-filecount"))
145 ctx.cfg.enable_log_filecount = atoi(value); 149 ctx.cfg.enable_log_filecount = atoi(value);
146 else if (!strcmp(name, "enable-log-linecount")) 150 else if (!strcmp(name, "enable-log-linecount"))
147 ctx.cfg.enable_log_linecount = atoi(value); 151 ctx.cfg.enable_log_linecount = atoi(value);
148 else if (!strcmp(name, "enable-remote-branches")) 152 else if (!strcmp(name, "enable-remote-branches"))
149 ctx.cfg.enable_remote_branches = atoi(value); 153 ctx.cfg.enable_remote_branches = atoi(value);
150 else if (!strcmp(name, "enable-subject-links")) 154 else if (!strcmp(name, "enable-subject-links"))
151 ctx.cfg.enable_subject_links = atoi(value); 155 ctx.cfg.enable_subject_links = atoi(value);
152 else if (!strcmp(name, "enable-tree-linenumbers")) 156 else if (!strcmp(name, "enable-tree-linenumbers"))
153 ctx.cfg.enable_tree_linenumbers = atoi(value); 157 ctx.cfg.enable_tree_linenumbers = atoi(value);
154 else if (!strcmp(name, "max-stats")) 158 else if (!strcmp(name, "max-stats"))
155 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 159 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
156 else if (!strcmp(name, "cache-size")) 160 else if (!strcmp(name, "cache-size"))
157 ctx.cfg.cache_size = atoi(value); 161 ctx.cfg.cache_size = atoi(value);
158 else if (!strcmp(name, "cache-root")) 162 else if (!strcmp(name, "cache-root"))
159 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 163 ctx.cfg.cache_root = xstrdup(expand_macros(value));
160 else if (!strcmp(name, "cache-root-ttl")) 164 else if (!strcmp(name, "cache-root-ttl"))
161 ctx.cfg.cache_root_ttl = atoi(value); 165 ctx.cfg.cache_root_ttl = atoi(value);
162 else if (!strcmp(name, "cache-repo-ttl")) 166 else if (!strcmp(name, "cache-repo-ttl"))
163 ctx.cfg.cache_repo_ttl = atoi(value); 167 ctx.cfg.cache_repo_ttl = atoi(value);
164 else if (!strcmp(name, "cache-scanrc-ttl")) 168 else if (!strcmp(name, "cache-scanrc-ttl"))
165 ctx.cfg.cache_scanrc_ttl = atoi(value); 169 ctx.cfg.cache_scanrc_ttl = atoi(value);
166 else if (!strcmp(name, "cache-static-ttl")) 170 else if (!strcmp(name, "cache-static-ttl"))
167 ctx.cfg.cache_static_ttl = atoi(value); 171 ctx.cfg.cache_static_ttl = atoi(value);
168 else if (!strcmp(name, "cache-dynamic-ttl")) 172 else if (!strcmp(name, "cache-dynamic-ttl"))
169 ctx.cfg.cache_dynamic_ttl = atoi(value); 173 ctx.cfg.cache_dynamic_ttl = atoi(value);
170 else if (!strcmp(name, "about-filter")) 174 else if (!strcmp(name, "about-filter"))
171 ctx.cfg.about_filter = new_filter(value, 0); 175 ctx.cfg.about_filter = new_filter(value, 0);
172 else if (!strcmp(name, "commit-filter")) 176 else if (!strcmp(name, "commit-filter"))
173 ctx.cfg.commit_filter = new_filter(value, 0); 177 ctx.cfg.commit_filter = new_filter(value, 0);
174 else if (!strcmp(name, "embedded")) 178 else if (!strcmp(name, "embedded"))
175 ctx.cfg.embedded = atoi(value); 179 ctx.cfg.embedded = atoi(value);
176 else if (!strcmp(name, "max-atom-items")) 180 else if (!strcmp(name, "max-atom-items"))
177 ctx.cfg.max_atom_items = atoi(value); 181 ctx.cfg.max_atom_items = atoi(value);
178 else if (!strcmp(name, "max-message-length")) 182 else if (!strcmp(name, "max-message-length"))
179 ctx.cfg.max_msg_len = atoi(value); 183 ctx.cfg.max_msg_len = atoi(value);
180 else if (!strcmp(name, "max-repodesc-length")) 184 else if (!strcmp(name, "max-repodesc-length"))
181 ctx.cfg.max_repodesc_len = atoi(value); 185 ctx.cfg.max_repodesc_len = atoi(value);
182 else if (!strcmp(name, "max-blob-size")) 186 else if (!strcmp(name, "max-blob-size"))
183 ctx.cfg.max_blob_size = atoi(value); 187 ctx.cfg.max_blob_size = atoi(value);
184 else if (!strcmp(name, "max-repo-count")) 188 else if (!strcmp(name, "max-repo-count"))
185 ctx.cfg.max_repo_count = atoi(value); 189 ctx.cfg.max_repo_count = atoi(value);
186 else if (!strcmp(name, "max-commit-count")) 190 else if (!strcmp(name, "max-commit-count"))
187 ctx.cfg.max_commit_count = atoi(value); 191 ctx.cfg.max_commit_count = atoi(value);
188 else if (!strcmp(name, "project-list")) 192 else if (!strcmp(name, "project-list"))
189 ctx.cfg.project_list = xstrdup(expand_macros(value)); 193 ctx.cfg.project_list = xstrdup(expand_macros(value));
190 else if (!strcmp(name, "scan-path")) 194 else if (!strcmp(name, "scan-path"))
191 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 195 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
192 process_cached_repolist(expand_macros(value)); 196 process_cached_repolist(expand_macros(value));
193 else if (ctx.cfg.project_list) 197 else if (ctx.cfg.project_list)
194 scan_projects(expand_macros(value), 198 scan_projects(expand_macros(value),
195 ctx.cfg.project_list, repo_config); 199 ctx.cfg.project_list, repo_config);
196 else 200 else
197 scan_tree(expand_macros(value), repo_config); 201 scan_tree(expand_macros(value), repo_config);
198 else if (!strcmp(name, "section-from-path")) 202 else if (!strcmp(name, "section-from-path"))
199 ctx.cfg.section_from_path = atoi(value); 203 ctx.cfg.section_from_path = atoi(value);
200 else if (!strcmp(name, "source-filter")) 204 else if (!strcmp(name, "source-filter"))
201 ctx.cfg.source_filter = new_filter(value, 1); 205 ctx.cfg.source_filter = new_filter(value, 1);
202 else if (!strcmp(name, "summary-log")) 206 else if (!strcmp(name, "summary-log"))
203 ctx.cfg.summary_log = atoi(value); 207 ctx.cfg.summary_log = atoi(value);
204 else if (!strcmp(name, "summary-branches")) 208 else if (!strcmp(name, "summary-branches"))
205 ctx.cfg.summary_branches = atoi(value); 209 ctx.cfg.summary_branches = atoi(value);
206 else if (!strcmp(name, "summary-tags")) 210 else if (!strcmp(name, "summary-tags"))
207 ctx.cfg.summary_tags = atoi(value); 211 ctx.cfg.summary_tags = atoi(value);
208 else if (!strcmp(name, "side-by-side-diffs")) 212 else if (!strcmp(name, "side-by-side-diffs"))
209 ctx.cfg.ssdiff = atoi(value); 213 ctx.cfg.ssdiff = atoi(value);
210 else if (!strcmp(name, "agefile")) 214 else if (!strcmp(name, "agefile"))
211 ctx.cfg.agefile = xstrdup(value); 215 ctx.cfg.agefile = xstrdup(value);
212 else if (!strcmp(name, "renamelimit")) 216 else if (!strcmp(name, "renamelimit"))
213 ctx.cfg.renamelimit = atoi(value); 217 ctx.cfg.renamelimit = atoi(value);
214 else if (!strcmp(name, "remove-suffix")) 218 else if (!strcmp(name, "remove-suffix"))
215 ctx.cfg.remove_suffix = atoi(value); 219 ctx.cfg.remove_suffix = atoi(value);
216 else if (!strcmp(name, "robots")) 220 else if (!strcmp(name, "robots"))
217 ctx.cfg.robots = xstrdup(value); 221 ctx.cfg.robots = xstrdup(value);
218 else if (!strcmp(name, "clone-prefix")) 222 else if (!strcmp(name, "clone-prefix"))
219 ctx.cfg.clone_prefix = xstrdup(value); 223 ctx.cfg.clone_prefix = xstrdup(value);
220 else if (!strcmp(name, "local-time")) 224 else if (!strcmp(name, "local-time"))
221 ctx.cfg.local_time = atoi(value); 225 ctx.cfg.local_time = atoi(value);
222 else if (!prefixcmp(name, "mimetype.")) 226 else if (!prefixcmp(name, "mimetype."))
223 add_mimetype(name + 9, value); 227 add_mimetype(name + 9, value);
224 else if (!strcmp(name, "include")) 228 else if (!strcmp(name, "include"))
225 parse_configfile(expand_macros(value), config_cb); 229 parse_configfile(expand_macros(value), config_cb);
226} 230}
227 231
228static void querystring_cb(const char *name, const char *value) 232static void querystring_cb(const char *name, const char *value)
229{ 233{
230 if (!value) 234 if (!value)
231 value = ""; 235 value = "";
232 236
233 if (!strcmp(name,"r")) { 237 if (!strcmp(name,"r")) {
234 ctx.qry.repo = xstrdup(value); 238 ctx.qry.repo = xstrdup(value);
235 ctx.repo = cgit_get_repoinfo(value); 239 ctx.repo = cgit_get_repoinfo(value);
236 } else if (!strcmp(name, "p")) { 240 } else if (!strcmp(name, "p")) {
237 ctx.qry.page = xstrdup(value); 241 ctx.qry.page = xstrdup(value);
238 } else if (!strcmp(name, "url")) { 242 } else if (!strcmp(name, "url")) {
239 if (*value == '/') 243 if (*value == '/')
240 value++; 244 value++;
241 ctx.qry.url = xstrdup(value); 245 ctx.qry.url = xstrdup(value);
242 cgit_parse_url(value); 246 cgit_parse_url(value);
243 } else if (!strcmp(name, "qt")) { 247 } else if (!strcmp(name, "qt")) {
244 ctx.qry.grep = xstrdup(value); 248 ctx.qry.grep = xstrdup(value);
245 } else if (!strcmp(name, "q")) { 249 } else if (!strcmp(name, "q")) {
246 ctx.qry.search = xstrdup(value); 250 ctx.qry.search = xstrdup(value);
247 } else if (!strcmp(name, "h")) { 251 } else if (!strcmp(name, "h")) {
248 ctx.qry.head = xstrdup(value); 252 ctx.qry.head = xstrdup(value);
249 ctx.qry.has_symref = 1; 253 ctx.qry.has_symref = 1;
250 } else if (!strcmp(name, "id")) { 254 } else if (!strcmp(name, "id")) {
251 ctx.qry.sha1 = xstrdup(value); 255 ctx.qry.sha1 = xstrdup(value);
252 ctx.qry.has_sha1 = 1; 256 ctx.qry.has_sha1 = 1;
253 } else if (!strcmp(name, "id2")) { 257 } else if (!strcmp(name, "id2")) {
254 ctx.qry.sha2 = xstrdup(value); 258 ctx.qry.sha2 = xstrdup(value);
255 ctx.qry.has_sha1 = 1; 259 ctx.qry.has_sha1 = 1;
256 } else if (!strcmp(name, "ofs")) { 260 } else if (!strcmp(name, "ofs")) {
257 ctx.qry.ofs = atoi(value); 261 ctx.qry.ofs = atoi(value);
258 } else if (!strcmp(name, "path")) { 262 } else if (!strcmp(name, "path")) {
259 ctx.qry.path = trim_end(value, '/'); 263 ctx.qry.path = trim_end(value, '/');
260 } else if (!strcmp(name, "name")) { 264 } else if (!strcmp(name, "name")) {
261 ctx.qry.name = xstrdup(value); 265 ctx.qry.name = xstrdup(value);
262 } else if (!strcmp(name, "mimetype")) { 266 } else if (!strcmp(name, "mimetype")) {
263 ctx.qry.mimetype = xstrdup(value); 267 ctx.qry.mimetype = xstrdup(value);
264 } else if (!strcmp(name, "s")){ 268 } else if (!strcmp(name, "s")){
265 ctx.qry.sort = xstrdup(value); 269 ctx.qry.sort = xstrdup(value);
266 } else if (!strcmp(name, "showmsg")) { 270 } else if (!strcmp(name, "showmsg")) {
267 ctx.qry.showmsg = atoi(value); 271 ctx.qry.showmsg = atoi(value);
268 } else if (!strcmp(name, "period")) { 272 } else if (!strcmp(name, "period")) {
269 ctx.qry.period = xstrdup(value); 273 ctx.qry.period = xstrdup(value);
270 } else if (!strcmp(name, "ss")) { 274 } else if (!strcmp(name, "ss")) {
271 ctx.qry.ssdiff = atoi(value); 275 ctx.qry.ssdiff = atoi(value);
272 } else if (!strcmp(name, "all")) { 276 } else if (!strcmp(name, "all")) {
273 ctx.qry.show_all = atoi(value); 277 ctx.qry.show_all = atoi(value);
274 } else if (!strcmp(name, "context")) { 278 } else if (!strcmp(name, "context")) {
275 ctx.qry.context = atoi(value); 279 ctx.qry.context = atoi(value);
276 } else if (!strcmp(name, "ignorews")) { 280 } else if (!strcmp(name, "ignorews")) {
277 ctx.qry.ignorews = atoi(value); 281 ctx.qry.ignorews = atoi(value);
278 } 282 }
279} 283}
280 284
281char *xstrdupn(const char *str) 285char *xstrdupn(const char *str)
282{ 286{
283 return (str ? xstrdup(str) : NULL); 287 return (str ? xstrdup(str) : NULL);
284} 288}
285 289
286static void prepare_context(struct cgit_context *ctx) 290static void prepare_context(struct cgit_context *ctx)
287{ 291{
288 memset(ctx, 0, sizeof(*ctx)); 292 memset(ctx, 0, sizeof(*ctx));
289 ctx->cfg.agefile = "info/web/last-modified"; 293 ctx->cfg.agefile = "info/web/last-modified";
290 ctx->cfg.nocache = 0; 294 ctx->cfg.nocache = 0;
291 ctx->cfg.cache_size = 0; 295 ctx->cfg.cache_size = 0;
292 ctx->cfg.cache_dynamic_ttl = 5; 296 ctx->cfg.cache_dynamic_ttl = 5;
293 ctx->cfg.cache_max_create_time = 5; 297 ctx->cfg.cache_max_create_time = 5;
294 ctx->cfg.cache_repo_ttl = 5; 298 ctx->cfg.cache_repo_ttl = 5;
295 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 299 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
296 ctx->cfg.cache_root_ttl = 5; 300 ctx->cfg.cache_root_ttl = 5;
297 ctx->cfg.cache_scanrc_ttl = 15; 301 ctx->cfg.cache_scanrc_ttl = 15;
298 ctx->cfg.cache_static_ttl = -1; 302 ctx->cfg.cache_static_ttl = -1;
299 ctx->cfg.css = "/cgit.css"; 303 ctx->cfg.css = "/cgit.css";
300 ctx->cfg.logo = "/cgit.png"; 304 ctx->cfg.logo = "/cgit.png";
301 ctx->cfg.local_time = 0; 305 ctx->cfg.local_time = 0;
302 ctx->cfg.enable_gitweb_owner = 1; 306 ctx->cfg.enable_gitweb_owner = 1;
303 ctx->cfg.enable_tree_linenumbers = 1; 307 ctx->cfg.enable_tree_linenumbers = 1;
304 ctx->cfg.max_repo_count = 50; 308 ctx->cfg.max_repo_count = 50;
305 ctx->cfg.max_commit_count = 50; 309 ctx->cfg.max_commit_count = 50;
306 ctx->cfg.max_lock_attempts = 5; 310 ctx->cfg.max_lock_attempts = 5;
307 ctx->cfg.max_msg_len = 80; 311 ctx->cfg.max_msg_len = 80;
308 ctx->cfg.max_repodesc_len = 80; 312 ctx->cfg.max_repodesc_len = 80;
309 ctx->cfg.max_blob_size = 0; 313 ctx->cfg.max_blob_size = 0;
310 ctx->cfg.max_stats = 0; 314 ctx->cfg.max_stats = 0;
311 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 315 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
312 ctx->cfg.project_list = NULL; 316 ctx->cfg.project_list = NULL;
313 ctx->cfg.renamelimit = -1; 317 ctx->cfg.renamelimit = -1;
314 ctx->cfg.remove_suffix = 0; 318 ctx->cfg.remove_suffix = 0;
315 ctx->cfg.robots = "index, nofollow"; 319 ctx->cfg.robots = "index, nofollow";
316 ctx->cfg.root_title = "Git repository browser"; 320 ctx->cfg.root_title = "Git repository browser";
317 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 321 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
318 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 322 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
319 ctx->cfg.section = ""; 323 ctx->cfg.section = "";
320 ctx->cfg.summary_branches = 10; 324 ctx->cfg.summary_branches = 10;
321 ctx->cfg.summary_log = 10; 325 ctx->cfg.summary_log = 10;
322 ctx->cfg.summary_tags = 10; 326 ctx->cfg.summary_tags = 10;
323 ctx->cfg.max_atom_items = 10; 327 ctx->cfg.max_atom_items = 10;
324 ctx->cfg.ssdiff = 0; 328 ctx->cfg.ssdiff = 0;
325 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 329 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
326 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 330 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
327 ctx->env.https = xstrdupn(getenv("HTTPS")); 331 ctx->env.https = xstrdupn(getenv("HTTPS"));
328 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 332 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
329 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 333 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
330 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 334 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
331 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 335 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
332 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 336 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
333 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 337 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
334 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 338 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
335 ctx->page.mimetype = "text/html"; 339 ctx->page.mimetype = "text/html";
336 ctx->page.charset = PAGE_ENCODING; 340 ctx->page.charset = PAGE_ENCODING;
337 ctx->page.filename = NULL; 341 ctx->page.filename = NULL;
338 ctx->page.size = 0; 342 ctx->page.size = 0;
339 ctx->page.modified = time(NULL); 343 ctx->page.modified = time(NULL);
340 ctx->page.expires = ctx->page.modified; 344 ctx->page.expires = ctx->page.modified;
341 ctx->page.etag = NULL; 345 ctx->page.etag = NULL;
342 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 346 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
343 if (ctx->env.script_name) 347 if (ctx->env.script_name)
344 ctx->cfg.script_name = ctx->env.script_name; 348 ctx->cfg.script_name = ctx->env.script_name;
345 if (ctx->env.query_string) 349 if (ctx->env.query_string)
346 ctx->qry.raw = ctx->env.query_string; 350 ctx->qry.raw = ctx->env.query_string;
347 if (!ctx->env.cgit_config) 351 if (!ctx->env.cgit_config)
348 ctx->env.cgit_config = CGIT_CONFIG; 352 ctx->env.cgit_config = CGIT_CONFIG;
349} 353}
350 354
351struct refmatch { 355struct refmatch {
352 char *req_ref; 356 char *req_ref;
353 char *first_ref; 357 char *first_ref;
354 int match; 358 int match;
355}; 359};
356 360
357int find_current_ref(const char *refname, const unsigned char *sha1, 361int find_current_ref(const char *refname, const unsigned char *sha1,
358 int flags, void *cb_data) 362 int flags, void *cb_data)
359{ 363{
360 struct refmatch *info; 364 struct refmatch *info;
361 365
362 info = (struct refmatch *)cb_data; 366 info = (struct refmatch *)cb_data;
363 if (!strcmp(refname, info->req_ref)) 367 if (!strcmp(refname, info->req_ref))
364 info->match = 1; 368 info->match = 1;
365 if (!info->first_ref) 369 if (!info->first_ref)
366 info->first_ref = xstrdup(refname); 370 info->first_ref = xstrdup(refname);
367 return info->match; 371 return info->match;
368} 372}
369 373
370char *find_default_branch(struct cgit_repo *repo) 374char *find_default_branch(struct cgit_repo *repo)
371{ 375{
372 struct refmatch info; 376 struct refmatch info;
373 char *ref; 377 char *ref;
374 378
375 info.req_ref = repo->defbranch; 379 info.req_ref = repo->defbranch;
376 info.first_ref = NULL; 380 info.first_ref = NULL;
377 info.match = 0; 381 info.match = 0;
378 for_each_branch_ref(find_current_ref, &info); 382 for_each_branch_ref(find_current_ref, &info);
379 if (info.match) 383 if (info.match)
380 ref = info.req_ref; 384 ref = info.req_ref;
381 else 385 else
382 ref = info.first_ref; 386 ref = info.first_ref;
383 if (ref) 387 if (ref)
384 ref = xstrdup(ref); 388 ref = xstrdup(ref);
385 return ref; 389 return ref;
386} 390}
387 391
388static int prepare_repo_cmd(struct cgit_context *ctx) 392static int prepare_repo_cmd(struct cgit_context *ctx)
389{ 393{
390 char *tmp; 394 char *tmp;
391 unsigned char sha1[20]; 395 unsigned char sha1[20];
392 int nongit = 0; 396 int nongit = 0;
393 397
394 setenv("GIT_DIR", ctx->repo->path, 1); 398 setenv("GIT_DIR", ctx->repo->path, 1);
395 setup_git_directory_gently(&nongit); 399 setup_git_directory_gently(&nongit);
396 if (nongit) { 400 if (nongit) {
397 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 401 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
398 "config error"); 402 "config error");
399 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 403 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
400 ctx->repo = NULL; 404 ctx->repo = NULL;
401 cgit_print_http_headers(ctx); 405 cgit_print_http_headers(ctx);
402 cgit_print_docstart(ctx); 406 cgit_print_docstart(ctx);
403 cgit_print_pageheader(ctx); 407 cgit_print_pageheader(ctx);
404 cgit_print_error(tmp); 408 cgit_print_error(tmp);
405 cgit_print_docend(); 409 cgit_print_docend();
406 return 1; 410 return 1;
407 } 411 }
408 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 412 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
409 413
410 if (!ctx->qry.head) { 414 if (!ctx->qry.head) {
411 ctx->qry.nohead = 1; 415 ctx->qry.nohead = 1;
412 ctx->qry.head = find_default_branch(ctx->repo); 416 ctx->qry.head = find_default_branch(ctx->repo);
413 ctx->repo->defbranch = ctx->qry.head; 417 ctx->repo->defbranch = ctx->qry.head;
414 } 418 }
415 419
416 if (!ctx->qry.head) { 420 if (!ctx->qry.head) {
417 cgit_print_http_headers(ctx); 421 cgit_print_http_headers(ctx);
418 cgit_print_docstart(ctx); 422 cgit_print_docstart(ctx);
419 cgit_print_pageheader(ctx); 423 cgit_print_pageheader(ctx);
420 cgit_print_error("Repository seems to be empty"); 424 cgit_print_error("Repository seems to be empty");
421 cgit_print_docend(); 425 cgit_print_docend();
422 return 1; 426 return 1;
423 } 427 }
424 428
425 if (get_sha1(ctx->qry.head, sha1)) { 429 if (get_sha1(ctx->qry.head, sha1)) {
426 tmp = xstrdup(ctx->qry.head); 430 tmp = xstrdup(ctx->qry.head);
427 ctx->qry.head = ctx->repo->defbranch; 431 ctx->qry.head = ctx->repo->defbranch;
428 ctx->page.status = 404; 432 ctx->page.status = 404;
429 ctx->page.statusmsg = "not found"; 433 ctx->page.statusmsg = "not found";
430 cgit_print_http_headers(ctx); 434 cgit_print_http_headers(ctx);
431 cgit_print_docstart(ctx); 435 cgit_print_docstart(ctx);
432 cgit_print_pageheader(ctx); 436 cgit_print_pageheader(ctx);
433 cgit_print_error(fmt("Invalid branch: %s", tmp)); 437 cgit_print_error(fmt("Invalid branch: %s", tmp));
434 cgit_print_docend(); 438 cgit_print_docend();
435 return 1; 439 return 1;
436 } 440 }
437 return 0; 441 return 0;
438} 442}
439 443
440static void process_request(void *cbdata) 444static void process_request(void *cbdata)
441{ 445{
442 struct cgit_context *ctx = cbdata; 446 struct cgit_context *ctx = cbdata;
443 struct cgit_cmd *cmd; 447 struct cgit_cmd *cmd;
444 448
445 cmd = cgit_get_cmd(ctx); 449 cmd = cgit_get_cmd(ctx);
446 if (!cmd) { 450 if (!cmd) {
447 ctx->page.title = "cgit error"; 451 ctx->page.title = "cgit error";
448 cgit_print_http_headers(ctx); 452 cgit_print_http_headers(ctx);
449 cgit_print_docstart(ctx); 453 cgit_print_docstart(ctx);
450 cgit_print_pageheader(ctx); 454 cgit_print_pageheader(ctx);
451 cgit_print_error("Invalid request"); 455 cgit_print_error("Invalid request");
452 cgit_print_docend(); 456 cgit_print_docend();
453 return; 457 return;
454 } 458 }
455 459
456 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual" 460 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
457 * in-project path limit to be made available at ctx->qry.vpath. 461 * in-project path limit to be made available at ctx->qry.vpath.
458 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL). 462 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
459 */ 463 */
460 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL; 464 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
461 465
462 if (cmd->want_repo && !ctx->repo) { 466 if (cmd->want_repo && !ctx->repo) {
463 cgit_print_http_headers(ctx); 467 cgit_print_http_headers(ctx);
464 cgit_print_docstart(ctx); 468 cgit_print_docstart(ctx);
465 cgit_print_pageheader(ctx); 469 cgit_print_pageheader(ctx);
466 cgit_print_error(fmt("No repository selected")); 470 cgit_print_error(fmt("No repository selected"));
467 cgit_print_docend(); 471 cgit_print_docend();
468 return; 472 return;
469 } 473 }
470 474
471 if (ctx->repo && prepare_repo_cmd(ctx)) 475 if (ctx->repo && prepare_repo_cmd(ctx))
472 return; 476 return;
473 477
474 if (cmd->want_layout) { 478 if (cmd->want_layout) {
475 cgit_print_http_headers(ctx); 479 cgit_print_http_headers(ctx);
476 cgit_print_docstart(ctx); 480 cgit_print_docstart(ctx);
477 cgit_print_pageheader(ctx); 481 cgit_print_pageheader(ctx);
478 } 482 }
479 483
480 cmd->fn(ctx); 484 cmd->fn(ctx);
481 485
482 if (cmd->want_layout) 486 if (cmd->want_layout)
483 cgit_print_docend(); 487 cgit_print_docend();
484} 488}
485 489
486int cmp_repos(const void *a, const void *b) 490int cmp_repos(const void *a, const void *b)
487{ 491{
488 const struct cgit_repo *ra = a, *rb = b; 492 const struct cgit_repo *ra = a, *rb = b;
489 return strcmp(ra->url, rb->url); 493 return strcmp(ra->url, rb->url);
490} 494}
491 495
492char *build_snapshot_setting(int bitmap) 496char *build_snapshot_setting(int bitmap)
493{ 497{
494 const struct cgit_snapshot_format *f; 498 const struct cgit_snapshot_format *f;
495 char *result = xstrdup(""); 499 char *result = xstrdup("");
496 char *tmp; 500 char *tmp;
497 int len; 501 int len;
498 502
499 for (f = cgit_snapshot_formats; f->suffix; f++) { 503 for (f = cgit_snapshot_formats; f->suffix; f++) {
500 if (f->bit & bitmap) { 504 if (f->bit & bitmap) {
501 tmp = result; 505 tmp = result;
502 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 506 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
503 free(tmp); 507 free(tmp);
504 } 508 }
505 } 509 }
506 len = strlen(result); 510 len = strlen(result);
507 if (len) 511 if (len)
508 result[len - 1] = '\0'; 512 result[len - 1] = '\0';
509 return result; 513 return result;
510} 514}
511 515
512char *get_first_line(char *txt) 516char *get_first_line(char *txt)
513{ 517{
514 char *t = xstrdup(txt); 518 char *t = xstrdup(txt);
515 char *p = strchr(t, '\n'); 519 char *p = strchr(t, '\n');
516 if (p) 520 if (p)
517 *p = '\0'; 521 *p = '\0';
518 return t; 522 return t;
519} 523}
520 524
521void print_repo(FILE *f, struct cgit_repo *repo) 525void print_repo(FILE *f, struct cgit_repo *repo)
522{ 526{
523 fprintf(f, "repo.url=%s\n", repo->url); 527 fprintf(f, "repo.url=%s\n", repo->url);
524 fprintf(f, "repo.name=%s\n", repo->name); 528 fprintf(f, "repo.name=%s\n", repo->name);
525 fprintf(f, "repo.path=%s\n", repo->path); 529 fprintf(f, "repo.path=%s\n", repo->path);
526 if (repo->owner) 530 if (repo->owner)
527 fprintf(f, "repo.owner=%s\n", repo->owner); 531 fprintf(f, "repo.owner=%s\n", repo->owner);
528 if (repo->desc) { 532 if (repo->desc) {
529 char *tmp = get_first_line(repo->desc); 533 char *tmp = get_first_line(repo->desc);
530 fprintf(f, "repo.desc=%s\n", tmp); 534 fprintf(f, "repo.desc=%s\n", tmp);
531 free(tmp); 535 free(tmp);
532 } 536 }
533 if (repo->readme) 537 if (repo->readme)
534 fprintf(f, "repo.readme=%s\n", repo->readme); 538 fprintf(f, "repo.readme=%s\n", repo->readme);
535 if (repo->defbranch) 539 if (repo->defbranch)
536 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 540 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
537 if (repo->module_link) 541 if (repo->module_link)
538 fprintf(f, "repo.module-link=%s\n", repo->module_link); 542 fprintf(f, "repo.module-link=%s\n", repo->module_link);
539 if (repo->section) 543 if (repo->section)
540 fprintf(f, "repo.section=%s\n", repo->section); 544 fprintf(f, "repo.section=%s\n", repo->section);
541 if (repo->clone_url) 545 if (repo->clone_url)
542 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 546 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
547 fprintf(f, "repo.enable-commit-graph=%d\n",
548 repo->enable_commit_graph);
543 fprintf(f, "repo.enable-log-filecount=%d\n", 549 fprintf(f, "repo.enable-log-filecount=%d\n",
544 repo->enable_log_filecount); 550 repo->enable_log_filecount);
545 fprintf(f, "repo.enable-log-linecount=%d\n", 551 fprintf(f, "repo.enable-log-linecount=%d\n",
546 repo->enable_log_linecount); 552 repo->enable_log_linecount);
547 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 553 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
548 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 554 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
549 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 555 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
550 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 556 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
551 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 557 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
552 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 558 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
553 if (repo->snapshots != ctx.cfg.snapshots) { 559 if (repo->snapshots != ctx.cfg.snapshots) {
554 char *tmp = build_snapshot_setting(repo->snapshots); 560 char *tmp = build_snapshot_setting(repo->snapshots);
555 fprintf(f, "repo.snapshots=%s\n", tmp); 561 fprintf(f, "repo.snapshots=%s\n", tmp);
556 free(tmp); 562 free(tmp);
557 } 563 }
558 if (repo->max_stats != ctx.cfg.max_stats) 564 if (repo->max_stats != ctx.cfg.max_stats)
559 fprintf(f, "repo.max-stats=%s\n", 565 fprintf(f, "repo.max-stats=%s\n",
560 cgit_find_stats_periodname(repo->max_stats)); 566 cgit_find_stats_periodname(repo->max_stats));
561 fprintf(f, "\n"); 567 fprintf(f, "\n");
562} 568}
563 569
564void print_repolist(FILE *f, struct cgit_repolist *list, int start) 570void print_repolist(FILE *f, struct cgit_repolist *list, int start)
565{ 571{
566 int i; 572 int i;
567 573
568 for(i = start; i < list->count; i++) 574 for(i = start; i < list->count; i++)
569 print_repo(f, &list->repos[i]); 575 print_repo(f, &list->repos[i]);
570} 576}
571 577
572/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 578/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
573 * and return 0 on success. 579 * and return 0 on success.
574 */ 580 */
575static int generate_cached_repolist(const char *path, const char *cached_rc) 581static int generate_cached_repolist(const char *path, const char *cached_rc)
576{ 582{
577 char *locked_rc; 583 char *locked_rc;
578 int idx; 584 int idx;
579 FILE *f; 585 FILE *f;
580 586
581 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 587 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
582 f = fopen(locked_rc, "wx"); 588 f = fopen(locked_rc, "wx");
583 if (!f) { 589 if (!f) {
584 /* Inform about the error unless the lockfile already existed, 590 /* Inform about the error unless the lockfile already existed,
585 * since that only means we've got concurrent requests. 591 * since that only means we've got concurrent requests.
586 */ 592 */
587 if (errno != EEXIST) 593 if (errno != EEXIST)
588 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 594 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
589 locked_rc, strerror(errno), errno); 595 locked_rc, strerror(errno), errno);
590 return errno; 596 return errno;
591 } 597 }
592 idx = cgit_repolist.count; 598 idx = cgit_repolist.count;
593 if (ctx.cfg.project_list) 599 if (ctx.cfg.project_list)
594 scan_projects(path, ctx.cfg.project_list, repo_config); 600 scan_projects(path, ctx.cfg.project_list, repo_config);
595 else 601 else
596 scan_tree(path, repo_config); 602 scan_tree(path, repo_config);
597 print_repolist(f, &cgit_repolist, idx); 603 print_repolist(f, &cgit_repolist, idx);
598 if (rename(locked_rc, cached_rc)) 604 if (rename(locked_rc, cached_rc))
599 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 605 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
600 locked_rc, cached_rc, strerror(errno), errno); 606 locked_rc, cached_rc, strerror(errno), errno);
601 fclose(f); 607 fclose(f);
602 return 0; 608 return 0;
603} 609}
604 610
605static void process_cached_repolist(const char *path) 611static void process_cached_repolist(const char *path)
606{ 612{
607 struct stat st; 613 struct stat st;
608 char *cached_rc; 614 char *cached_rc;
609 time_t age; 615 time_t age;
610 unsigned long hash; 616 unsigned long hash;
611 617
612 hash = hash_str(path); 618 hash = hash_str(path);
613 if (ctx.cfg.project_list) 619 if (ctx.cfg.project_list)
614 hash += hash_str(ctx.cfg.project_list); 620 hash += hash_str(ctx.cfg.project_list);
615 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash)); 621 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
616 622
617 if (stat(cached_rc, &st)) { 623 if (stat(cached_rc, &st)) {
618 /* Nothing is cached, we need to scan without forking. And 624 /* Nothing is cached, we need to scan without forking. And
619 * if we fail to generate a cached repolist, we need to 625 * if we fail to generate a cached repolist, we need to
620 * invoke scan_tree manually. 626 * invoke scan_tree manually.
621 */ 627 */
622 if (generate_cached_repolist(path, cached_rc)) { 628 if (generate_cached_repolist(path, cached_rc)) {
623 if (ctx.cfg.project_list) 629 if (ctx.cfg.project_list)
624 scan_projects(path, ctx.cfg.project_list, 630 scan_projects(path, ctx.cfg.project_list,
625 repo_config); 631 repo_config);
626 else 632 else
627 scan_tree(path, repo_config); 633 scan_tree(path, repo_config);
628 } 634 }
629 return; 635 return;
630 } 636 }
631 637
632 parse_configfile(cached_rc, config_cb); 638 parse_configfile(cached_rc, config_cb);
633 639
634 /* If the cached configfile hasn't expired, lets exit now */ 640 /* If the cached configfile hasn't expired, lets exit now */
635 age = time(NULL) - st.st_mtime; 641 age = time(NULL) - st.st_mtime;
636 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 642 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
637 return; 643 return;
638 644
639 /* The cached repolist has been parsed, but it was old. So lets 645 /* The cached repolist has been parsed, but it was old. So lets
640 * rescan the specified path and generate a new cached repolist 646 * rescan the specified path and generate a new cached repolist
641 * in a child-process to avoid latency for the current request. 647 * in a child-process to avoid latency for the current request.
642 */ 648 */
643 if (fork()) 649 if (fork())
644 return; 650 return;
645 651
646 exit(generate_cached_repolist(path, cached_rc)); 652 exit(generate_cached_repolist(path, cached_rc));
647} 653}
648 654
649static void cgit_parse_args(int argc, const char **argv) 655static void cgit_parse_args(int argc, const char **argv)
650{ 656{
651 int i; 657 int i;
652 int scan = 0; 658 int scan = 0;
653 659
654 for (i = 1; i < argc; i++) { 660 for (i = 1; i < argc; i++) {
655 if (!strncmp(argv[i], "--cache=", 8)) { 661 if (!strncmp(argv[i], "--cache=", 8)) {
656 ctx.cfg.cache_root = xstrdup(argv[i]+8); 662 ctx.cfg.cache_root = xstrdup(argv[i]+8);
657 } 663 }
658 if (!strcmp(argv[i], "--nocache")) { 664 if (!strcmp(argv[i], "--nocache")) {
659 ctx.cfg.nocache = 1; 665 ctx.cfg.nocache = 1;
660 } 666 }
661 if (!strcmp(argv[i], "--nohttp")) { 667 if (!strcmp(argv[i], "--nohttp")) {
662 ctx.env.no_http = "1"; 668 ctx.env.no_http = "1";
663 } 669 }
664 if (!strncmp(argv[i], "--query=", 8)) { 670 if (!strncmp(argv[i], "--query=", 8)) {
665 ctx.qry.raw = xstrdup(argv[i]+8); 671 ctx.qry.raw = xstrdup(argv[i]+8);
666 } 672 }
667 if (!strncmp(argv[i], "--repo=", 7)) { 673 if (!strncmp(argv[i], "--repo=", 7)) {
668 ctx.qry.repo = xstrdup(argv[i]+7); 674 ctx.qry.repo = xstrdup(argv[i]+7);
669 } 675 }
670 if (!strncmp(argv[i], "--page=", 7)) { 676 if (!strncmp(argv[i], "--page=", 7)) {
671 ctx.qry.page = xstrdup(argv[i]+7); 677 ctx.qry.page = xstrdup(argv[i]+7);
672 } 678 }
673 if (!strncmp(argv[i], "--head=", 7)) { 679 if (!strncmp(argv[i], "--head=", 7)) {
674 ctx.qry.head = xstrdup(argv[i]+7); 680 ctx.qry.head = xstrdup(argv[i]+7);
675 ctx.qry.has_symref = 1; 681 ctx.qry.has_symref = 1;
676 } 682 }
677 if (!strncmp(argv[i], "--sha1=", 7)) { 683 if (!strncmp(argv[i], "--sha1=", 7)) {
678 ctx.qry.sha1 = xstrdup(argv[i]+7); 684 ctx.qry.sha1 = xstrdup(argv[i]+7);
679 ctx.qry.has_sha1 = 1; 685 ctx.qry.has_sha1 = 1;
680 } 686 }
681 if (!strncmp(argv[i], "--ofs=", 6)) { 687 if (!strncmp(argv[i], "--ofs=", 6)) {
682 ctx.qry.ofs = atoi(argv[i]+6); 688 ctx.qry.ofs = atoi(argv[i]+6);
683 } 689 }
684 if (!strncmp(argv[i], "--scan-tree=", 12) || 690 if (!strncmp(argv[i], "--scan-tree=", 12) ||
685 !strncmp(argv[i], "--scan-path=", 12)) { 691 !strncmp(argv[i], "--scan-path=", 12)) {
686 /* HACK: the global snapshot bitmask defines the 692 /* HACK: the global snapshot bitmask defines the
687 * set of allowed snapshot formats, but the config 693 * set of allowed snapshot formats, but the config
688 * file hasn't been parsed yet so the mask is 694 * file hasn't been parsed yet so the mask is
689 * currently 0. By setting all bits high before 695 * currently 0. By setting all bits high before
690 * scanning we make sure that any in-repo cgitrc 696 * scanning we make sure that any in-repo cgitrc
691 * snapshot setting is respected by scan_tree(). 697 * snapshot setting is respected by scan_tree().
692 * BTW: we assume that there'll never be more than 698 * BTW: we assume that there'll never be more than
693 * 255 different snapshot formats supported by cgit... 699 * 255 different snapshot formats supported by cgit...
694 */ 700 */
695 ctx.cfg.snapshots = 0xFF; 701 ctx.cfg.snapshots = 0xFF;
696 scan++; 702 scan++;
697 scan_tree(argv[i] + 12, repo_config); 703 scan_tree(argv[i] + 12, repo_config);
698 } 704 }
699 } 705 }
700 if (scan) { 706 if (scan) {
701 qsort(cgit_repolist.repos, cgit_repolist.count, 707 qsort(cgit_repolist.repos, cgit_repolist.count,
702 sizeof(struct cgit_repo), cmp_repos); 708 sizeof(struct cgit_repo), cmp_repos);
703 print_repolist(stdout, &cgit_repolist, 0); 709 print_repolist(stdout, &cgit_repolist, 0);
704 exit(0); 710 exit(0);
705 } 711 }
706} 712}
707 713
708static int calc_ttl() 714static int calc_ttl()
709{ 715{
710 if (!ctx.repo) 716 if (!ctx.repo)
711 return ctx.cfg.cache_root_ttl; 717 return ctx.cfg.cache_root_ttl;
712 718
713 if (!ctx.qry.page) 719 if (!ctx.qry.page)
714 return ctx.cfg.cache_repo_ttl; 720 return ctx.cfg.cache_repo_ttl;
715 721
716 if (ctx.qry.has_symref) 722 if (ctx.qry.has_symref)
717 return ctx.cfg.cache_dynamic_ttl; 723 return ctx.cfg.cache_dynamic_ttl;
718 724
719 if (ctx.qry.has_sha1) 725 if (ctx.qry.has_sha1)
720 return ctx.cfg.cache_static_ttl; 726 return ctx.cfg.cache_static_ttl;
721 727
722 return ctx.cfg.cache_repo_ttl; 728 return ctx.cfg.cache_repo_ttl;
723} 729}
724 730
725int main(int argc, const char **argv) 731int main(int argc, const char **argv)
726{ 732{
727 const char *path; 733 const char *path;
728 char *qry; 734 char *qry;
729 int err, ttl; 735 int err, ttl;
730 736
731 prepare_context(&ctx); 737 prepare_context(&ctx);
732 cgit_repolist.length = 0; 738 cgit_repolist.length = 0;
733 cgit_repolist.count = 0; 739 cgit_repolist.count = 0;
734 cgit_repolist.repos = NULL; 740 cgit_repolist.repos = NULL;
735 741
736 cgit_parse_args(argc, argv); 742 cgit_parse_args(argc, argv);
737 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); 743 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
738 ctx.repo = NULL; 744 ctx.repo = NULL;
739 http_parse_querystring(ctx.qry.raw, querystring_cb); 745 http_parse_querystring(ctx.qry.raw, querystring_cb);
740 746
741 /* If virtual-root isn't specified in cgitrc, lets pretend 747 /* If virtual-root isn't specified in cgitrc, lets pretend
742 * that virtual-root equals SCRIPT_NAME. 748 * that virtual-root equals SCRIPT_NAME.
743 */ 749 */
744 if (!ctx.cfg.virtual_root) 750 if (!ctx.cfg.virtual_root)
745 ctx.cfg.virtual_root = ctx.cfg.script_name; 751 ctx.cfg.virtual_root = ctx.cfg.script_name;
746 752
747 /* If no url parameter is specified on the querystring, lets 753 /* If no url parameter is specified on the querystring, lets
748 * use PATH_INFO as url. This allows cgit to work with virtual 754 * use PATH_INFO as url. This allows cgit to work with virtual
749 * urls without the need for rewriterules in the webserver (as 755 * urls without the need for rewriterules in the webserver (as
750 * long as PATH_INFO is included in the cache lookup key). 756 * long as PATH_INFO is included in the cache lookup key).
751 */ 757 */
752 path = ctx.env.path_info; 758 path = ctx.env.path_info;
753 if (!ctx.qry.url && path) { 759 if (!ctx.qry.url && path) {
754 if (path[0] == '/') 760 if (path[0] == '/')
755 path++; 761 path++;
756 ctx.qry.url = xstrdup(path); 762 ctx.qry.url = xstrdup(path);
757 if (ctx.qry.raw) { 763 if (ctx.qry.raw) {
758 qry = ctx.qry.raw; 764 qry = ctx.qry.raw;
759 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 765 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
760 free(qry); 766 free(qry);
761 } else 767 } else
762 ctx.qry.raw = xstrdup(ctx.qry.url); 768 ctx.qry.raw = xstrdup(ctx.qry.url);
763 cgit_parse_url(ctx.qry.url); 769 cgit_parse_url(ctx.qry.url);
764 } 770 }
765 771
766 ttl = calc_ttl(); 772 ttl = calc_ttl();
767 ctx.page.expires += ttl*60; 773 ctx.page.expires += ttl*60;
768 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 774 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
769 ctx.cfg.nocache = 1; 775 ctx.cfg.nocache = 1;
770 if (ctx.cfg.nocache) 776 if (ctx.cfg.nocache)
771 ctx.cfg.cache_size = 0; 777 ctx.cfg.cache_size = 0;
772 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 778 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
773 ctx.qry.raw, ttl, process_request, &ctx); 779 ctx.qry.raw, ttl, process_request, &ctx);
774 if (err) 780 if (err)
775 cgit_print_error(fmt("Error processing page: %s (%d)", 781 cgit_print_error(fmt("Error processing page: %s (%d)",
776 strerror(err), err)); 782 strerror(err), err));
777 return err; 783 return err;
778} 784}
diff --git a/cgit.css b/cgit.css
index a2a685b..008cff8 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,759 +1,777 @@
1body, table, form { 1body, table, form {
2 padding: 0em; 2 padding: 0em;
3 margin: 0em; 3 margin: 0em;
4} 4}
5 5
6body { 6body {
7 font-family: sans-serif; 7 font-family: sans-serif;
8 font-size: 10pt; 8 font-size: 10pt;
9 color: #333; 9 color: #333;
10 background: white; 10 background: white;
11 padding: 4px; 11 padding: 4px;
12} 12}
13 13
14a { 14a {
15 color: blue; 15 color: blue;
16 text-decoration: none; 16 text-decoration: none;
17} 17}
18 18
19a:hover { 19a:hover {
20 text-decoration: underline; 20 text-decoration: underline;
21} 21}
22 22
23table { 23table {
24 border-collapse: collapse; 24 border-collapse: collapse;
25} 25}
26 26
27table#header { 27table#header {
28 width: 100%; 28 width: 100%;
29 margin-bottom: 1em; 29 margin-bottom: 1em;
30} 30}
31 31
32table#header td.logo { 32table#header td.logo {
33 width: 96px; 33 width: 96px;
34} 34}
35 35
36table#header td.main { 36table#header td.main {
37 font-size: 250%; 37 font-size: 250%;
38 padding-left: 10px; 38 padding-left: 10px;
39 white-space: nowrap; 39 white-space: nowrap;
40} 40}
41 41
42table#header td.main a { 42table#header td.main a {
43 color: #000; 43 color: #000;
44} 44}
45 45
46table#header td.form { 46table#header td.form {
47 text-align: right; 47 text-align: right;
48 vertical-align: bottom; 48 vertical-align: bottom;
49 padding-right: 1em; 49 padding-right: 1em;
50 padding-bottom: 2px; 50 padding-bottom: 2px;
51 white-space: nowrap; 51 white-space: nowrap;
52} 52}
53 53
54table#header td.form form, 54table#header td.form form,
55table#header td.form input, 55table#header td.form input,
56table#header td.form select { 56table#header td.form select {
57 font-size: 90%; 57 font-size: 90%;
58} 58}
59 59
60table#header td.sub { 60table#header td.sub {
61 color: #777; 61 color: #777;
62 border-top: solid 1px #ccc; 62 border-top: solid 1px #ccc;
63 padding-left: 10px; 63 padding-left: 10px;
64} 64}
65 65
66table.tabs { 66table.tabs {
67 border-bottom: solid 3px #ccc; 67 border-bottom: solid 3px #ccc;
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
71 width: 100%; 71 width: 100%;
72} 72}
73 73
74table.tabs td { 74table.tabs td {
75 padding: 0px 1em; 75 padding: 0px 1em;
76 vertical-align: bottom; 76 vertical-align: bottom;
77} 77}
78 78
79table.tabs td a { 79table.tabs td a {
80 padding: 2px 0.75em; 80 padding: 2px 0.75em;
81 color: #777; 81 color: #777;
82 font-size: 110%; 82 font-size: 110%;
83} 83}
84 84
85table.tabs td a.active { 85table.tabs td a.active {
86 color: #000; 86 color: #000;
87 background-color: #ccc; 87 background-color: #ccc;
88} 88}
89 89
90table.tabs td.form { 90table.tabs td.form {
91 text-align: right; 91 text-align: right;
92} 92}
93 93
94table.tabs td.form form { 94table.tabs td.form form {
95 padding-bottom: 2px; 95 padding-bottom: 2px;
96 font-size: 90%; 96 font-size: 90%;
97 white-space: nowrap; 97 white-space: nowrap;
98} 98}
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.path { 105div.path {
106 margin: 0px; 106 margin: 0px;
107 padding: 5px 2em 2px 2em; 107 padding: 5px 2em 2px 2em;
108 color: #000; 108 color: #000;
109 background-color: #eee; 109 background-color: #eee;
110} 110}
111 111
112div.content { 112div.content {
113 margin: 0px; 113 margin: 0px;
114 padding: 2em; 114 padding: 2em;
115 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
116} 116}
117 117
118 118
119table.list { 119table.list {
120 width: 100%; 120 width: 100%;
121 border: none; 121 border: none;
122 border-collapse: collapse; 122 border-collapse: collapse;
123} 123}
124 124
125table.list tr { 125table.list tr {
126 background: white; 126 background: white;
127} 127}
128 128
129table.list tr.logheader { 129table.list tr.logheader {
130 background: #eee; 130 background: #eee;
131} 131}
132 132
133table.list tr:hover { 133table.list tr:hover {
134 background: #eee; 134 background: #eee;
135} 135}
136 136
137table.list tr.nohover:hover { 137table.list tr.nohover:hover {
138 background: white; 138 background: white;
139} 139}
140 140
141table.list th { 141table.list th {
142 font-weight: bold; 142 font-weight: bold;
143 /* color: #888; 143 /* color: #888;
144 border-top: dashed 1px #888; 144 border-top: dashed 1px #888;
145 border-bottom: dashed 1px #888; 145 border-bottom: dashed 1px #888;
146 */ 146 */
147 padding: 0.1em 0.5em 0.05em 0.5em; 147 padding: 0.1em 0.5em 0.05em 0.5em;
148 vertical-align: baseline; 148 vertical-align: baseline;
149} 149}
150 150
151table.list td { 151table.list td {
152 border: none; 152 border: none;
153 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
154} 154}
155 155
156table.list td.logsubject { 156table.list td.commitgraph {
157 font-family: monospace; 157 font-family: monospace;
158 font-weight: bold; 158 white-space: pre;
159} 159}
160 160
161table.list td.logmsg { 161table.list td.commitgraph .column1 {
162 font-family: monospace; 162 color: #a00;
163 white-space: pre; 163}
164 padding: 1em 0.5em 2em 0.5em; 164
165table.list td.commitgraph .column2 {
166 color: #0a0;
167}
168
169table.list td.commitgraph .column3 {
170 color: #aa0;
165} 171}
166 172
167table.list td.lognotes-label { 173table.list td.commitgraph .column4 {
168 text-align:right; 174 color: #00a;
169 vertical-align:top;
170} 175}
171 176
172table.list td.lognotes { 177table.list td.commitgraph .column5 {
178 color: #a0a;
179}
180
181table.list td.commitgraph .column6 {
182 color: #0aa;
183}
184
185table.list td.logsubject {
186 font-family: monospace;
187 font-weight: bold;
188}
189
190table.list td.logmsg {
173 font-family: monospace; 191 font-family: monospace;
174 white-space: pre; 192 white-space: pre;
175 padding: 0em 0.5em 2em 0.5em; 193 padding: 0 0.5em;
176} 194}
177 195
178table.list td a { 196table.list td a {
179 color: black; 197 color: black;
180} 198}
181 199
182table.list td a.ls-dir { 200table.list td a.ls-dir {
183 font-weight: bold; 201 font-weight: bold;
184 color: #00f; 202 color: #00f;
185} 203}
186 204
187table.list td a:hover { 205table.list td a:hover {
188 color: #00f; 206 color: #00f;
189} 207}
190 208
191img { 209img {
192 border: none; 210 border: none;
193} 211}
194 212
195input#switch-btn { 213input#switch-btn {
196 margin: 2px 0px 0px 0px; 214 margin: 2px 0px 0px 0px;
197} 215}
198 216
199td#sidebar input.txt { 217td#sidebar input.txt {
200 width: 100%; 218 width: 100%;
201 margin: 2px 0px 0px 0px; 219 margin: 2px 0px 0px 0px;
202} 220}
203 221
204table#grid { 222table#grid {
205 margin: 0px; 223 margin: 0px;
206} 224}
207 225
208td#content { 226td#content {
209 vertical-align: top; 227 vertical-align: top;
210 padding: 1em 2em 1em 1em; 228 padding: 1em 2em 1em 1em;
211 border: none; 229 border: none;
212} 230}
213 231
214div#summary { 232div#summary {
215 vertical-align: top; 233 vertical-align: top;
216 margin-bottom: 1em; 234 margin-bottom: 1em;
217} 235}
218 236
219table#downloads { 237table#downloads {
220 float: right; 238 float: right;
221 border-collapse: collapse; 239 border-collapse: collapse;
222 border: solid 1px #777; 240 border: solid 1px #777;
223 margin-left: 0.5em; 241 margin-left: 0.5em;
224 margin-bottom: 0.5em; 242 margin-bottom: 0.5em;
225} 243}
226 244
227table#downloads th { 245table#downloads th {
228 background-color: #ccc; 246 background-color: #ccc;
229} 247}
230 248
231div#blob { 249div#blob {
232 border: solid 1px black; 250 border: solid 1px black;
233} 251}
234 252
235div.error { 253div.error {
236 color: red; 254 color: red;
237 font-weight: bold; 255 font-weight: bold;
238 margin: 1em 2em; 256 margin: 1em 2em;
239} 257}
240 258
241a.ls-blob, a.ls-dir, a.ls-mod { 259a.ls-blob, a.ls-dir, a.ls-mod {
242 font-family: monospace; 260 font-family: monospace;
243} 261}
244 262
245td.ls-size { 263td.ls-size {
246 text-align: right; 264 text-align: right;
247 font-family: monospace; 265 font-family: monospace;
248 width: 10em; 266 width: 10em;
249} 267}
250 268
251td.ls-mode { 269td.ls-mode {
252 font-family: monospace; 270 font-family: monospace;
253 width: 10em; 271 width: 10em;
254} 272}
255 273
256table.blob { 274table.blob {
257 margin-top: 0.5em; 275 margin-top: 0.5em;
258 border-top: solid 1px black; 276 border-top: solid 1px black;
259} 277}
260 278
261table.blob td.lines { 279table.blob td.lines {
262 margin: 0; padding: 0 0 0 0.5em; 280 margin: 0; padding: 0 0 0 0.5em;
263 vertical-align: top; 281 vertical-align: top;
264 color: black; 282 color: black;
265} 283}
266 284
267table.blob td.linenumbers { 285table.blob td.linenumbers {
268 margin: 0; padding: 0 0.5em 0 0.5em; 286 margin: 0; padding: 0 0.5em 0 0.5em;
269 vertical-align: top; 287 vertical-align: top;
270 text-align: right; 288 text-align: right;
271 border-right: 1px solid gray; 289 border-right: 1px solid gray;
272} 290}
273 291
274table.blob pre { 292table.blob pre {
275 padding: 0; margin: 0; 293 padding: 0; margin: 0;
276} 294}
277 295
278table.blob a.no { 296table.blob a.no {
279 color: gray; 297 color: gray;
280 text-align: right; 298 text-align: right;
281 text-decoration: none; 299 text-decoration: none;
282} 300}
283 301
284table.blob a.no a:hover { 302table.blob a.no a:hover {
285 color: black; 303 color: black;
286} 304}
287 305
288table.bin-blob { 306table.bin-blob {
289 margin-top: 0.5em; 307 margin-top: 0.5em;
290 border: solid 1px black; 308 border: solid 1px black;
291} 309}
292 310
293table.bin-blob th { 311table.bin-blob th {
294 font-family: monospace; 312 font-family: monospace;
295 white-space: pre; 313 white-space: pre;
296 border: solid 1px #777; 314 border: solid 1px #777;
297 padding: 0.5em 1em; 315 padding: 0.5em 1em;
298} 316}
299 317
300table.bin-blob td { 318table.bin-blob td {
301 font-family: monospace; 319 font-family: monospace;
302 white-space: pre; 320 white-space: pre;
303 border-left: solid 1px #777; 321 border-left: solid 1px #777;
304 padding: 0em 1em; 322 padding: 0em 1em;
305} 323}
306 324
307table.nowrap td { 325table.nowrap td {
308 white-space: nowrap; 326 white-space: nowrap;
309} 327}
310 328
311table.commit-info { 329table.commit-info {
312 border-collapse: collapse; 330 border-collapse: collapse;
313 margin-top: 1.5em; 331 margin-top: 1.5em;
314} 332}
315 333
316table.commit-info th { 334table.commit-info th {
317 text-align: left; 335 text-align: left;
318 font-weight: normal; 336 font-weight: normal;
319 padding: 0.1em 1em 0.1em 0.1em; 337 padding: 0.1em 1em 0.1em 0.1em;
320 vertical-align: top; 338 vertical-align: top;
321} 339}
322 340
323table.commit-info td { 341table.commit-info td {
324 font-weight: normal; 342 font-weight: normal;
325 padding: 0.1em 1em 0.1em 0.1em; 343 padding: 0.1em 1em 0.1em 0.1em;
326} 344}
327 345
328div.commit-subject { 346div.commit-subject {
329 font-weight: bold; 347 font-weight: bold;
330 font-size: 125%; 348 font-size: 125%;
331 margin: 1.5em 0em 0.5em 0em; 349 margin: 1.5em 0em 0.5em 0em;
332 padding: 0em; 350 padding: 0em;
333} 351}
334 352
335div.commit-msg { 353div.commit-msg {
336 white-space: pre; 354 white-space: pre;
337 font-family: monospace; 355 font-family: monospace;
338} 356}
339 357
340div.notes-header { 358div.notes-header {
341 font-weight: bold; 359 font-weight: bold;
342 padding-top: 1.5em; 360 padding-top: 1.5em;
343} 361}
344 362
345div.notes { 363div.notes {
346 white-space: pre; 364 white-space: pre;
347 font-family: monospace; 365 font-family: monospace;
348 border: solid 1px #ee9; 366 border: solid 1px #ee9;
349 background-color: #ffd; 367 background-color: #ffd;
350 padding: 0.3em 2em 0.3em 1em; 368 padding: 0.3em 2em 0.3em 1em;
351 float: left; 369 float: left;
352} 370}
353 371
354div.notes-footer { 372div.notes-footer {
355 clear: left; 373 clear: left;
356} 374}
357 375
358div.diffstat-header { 376div.diffstat-header {
359 font-weight: bold; 377 font-weight: bold;
360 padding-top: 1.5em; 378 padding-top: 1.5em;
361} 379}
362 380
363table.diffstat { 381table.diffstat {
364 border-collapse: collapse; 382 border-collapse: collapse;
365 border: solid 1px #aaa; 383 border: solid 1px #aaa;
366 background-color: #eee; 384 background-color: #eee;
367} 385}
368 386
369table.diffstat th { 387table.diffstat th {
370 font-weight: normal; 388 font-weight: normal;
371 text-align: left; 389 text-align: left;
372 text-decoration: underline; 390 text-decoration: underline;
373 padding: 0.1em 1em 0.1em 0.1em; 391 padding: 0.1em 1em 0.1em 0.1em;
374 font-size: 100%; 392 font-size: 100%;
375} 393}
376 394
377table.diffstat td { 395table.diffstat td {
378 padding: 0.2em 0.2em 0.1em 0.1em; 396 padding: 0.2em 0.2em 0.1em 0.1em;
379 font-size: 100%; 397 font-size: 100%;
380 border: none; 398 border: none;
381} 399}
382 400
383table.diffstat td.mode { 401table.diffstat td.mode {
384 white-space: nowrap; 402 white-space: nowrap;
385} 403}
386 404
387table.diffstat td span.modechange { 405table.diffstat td span.modechange {
388 padding-left: 1em; 406 padding-left: 1em;
389 color: red; 407 color: red;
390} 408}
391 409
392table.diffstat td.add a { 410table.diffstat td.add a {
393 color: green; 411 color: green;
394} 412}
395 413
396table.diffstat td.del a { 414table.diffstat td.del a {
397 color: red; 415 color: red;
398} 416}
399 417
400table.diffstat td.upd a { 418table.diffstat td.upd a {
401 color: blue; 419 color: blue;
402} 420}
403 421
404table.diffstat td.graph { 422table.diffstat td.graph {
405 width: 500px; 423 width: 500px;
406 vertical-align: middle; 424 vertical-align: middle;
407} 425}
408 426
409table.diffstat td.graph table { 427table.diffstat td.graph table {
410 border: none; 428 border: none;
411} 429}
412 430
413table.diffstat td.graph td { 431table.diffstat td.graph td {
414 padding: 0px; 432 padding: 0px;
415 border: 0px; 433 border: 0px;
416 height: 7pt; 434 height: 7pt;
417} 435}
418 436
419table.diffstat td.graph td.add { 437table.diffstat td.graph td.add {
420 background-color: #5c5; 438 background-color: #5c5;
421} 439}
422 440
423table.diffstat td.graph td.rem { 441table.diffstat td.graph td.rem {
424 background-color: #c55; 442 background-color: #c55;
425} 443}
426 444
427div.diffstat-summary { 445div.diffstat-summary {
428 color: #888; 446 color: #888;
429 padding-top: 0.5em; 447 padding-top: 0.5em;
430} 448}
431 449
432table.diff { 450table.diff {
433 width: 100%; 451 width: 100%;
434} 452}
435 453
436table.diff td { 454table.diff td {
437 font-family: monospace; 455 font-family: monospace;
438 white-space: pre; 456 white-space: pre;
439} 457}
440 458
441table.diff td div.head { 459table.diff td div.head {
442 font-weight: bold; 460 font-weight: bold;
443 margin-top: 1em; 461 margin-top: 1em;
444 color: black; 462 color: black;
445} 463}
446 464
447table.diff td div.hunk { 465table.diff td div.hunk {
448 color: #009; 466 color: #009;
449} 467}
450 468
451table.diff td div.add { 469table.diff td div.add {
452 color: green; 470 color: green;
453} 471}
454 472
455table.diff td div.del { 473table.diff td div.del {
456 color: red; 474 color: red;
457} 475}
458 476
459.sha1 { 477.sha1 {
460 font-family: monospace; 478 font-family: monospace;
461 font-size: 90%; 479 font-size: 90%;
462} 480}
463 481
464.left { 482.left {
465 text-align: left; 483 text-align: left;
466} 484}
467 485
468.right { 486.right {
469 text-align: right; 487 text-align: right;
470} 488}
471 489
472table.list td.reposection { 490table.list td.reposection {
473 font-style: italic; 491 font-style: italic;
474 color: #888; 492 color: #888;
475} 493}
476 494
477a.button { 495a.button {
478 font-size: 80%; 496 font-size: 80%;
479 padding: 0em 0.5em; 497 padding: 0em 0.5em;
480} 498}
481 499
482a.primary { 500a.primary {
483 font-size: 100%; 501 font-size: 100%;
484} 502}
485 503
486a.secondary { 504a.secondary {
487 font-size: 90%; 505 font-size: 90%;
488} 506}
489 507
490td.toplevel-repo { 508td.toplevel-repo {
491 509
492} 510}
493 511
494table.list td.sublevel-repo { 512table.list td.sublevel-repo {
495 padding-left: 1.5em; 513 padding-left: 1.5em;
496} 514}
497 515
498div.pager { 516div.pager {
499 text-align: center; 517 text-align: center;
500 margin: 1em 0em 0em 0em; 518 margin: 1em 0em 0em 0em;
501} 519}
502 520
503div.pager a { 521div.pager a {
504 color: #777; 522 color: #777;
505 margin: 0em 0.5em; 523 margin: 0em 0.5em;
506} 524}
507 525
508span.age-mins { 526span.age-mins {
509 font-weight: bold; 527 font-weight: bold;
510 color: #080; 528 color: #080;
511} 529}
512 530
513span.age-hours { 531span.age-hours {
514 color: #080; 532 color: #080;
515} 533}
516 534
517span.age-days { 535span.age-days {
518 color: #040; 536 color: #040;
519} 537}
520 538
521span.age-weeks { 539span.age-weeks {
522 color: #444; 540 color: #444;
523} 541}
524 542
525span.age-months { 543span.age-months {
526 color: #888; 544 color: #888;
527} 545}
528 546
529span.age-years { 547span.age-years {
530 color: #bbb; 548 color: #bbb;
531} 549}
532div.footer { 550div.footer {
533 margin-top: 0.5em; 551 margin-top: 0.5em;
534 text-align: center; 552 text-align: center;
535 font-size: 80%; 553 font-size: 80%;
536 color: #ccc; 554 color: #ccc;
537} 555}
538a.branch-deco { 556a.branch-deco {
539 margin: 0px 0.5em; 557 margin: 0px 0.5em;
540 padding: 0px 0.25em; 558 padding: 0px 0.25em;
541 background-color: #88ff88; 559 background-color: #88ff88;
542 border: solid 1px #007700; 560 border: solid 1px #007700;
543} 561}
544a.tag-deco { 562a.tag-deco {
545 margin: 0px 0.5em; 563 margin: 0px 0.5em;
546 padding: 0px 0.25em; 564 padding: 0px 0.25em;
547 background-color: #ffff88; 565 background-color: #ffff88;
548 border: solid 1px #777700; 566 border: solid 1px #777700;
549} 567}
550a.remote-deco { 568a.remote-deco {
551 margin: 0px 0.5em; 569 margin: 0px 0.5em;
552 padding: 0px 0.25em; 570 padding: 0px 0.25em;
553 background-color: #ccccff; 571 background-color: #ccccff;
554 border: solid 1px #000077; 572 border: solid 1px #000077;
555} 573}
556a.deco { 574a.deco {
557 margin: 0px 0.5em; 575 margin: 0px 0.5em;
558 padding: 0px 0.25em; 576 padding: 0px 0.25em;
559 background-color: #ff8888; 577 background-color: #ff8888;
560 border: solid 1px #770000; 578 border: solid 1px #770000;
561} 579}
562 580
563div.commit-subject a.branch-deco, 581div.commit-subject a.branch-deco,
564div.commit-subject a.tag-deco, 582div.commit-subject a.tag-deco,
565div.commit-subject a.remote-deco, 583div.commit-subject a.remote-deco,
566div.commit-subject a.deco { 584div.commit-subject a.deco {
567 margin-left: 1em; 585 margin-left: 1em;
568 font-size: 75%; 586 font-size: 75%;
569} 587}
570 588
571table.stats { 589table.stats {
572 border: solid 1px black; 590 border: solid 1px black;
573 border-collapse: collapse; 591 border-collapse: collapse;
574} 592}
575 593
576table.stats th { 594table.stats th {
577 text-align: left; 595 text-align: left;
578 padding: 1px 0.5em; 596 padding: 1px 0.5em;
579 background-color: #eee; 597 background-color: #eee;
580 border: solid 1px black; 598 border: solid 1px black;
581} 599}
582 600
583table.stats td { 601table.stats td {
584 text-align: right; 602 text-align: right;
585 padding: 1px 0.5em; 603 padding: 1px 0.5em;
586 border: solid 1px black; 604 border: solid 1px black;
587} 605}
588 606
589table.stats td.total { 607table.stats td.total {
590 font-weight: bold; 608 font-weight: bold;
591 text-align: left; 609 text-align: left;
592} 610}
593 611
594table.stats td.sum { 612table.stats td.sum {
595 color: #c00; 613 color: #c00;
596 font-weight: bold; 614 font-weight: bold;
597 /*background-color: #eee; */ 615 /*background-color: #eee; */
598} 616}
599 617
600table.stats td.left { 618table.stats td.left {
601 text-align: left; 619 text-align: left;
602} 620}
603 621
604table.vgraph { 622table.vgraph {
605 border-collapse: separate; 623 border-collapse: separate;
606 border: solid 1px black; 624 border: solid 1px black;
607 height: 200px; 625 height: 200px;
608} 626}
609 627
610table.vgraph th { 628table.vgraph th {
611 background-color: #eee; 629 background-color: #eee;
612 font-weight: bold; 630 font-weight: bold;
613 border: solid 1px white; 631 border: solid 1px white;
614 padding: 1px 0.5em; 632 padding: 1px 0.5em;
615} 633}
616 634
617table.vgraph td { 635table.vgraph td {
618 vertical-align: bottom; 636 vertical-align: bottom;
619 padding: 0px 10px; 637 padding: 0px 10px;
620} 638}
621 639
622table.vgraph div.bar { 640table.vgraph div.bar {
623 background-color: #eee; 641 background-color: #eee;
624} 642}
625 643
626table.hgraph { 644table.hgraph {
627 border: solid 1px black; 645 border: solid 1px black;
628 width: 800px; 646 width: 800px;
629} 647}
630 648
631table.hgraph th { 649table.hgraph th {
632 background-color: #eee; 650 background-color: #eee;
633 font-weight: bold; 651 font-weight: bold;
634 border: solid 1px black; 652 border: solid 1px black;
635 padding: 1px 0.5em; 653 padding: 1px 0.5em;
636} 654}
637 655
638table.hgraph td { 656table.hgraph td {
639 vertical-align: center; 657 vertical-align: center;
640 padding: 2px 2px; 658 padding: 2px 2px;
641} 659}
642 660
643table.hgraph div.bar { 661table.hgraph div.bar {
644 background-color: #eee; 662 background-color: #eee;
645 height: 1em; 663 height: 1em;
646} 664}
647 665
648table.ssdiff { 666table.ssdiff {
649 width: 100%; 667 width: 100%;
650} 668}
651 669
652table.ssdiff td { 670table.ssdiff td {
653 font-size: 75%; 671 font-size: 75%;
654 font-family: monospace; 672 font-family: monospace;
655 white-space: pre; 673 white-space: pre;
656 padding: 1px 4px 1px 4px; 674 padding: 1px 4px 1px 4px;
657 border-left: solid 1px #aaa; 675 border-left: solid 1px #aaa;
658 border-right: solid 1px #aaa; 676 border-right: solid 1px #aaa;
659} 677}
660 678
661table.ssdiff td.add { 679table.ssdiff td.add {
662 color: black; 680 color: black;
663 background: #cfc; 681 background: #cfc;
664 min-width: 50%; 682 min-width: 50%;
665} 683}
666 684
667table.ssdiff td.add_dark { 685table.ssdiff td.add_dark {
668 color: black; 686 color: black;
669 background: #aca; 687 background: #aca;
670 min-width: 50%; 688 min-width: 50%;
671} 689}
672 690
673table.ssdiff span.add { 691table.ssdiff span.add {
674 background: #cfc; 692 background: #cfc;
675 font-weight: bold; 693 font-weight: bold;
676} 694}
677 695
678table.ssdiff td.del { 696table.ssdiff td.del {
679 color: black; 697 color: black;
680 background: #fcc; 698 background: #fcc;
681 min-width: 50%; 699 min-width: 50%;
682} 700}
683 701
684table.ssdiff td.del_dark { 702table.ssdiff td.del_dark {
685 color: black; 703 color: black;
686 background: #caa; 704 background: #caa;
687 min-width: 50%; 705 min-width: 50%;
688} 706}
689 707
690table.ssdiff span.del { 708table.ssdiff span.del {
691 background: #fcc; 709 background: #fcc;
692 font-weight: bold; 710 font-weight: bold;
693} 711}
694 712
695table.ssdiff td.changed { 713table.ssdiff td.changed {
696 color: black; 714 color: black;
697 background: #ffc; 715 background: #ffc;
698 min-width: 50%; 716 min-width: 50%;
699} 717}
700 718
701table.ssdiff td.changed_dark { 719table.ssdiff td.changed_dark {
702 color: black; 720 color: black;
703 background: #cca; 721 background: #cca;
704 min-width: 50%; 722 min-width: 50%;
705} 723}
706 724
707table.ssdiff td.lineno { 725table.ssdiff td.lineno {
708 color: black; 726 color: black;
709 background: #eee; 727 background: #eee;
710 text-align: right; 728 text-align: right;
711 width: 3em; 729 width: 3em;
712 min-width: 3em; 730 min-width: 3em;
713} 731}
714 732
715table.ssdiff td.hunk { 733table.ssdiff td.hunk {
716 color: #black; 734 color: #black;
717 background: #ccf; 735 background: #ccf;
718 border-top: solid 1px #aaa; 736 border-top: solid 1px #aaa;
719 border-bottom: solid 1px #aaa; 737 border-bottom: solid 1px #aaa;
720} 738}
721 739
722table.ssdiff td.head { 740table.ssdiff td.head {
723 border-top: solid 1px #aaa; 741 border-top: solid 1px #aaa;
724 border-bottom: solid 1px #aaa; 742 border-bottom: solid 1px #aaa;
725} 743}
726 744
727table.ssdiff td.head div.head { 745table.ssdiff td.head div.head {
728 font-weight: bold; 746 font-weight: bold;
729 color: black; 747 color: black;
730} 748}
731 749
732table.ssdiff td.foot { 750table.ssdiff td.foot {
733 border-top: solid 1px #aaa; 751 border-top: solid 1px #aaa;
734 border-left: none; 752 border-left: none;
735 border-right: none; 753 border-right: none;
736 border-bottom: none; 754 border-bottom: none;
737} 755}
738 756
739table.ssdiff td.space { 757table.ssdiff td.space {
740 border: none; 758 border: none;
741} 759}
742 760
743table.ssdiff td.space div { 761table.ssdiff td.space div {
744 min-height: 3em; 762 min-height: 3em;
745} 763}
746 764
747/* Syntax highlighting */ 765/* Syntax highlighting */
748table.blob .num { color:#2928ff; } 766table.blob .num { color:#2928ff; }
749table.blob .esc { color:#ff00ff; } 767table.blob .esc { color:#ff00ff; }
750table.blob .str { color:#ff0000; } 768table.blob .str { color:#ff0000; }
751table.blob .dstr { color:#818100; } 769table.blob .dstr { color:#818100; }
752table.blob .slc { color:#838183; font-style:italic; } 770table.blob .slc { color:#838183; font-style:italic; }
753table.blob .com { color:#838183; font-style:italic; } 771table.blob .com { color:#838183; font-style:italic; }
754table.blob .dir { color:#008200; } 772table.blob .dir { color:#008200; }
755table.blob .sym { color:#000000; } 773table.blob .sym { color:#000000; }
756table.blob .kwa { color:#000000; font-weight:bold; } 774table.blob .kwa { color:#000000; font-weight:bold; }
757table.blob .kwb { color:#830000; } 775table.blob .kwb { color:#830000; }
758table.blob .kwc { color:#000000; font-weight:bold; } 776table.blob .kwc { color:#000000; font-weight:bold; }
759table.blob .kwd { color:#010181; } 777table.blob .kwd { color:#010181; }
diff --git a/cgit.h b/cgit.h
index f5f68ac..bed770b 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,318 +1,321 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22#include <notes.h> 22#include <notes.h>
23#include <graph.h>
23 24
24 25
25/* 26/*
26 * Dateformats used on misc. pages 27 * Dateformats used on misc. pages
27 */ 28 */
28#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 29#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
29#define FMT_SHORTDATE "%Y-%m-%d" 30#define FMT_SHORTDATE "%Y-%m-%d"
30#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 31#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
31 32
32 33
33/* 34/*
34 * Limits used for relative dates 35 * Limits used for relative dates
35 */ 36 */
36#define TM_MIN 60 37#define TM_MIN 60
37#define TM_HOUR (TM_MIN * 60) 38#define TM_HOUR (TM_MIN * 60)
38#define TM_DAY (TM_HOUR * 24) 39#define TM_DAY (TM_HOUR * 24)
39#define TM_WEEK (TM_DAY * 7) 40#define TM_WEEK (TM_DAY * 7)
40#define TM_YEAR (TM_DAY * 365) 41#define TM_YEAR (TM_DAY * 365)
41#define TM_MONTH (TM_YEAR / 12.0) 42#define TM_MONTH (TM_YEAR / 12.0)
42 43
43 44
44/* 45/*
45 * Default encoding 46 * Default encoding
46 */ 47 */
47#define PAGE_ENCODING "UTF-8" 48#define PAGE_ENCODING "UTF-8"
48 49
49typedef void (*configfn)(const char *name, const char *value); 50typedef void (*configfn)(const char *name, const char *value);
50typedef void (*filepair_fn)(struct diff_filepair *pair); 51typedef void (*filepair_fn)(struct diff_filepair *pair);
51typedef void (*linediff_fn)(char *line, int len); 52typedef void (*linediff_fn)(char *line, int len);
52 53
53struct cgit_filter { 54struct cgit_filter {
54 char *cmd; 55 char *cmd;
55 char **argv; 56 char **argv;
56 int old_stdout; 57 int old_stdout;
57 int pipe_fh[2]; 58 int pipe_fh[2];
58 int pid; 59 int pid;
59 int exitstatus; 60 int exitstatus;
60}; 61};
61 62
62struct cgit_repo { 63struct cgit_repo {
63 char *url; 64 char *url;
64 char *name; 65 char *name;
65 char *path; 66 char *path;
66 char *desc; 67 char *desc;
67 char *owner; 68 char *owner;
68 char *defbranch; 69 char *defbranch;
69 char *module_link; 70 char *module_link;
70 char *readme; 71 char *readme;
71 char *section; 72 char *section;
72 char *clone_url; 73 char *clone_url;
73 int snapshots; 74 int snapshots;
75 int enable_commit_graph;
74 int enable_log_filecount; 76 int enable_log_filecount;
75 int enable_log_linecount; 77 int enable_log_linecount;
76 int enable_remote_branches; 78 int enable_remote_branches;
77 int enable_subject_links; 79 int enable_subject_links;
78 int max_stats; 80 int max_stats;
79 time_t mtime; 81 time_t mtime;
80 struct cgit_filter *about_filter; 82 struct cgit_filter *about_filter;
81 struct cgit_filter *commit_filter; 83 struct cgit_filter *commit_filter;
82 struct cgit_filter *source_filter; 84 struct cgit_filter *source_filter;
83}; 85};
84 86
85typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 87typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
86 const char *value); 88 const char *value);
87 89
88struct cgit_repolist { 90struct cgit_repolist {
89 int length; 91 int length;
90 int count; 92 int count;
91 struct cgit_repo *repos; 93 struct cgit_repo *repos;
92}; 94};
93 95
94struct commitinfo { 96struct commitinfo {
95 struct commit *commit; 97 struct commit *commit;
96 char *author; 98 char *author;
97 char *author_email; 99 char *author_email;
98 unsigned long author_date; 100 unsigned long author_date;
99 char *committer; 101 char *committer;
100 char *committer_email; 102 char *committer_email;
101 unsigned long committer_date; 103 unsigned long committer_date;
102 char *subject; 104 char *subject;
103 char *msg; 105 char *msg;
104 char *msg_encoding; 106 char *msg_encoding;
105}; 107};
106 108
107struct taginfo { 109struct taginfo {
108 char *tagger; 110 char *tagger;
109 char *tagger_email; 111 char *tagger_email;
110 unsigned long tagger_date; 112 unsigned long tagger_date;
111 char *msg; 113 char *msg;
112}; 114};
113 115
114struct refinfo { 116struct refinfo {
115 const char *refname; 117 const char *refname;
116 struct object *object; 118 struct object *object;
117 union { 119 union {
118 struct taginfo *tag; 120 struct taginfo *tag;
119 struct commitinfo *commit; 121 struct commitinfo *commit;
120 }; 122 };
121}; 123};
122 124
123struct reflist { 125struct reflist {
124 struct refinfo **refs; 126 struct refinfo **refs;
125 int alloc; 127 int alloc;
126 int count; 128 int count;
127}; 129};
128 130
129struct cgit_query { 131struct cgit_query {
130 int has_symref; 132 int has_symref;
131 int has_sha1; 133 int has_sha1;
132 char *raw; 134 char *raw;
133 char *repo; 135 char *repo;
134 char *page; 136 char *page;
135 char *search; 137 char *search;
136 char *grep; 138 char *grep;
137 char *head; 139 char *head;
138 char *sha1; 140 char *sha1;
139 char *sha2; 141 char *sha2;
140 char *path; 142 char *path;
141 char *name; 143 char *name;
142 char *mimetype; 144 char *mimetype;
143 char *url; 145 char *url;
144 char *period; 146 char *period;
145 int ofs; 147 int ofs;
146 int nohead; 148 int nohead;
147 char *sort; 149 char *sort;
148 int showmsg; 150 int showmsg;
149 int ssdiff; 151 int ssdiff;
150 int show_all; 152 int show_all;
151 int context; 153 int context;
152 int ignorews; 154 int ignorews;
153 char *vpath; 155 char *vpath;
154}; 156};
155 157
156struct cgit_config { 158struct cgit_config {
157 char *agefile; 159 char *agefile;
158 char *cache_root; 160 char *cache_root;
159 char *clone_prefix; 161 char *clone_prefix;
160 char *css; 162 char *css;
161 char *favicon; 163 char *favicon;
162 char *footer; 164 char *footer;
163 char *head_include; 165 char *head_include;
164 char *header; 166 char *header;
165 char *index_header; 167 char *index_header;
166 char *index_info; 168 char *index_info;
167 char *logo; 169 char *logo;
168 char *logo_link; 170 char *logo_link;
169 char *module_link; 171 char *module_link;
170 char *project_list; 172 char *project_list;
171 char *readme; 173 char *readme;
172 char *robots; 174 char *robots;
173 char *root_title; 175 char *root_title;
174 char *root_desc; 176 char *root_desc;
175 char *root_readme; 177 char *root_readme;
176 char *script_name; 178 char *script_name;
177 char *section; 179 char *section;
178 char *virtual_root; 180 char *virtual_root;
179 char *strict_export; 181 char *strict_export;
180 int cache_size; 182 int cache_size;
181 int cache_dynamic_ttl; 183 int cache_dynamic_ttl;
182 int cache_max_create_time; 184 int cache_max_create_time;
183 int cache_repo_ttl; 185 int cache_repo_ttl;
184 int cache_root_ttl; 186 int cache_root_ttl;
185 int cache_scanrc_ttl; 187 int cache_scanrc_ttl;
186 int cache_static_ttl; 188 int cache_static_ttl;
187 int embedded; 189 int embedded;
188 int enable_filter_overrides; 190 int enable_filter_overrides;
189 int enable_gitweb_owner; 191 int enable_gitweb_owner;
190 int enable_index_links; 192 int enable_index_links;
193 int enable_commit_graph;
191 int enable_log_filecount; 194 int enable_log_filecount;
192 int enable_log_linecount; 195 int enable_log_linecount;
193 int enable_remote_branches; 196 int enable_remote_branches;
194 int enable_subject_links; 197 int enable_subject_links;
195 int enable_tree_linenumbers; 198 int enable_tree_linenumbers;
196 int local_time; 199 int local_time;
197 int max_atom_items; 200 int max_atom_items;
198 int max_repo_count; 201 int max_repo_count;
199 int max_commit_count; 202 int max_commit_count;
200 int max_lock_attempts; 203 int max_lock_attempts;
201 int max_msg_len; 204 int max_msg_len;
202 int max_repodesc_len; 205 int max_repodesc_len;
203 int max_blob_size; 206 int max_blob_size;
204 int max_stats; 207 int max_stats;
205 int nocache; 208 int nocache;
206 int noplainemail; 209 int noplainemail;
207 int noheader; 210 int noheader;
208 int renamelimit; 211 int renamelimit;
209 int remove_suffix; 212 int remove_suffix;
210 int section_from_path; 213 int section_from_path;
211 int snapshots; 214 int snapshots;
212 int summary_branches; 215 int summary_branches;
213 int summary_log; 216 int summary_log;
214 int summary_tags; 217 int summary_tags;
215 int ssdiff; 218 int ssdiff;
216 struct string_list mimetypes; 219 struct string_list mimetypes;
217 struct cgit_filter *about_filter; 220 struct cgit_filter *about_filter;
218 struct cgit_filter *commit_filter; 221 struct cgit_filter *commit_filter;
219 struct cgit_filter *source_filter; 222 struct cgit_filter *source_filter;
220}; 223};
221 224
222struct cgit_page { 225struct cgit_page {
223 time_t modified; 226 time_t modified;
224 time_t expires; 227 time_t expires;
225 size_t size; 228 size_t size;
226 char *mimetype; 229 char *mimetype;
227 char *charset; 230 char *charset;
228 char *filename; 231 char *filename;
229 char *etag; 232 char *etag;
230 char *title; 233 char *title;
231 int status; 234 int status;
232 char *statusmsg; 235 char *statusmsg;
233}; 236};
234 237
235struct cgit_environment { 238struct cgit_environment {
236 char *cgit_config; 239 char *cgit_config;
237 char *http_host; 240 char *http_host;
238 char *https; 241 char *https;
239 char *no_http; 242 char *no_http;
240 char *path_info; 243 char *path_info;
241 char *query_string; 244 char *query_string;
242 char *request_method; 245 char *request_method;
243 char *script_name; 246 char *script_name;
244 char *server_name; 247 char *server_name;
245 char *server_port; 248 char *server_port;
246}; 249};
247 250
248struct cgit_context { 251struct cgit_context {
249 struct cgit_environment env; 252 struct cgit_environment env;
250 struct cgit_query qry; 253 struct cgit_query qry;
251 struct cgit_config cfg; 254 struct cgit_config cfg;
252 struct cgit_repo *repo; 255 struct cgit_repo *repo;
253 struct cgit_page page; 256 struct cgit_page page;
254}; 257};
255 258
256struct cgit_snapshot_format { 259struct cgit_snapshot_format {
257 const char *suffix; 260 const char *suffix;
258 const char *mimetype; 261 const char *mimetype;
259 write_archive_fn_t write_func; 262 write_archive_fn_t write_func;
260 int bit; 263 int bit;
261}; 264};
262 265
263extern const char *cgit_version; 266extern const char *cgit_version;
264 267
265extern struct cgit_repolist cgit_repolist; 268extern struct cgit_repolist cgit_repolist;
266extern struct cgit_context ctx; 269extern struct cgit_context ctx;
267extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 270extern const struct cgit_snapshot_format cgit_snapshot_formats[];
268 271
269extern struct cgit_repo *cgit_add_repo(const char *url); 272extern struct cgit_repo *cgit_add_repo(const char *url);
270extern struct cgit_repo *cgit_get_repoinfo(const char *url); 273extern struct cgit_repo *cgit_get_repoinfo(const char *url);
271extern void cgit_repo_config_cb(const char *name, const char *value); 274extern void cgit_repo_config_cb(const char *name, const char *value);
272 275
273extern int chk_zero(int result, char *msg); 276extern int chk_zero(int result, char *msg);
274extern int chk_positive(int result, char *msg); 277extern int chk_positive(int result, char *msg);
275extern int chk_non_negative(int result, char *msg); 278extern int chk_non_negative(int result, char *msg);
276 279
277extern char *trim_end(const char *str, char c); 280extern char *trim_end(const char *str, char c);
278extern char *strlpart(char *txt, int maxlen); 281extern char *strlpart(char *txt, int maxlen);
279extern char *strrpart(char *txt, int maxlen); 282extern char *strrpart(char *txt, int maxlen);
280 283
281extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 284extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
282extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 285extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
283 int flags, void *cb_data); 286 int flags, void *cb_data);
284 287
285extern void *cgit_free_commitinfo(struct commitinfo *info); 288extern void *cgit_free_commitinfo(struct commitinfo *info);
286 289
287extern int cgit_diff_files(const unsigned char *old_sha1, 290extern int cgit_diff_files(const unsigned char *old_sha1,
288 const unsigned char *new_sha1, 291 const unsigned char *new_sha1,
289 unsigned long *old_size, unsigned long *new_size, 292 unsigned long *old_size, unsigned long *new_size,
290 int *binary, int context, int ignorews, 293 int *binary, int context, int ignorews,
291 linediff_fn fn); 294 linediff_fn fn);
292 295
293extern void cgit_diff_tree(const unsigned char *old_sha1, 296extern void cgit_diff_tree(const unsigned char *old_sha1,
294 const unsigned char *new_sha1, 297 const unsigned char *new_sha1,
295 filepair_fn fn, const char *prefix, int ignorews); 298 filepair_fn fn, const char *prefix, int ignorews);
296 299
297extern void cgit_diff_commit(struct commit *commit, filepair_fn fn, 300extern void cgit_diff_commit(struct commit *commit, filepair_fn fn,
298 const char *prefix); 301 const char *prefix);
299 302
300__attribute__((format (printf,1,2))) 303__attribute__((format (printf,1,2)))
301extern char *fmt(const char *format,...); 304extern char *fmt(const char *format,...);
302 305
303extern struct commitinfo *cgit_parse_commit(struct commit *commit); 306extern struct commitinfo *cgit_parse_commit(struct commit *commit);
304extern struct taginfo *cgit_parse_tag(struct tag *tag); 307extern struct taginfo *cgit_parse_tag(struct tag *tag);
305extern void cgit_parse_url(const char *url); 308extern void cgit_parse_url(const char *url);
306 309
307extern const char *cgit_repobasename(const char *reponame); 310extern const char *cgit_repobasename(const char *reponame);
308 311
309extern int cgit_parse_snapshots_mask(const char *str); 312extern int cgit_parse_snapshots_mask(const char *str);
310 313
311extern int cgit_open_filter(struct cgit_filter *filter); 314extern int cgit_open_filter(struct cgit_filter *filter);
312extern int cgit_close_filter(struct cgit_filter *filter); 315extern int cgit_close_filter(struct cgit_filter *filter);
313 316
314extern int readfile(const char *path, char **buf, size_t *size); 317extern int readfile(const char *path, char **buf, size_t *size);
315 318
316extern char *expand_macros(const char *txt); 319extern char *expand_macros(const char *txt);
317 320
318#endif /* CGIT_H */ 321#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 8e51ca5..3c20fe1 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,570 +1,583 @@
1:man source: cgit 1:man source: cgit
2:man manual: cgit 2:man manual: cgit
3 3
4CGITRC(5) 4CGITRC(5)
5======== 5========
6 6
7 7
8NAME 8NAME
9---- 9----
10cgitrc - runtime configuration for cgit 10cgitrc - runtime configuration for cgit
11 11
12 12
13SYNOPSIS 13SYNOPSIS
14-------- 14--------
15Cgitrc contains all runtime settings for cgit, including the list of git 15Cgitrc contains all runtime settings for cgit, including the list of git
16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
17lines, and lines starting with '#', are ignored. 17lines, and lines starting with '#', are ignored.
18 18
19 19
20LOCATION 20LOCATION
21-------- 21--------
22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
23runtime, cgit will consult the environment variable CGIT_CONFIG and, if 23runtime, cgit will consult the environment variable CGIT_CONFIG and, if
24defined, use its value instead. 24defined, use its value instead.
25 25
26 26
27GLOBAL SETTINGS 27GLOBAL SETTINGS
28--------------- 28---------------
29about-filter:: 29about-filter::
30 Specifies a command which will be invoked to format the content of 30 Specifies a command which will be invoked to format the content of
31 about pages (both top-level and for each repository). The command will 31 about pages (both top-level and for each repository). The command will
32 get the content of the about-file on its STDIN, and the STDOUT from the 32 get the content of the about-file on its STDIN, and the STDOUT from the
33 command will be included verbatim on the about page. Default value: 33 command will be included verbatim on the about page. Default value:
34 none. 34 none.
35 35
36agefile:: 36agefile::
37 Specifies a path, relative to each repository path, which can be used 37 Specifies a path, relative to each repository path, which can be used
38 to specify the date and time of the youngest commit in the repository. 38 to specify the date and time of the youngest commit in the repository.
39 The first line in the file is used as input to the "parse_date" 39 The first line in the file is used as input to the "parse_date"
40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
41 hh:mm:ss". Default value: "info/web/last-modified". 41 hh:mm:ss". Default value: "info/web/last-modified".
42 42
43cache-root:: 43cache-root::
44 Path used to store the cgit cache entries. Default value: 44 Path used to store the cgit cache entries. Default value:
45 "/var/cache/cgit". 45 "/var/cache/cgit".
46 46
47cache-dynamic-ttl:: 47cache-dynamic-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed without a fixed SHA1. Default 49 version of repository pages accessed without a fixed SHA1. Default
50 value: "5". 50 value: "5".
51 51
52cache-repo-ttl:: 52cache-repo-ttl::
53 Number which specifies the time-to-live, in minutes, for the cached 53 Number which specifies the time-to-live, in minutes, for the cached
54 version of the repository summary page. Default value: "5". 54 version of the repository summary page. Default value: "5".
55 55
56cache-root-ttl:: 56cache-root-ttl::
57 Number which specifies the time-to-live, in minutes, for the cached 57 Number which specifies the time-to-live, in minutes, for the cached
58 version of the repository index page. Default value: "5". 58 version of the repository index page. Default value: "5".
59 59
60cache-scanrc-ttl:: 60cache-scanrc-ttl::
61 Number which specifies the time-to-live, in minutes, for the result 61 Number which specifies the time-to-live, in minutes, for the result
62 of scanning a path for git repositories. Default value: "15". 62 of scanning a path for git repositories. Default value: "15".
63 63
64cache-size:: 64cache-size::
65 The maximum number of entries in the cgit cache. Default value: "0" 65 The maximum number of entries in the cgit cache. Default value: "0"
66 (i.e. caching is disabled). 66 (i.e. caching is disabled).
67 67
68cache-static-ttl:: 68cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-commit-graph::
95 Flag which, when set to "1", will make cgit print an ASCII-art commit
96 history graph to the left of the commit messages in the repository
97 log page. Default value: "0".
98
94enable-filter-overrides:: 99enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 100 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 101 overridden in repository-specific cgitrc files. Default value: none.
97 102
98enable-gitweb-owner:: 103enable-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository 104 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner. 105 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path. 106 Default value: "1". See also: scan-path.
102 107
103enable-index-links:: 108enable-index-links::
104 Flag which, when set to "1", will make cgit generate extra links for 109 Flag which, when set to "1", will make cgit generate extra links for
105 each repo in the repository index (specifically, to the "summary", 110 each repo in the repository index (specifically, to the "summary",
106 "commit" and "tree" pages). Default value: "0". 111 "commit" and "tree" pages). Default value: "0".
107 112
108enable-log-filecount:: 113enable-log-filecount::
109 Flag which, when set to "1", will make cgit print the number of 114 Flag which, when set to "1", will make cgit print the number of
110 modified files for each commit on the repository log page. Default 115 modified files for each commit on the repository log page. Default
111 value: "0". 116 value: "0".
112 117
113enable-log-linecount:: 118enable-log-linecount::
114 Flag which, when set to "1", will make cgit print the number of added 119 Flag which, when set to "1", will make cgit print the number of added
115 and removed lines for each commit on the repository log page. Default 120 and removed lines for each commit on the repository log page. Default
116 value: "0". 121 value: "0".
117 122
118enable-remote-branches:: 123enable-remote-branches::
119 Flag which, when set to "1", will make cgit display remote branches 124 Flag which, when set to "1", will make cgit display remote branches
120 in the summary and refs views. Default value: "0". See also: 125 in the summary and refs views. Default value: "0". See also:
121 "repo.enable-remote-branches". 126 "repo.enable-remote-branches".
122 127
123enable-subject-links:: 128enable-subject-links::
124 Flag which, when set to "1", will make cgit use the subject of the 129 Flag which, when set to "1", will make cgit use the subject of the
125 parent commit as link text when generating links to parent commits 130 parent commit as link text when generating links to parent commits
126 in commit view. Default value: "0". See also: 131 in commit view. Default value: "0". See also:
127 "repo.enable-subject-links". 132 "repo.enable-subject-links".
128 133
129enable-tree-linenumbers:: 134enable-tree-linenumbers::
130 Flag which, when set to "1", will make cgit generate linenumber links 135 Flag which, when set to "1", will make cgit generate linenumber links
131 for plaintext blobs printed in the tree view. Default value: "1". 136 for plaintext blobs printed in the tree view. Default value: "1".
132 137
133favicon:: 138favicon::
134 Url used as link to a shortcut icon for cgit. If specified, it is 139 Url used as link to a shortcut icon for cgit. If specified, it is
135 suggested to use the value "/favicon.ico" since certain browsers will 140 suggested to use the value "/favicon.ico" since certain browsers will
136 ignore other values. Default value: none. 141 ignore other values. Default value: none.
137 142
138footer:: 143footer::
139 The content of the file specified with this option will be included 144 The content of the file specified with this option will be included
140 verbatim at the bottom of all pages (i.e. it replaces the standard 145 verbatim at the bottom of all pages (i.e. it replaces the standard
141 "generated by..." message. Default value: none. 146 "generated by..." message. Default value: none.
142 147
143head-include:: 148head-include::
144 The content of the file specified with this option will be included 149 The content of the file specified with this option will be included
145 verbatim in the html HEAD section on all pages. Default value: none. 150 verbatim in the html HEAD section on all pages. Default value: none.
146 151
147header:: 152header::
148 The content of the file specified with this option will be included 153 The content of the file specified with this option will be included
149 verbatim at the top of all pages. Default value: none. 154 verbatim at the top of all pages. Default value: none.
150 155
151include:: 156include::
152 Name of a configfile to include before the rest of the current config- 157 Name of a configfile to include before the rest of the current config-
153 file is parsed. Default value: none. 158 file is parsed. Default value: none.
154 159
155index-header:: 160index-header::
156 The content of the file specified with this option will be included 161 The content of the file specified with this option will be included
157 verbatim above the repository index. This setting is deprecated, and 162 verbatim above the repository index. This setting is deprecated, and
158 will not be supported by cgit-1.0 (use root-readme instead). Default 163 will not be supported by cgit-1.0 (use root-readme instead). Default
159 value: none. 164 value: none.
160 165
161index-info:: 166index-info::
162 The content of the file specified with this option will be included 167 The content of the file specified with this option will be included
163 verbatim below the heading on the repository index page. This setting 168 verbatim below the heading on the repository index page. This setting
164 is deprecated, and will not be supported by cgit-1.0 (use root-desc 169 is deprecated, and will not be supported by cgit-1.0 (use root-desc
165 instead). Default value: none. 170 instead). Default value: none.
166 171
167local-time:: 172local-time::
168 Flag which, if set to "1", makes cgit print commit and tag times in the 173 Flag which, if set to "1", makes cgit print commit and tag times in the
169 servers timezone. Default value: "0". 174 servers timezone. Default value: "0".
170 175
171logo:: 176logo::
172 Url which specifies the source of an image which will be used as a logo 177 Url which specifies the source of an image which will be used as a logo
173 on all cgit pages. Default value: "/cgit.png". 178 on all cgit pages. Default value: "/cgit.png".
174 179
175logo-link:: 180logo-link::
176 Url loaded when clicking on the cgit logo image. If unspecified the 181 Url loaded when clicking on the cgit logo image. If unspecified the
177 calculated url of the repository index page will be used. Default 182 calculated url of the repository index page will be used. Default
178 value: none. 183 value: none.
179 184
180max-atom-items:: 185max-atom-items::
181 Specifies the number of items to display in atom feeds view. Default 186 Specifies the number of items to display in atom feeds view. Default
182 value: "10". 187 value: "10".
183 188
184max-commit-count:: 189max-commit-count::
185 Specifies the number of entries to list per page in "log" view. Default 190 Specifies the number of entries to list per page in "log" view. Default
186 value: "50". 191 value: "50".
187 192
188max-message-length:: 193max-message-length::
189 Specifies the maximum number of commit message characters to display in 194 Specifies the maximum number of commit message characters to display in
190 "log" view. Default value: "80". 195 "log" view. Default value: "80".
191 196
192max-repo-count:: 197max-repo-count::
193 Specifies the number of entries to list per page on therepository 198 Specifies the number of entries to list per page on therepository
194 index page. Default value: "50". 199 index page. Default value: "50".
195 200
196max-repodesc-length:: 201max-repodesc-length::
197 Specifies the maximum number of repo description characters to display 202 Specifies the maximum number of repo description characters to display
198 on the repository index page. Default value: "80". 203 on the repository index page. Default value: "80".
199 204
200max-blob-size:: 205max-blob-size::
201 Specifies the maximum size of a blob to display HTML for in KBytes. 206 Specifies the maximum size of a blob to display HTML for in KBytes.
202 Default value: "0" (limit disabled). 207 Default value: "0" (limit disabled).
203 208
204max-stats:: 209max-stats::
205 Set the default maximum statistics period. Valid values are "week", 210 Set the default maximum statistics period. Valid values are "week",
206 "month", "quarter" and "year". If unspecified, statistics are 211 "month", "quarter" and "year". If unspecified, statistics are
207 disabled. Default value: none. See also: "repo.max-stats". 212 disabled. Default value: none. See also: "repo.max-stats".
208 213
209mimetype.<ext>:: 214mimetype.<ext>::
210 Set the mimetype for the specified filename extension. This is used 215 Set the mimetype for the specified filename extension. This is used
211 by the `plain` command when returning blob content. 216 by the `plain` command when returning blob content.
212 217
213module-link:: 218module-link::
214 Text which will be used as the formatstring for a hyperlink when a 219 Text which will be used as the formatstring for a hyperlink when a
215 submodule is printed in a directory listing. The arguments for the 220 submodule is printed in a directory listing. The arguments for the
216 formatstring are the path and SHA1 of the submodule commit. Default 221 formatstring are the path and SHA1 of the submodule commit. Default
217 value: "./?repo=%s&page=commit&id=%s" 222 value: "./?repo=%s&page=commit&id=%s"
218 223
219nocache:: 224nocache::
220 If set to the value "1" caching will be disabled. This settings is 225 If set to the value "1" caching will be disabled. This settings is
221 deprecated, and will not be honored starting with cgit-1.0. Default 226 deprecated, and will not be honored starting with cgit-1.0. Default
222 value: "0". 227 value: "0".
223 228
224noplainemail:: 229noplainemail::
225 If set to "1" showing full author email adresses will be disabled. 230 If set to "1" showing full author email adresses will be disabled.
226 Default value: "0". 231 Default value: "0".
227 232
228noheader:: 233noheader::
229 Flag which, when set to "1", will make cgit omit the standard header 234 Flag which, when set to "1", will make cgit omit the standard header
230 on all pages. Default value: none. See also: "embedded". 235 on all pages. Default value: none. See also: "embedded".
231 236
232project-list:: 237project-list::
233 A list of subdirectories inside of scan-path, relative to it, that 238 A list of subdirectories inside of scan-path, relative to it, that
234 should loaded as git repositories. This must be defined prior to 239 should loaded as git repositories. This must be defined prior to
235 scan-path. Default value: none. See also: scan-path. 240 scan-path. Default value: none. See also: scan-path.
236 241
237readme:: 242readme::
238 Text which will be used as default value for "repo.readme". Default 243 Text which will be used as default value for "repo.readme". Default
239 value: none. 244 value: none.
240 245
241remove-suffix:: 246remove-suffix::
242 If set to "1" and scan-path is enabled, if any repositories are found 247 If set to "1" and scan-path is enabled, if any repositories are found
243 with a suffix of ".git", this suffix will be removed for the url and 248 with a suffix of ".git", this suffix will be removed for the url and
244 name. Default value: "0". See also: scan-path. 249 name. Default value: "0". See also: scan-path.
245 250
246renamelimit:: 251renamelimit::
247 Maximum number of files to consider when detecting renames. The value 252 Maximum number of files to consider when detecting renames. The value
248 "-1" uses the compiletime value in git (for further info, look at 253 "-1" uses the compiletime value in git (for further info, look at
249 `man git-diff`). Default value: "-1". 254 `man git-diff`). Default value: "-1".
250 255
251repo.group:: 256repo.group::
252 Legacy alias for "section". This option is deprecated and will not be 257 Legacy alias for "section". This option is deprecated and will not be
253 supported in cgit-1.0. 258 supported in cgit-1.0.
254 259
255robots:: 260robots::
256 Text used as content for the "robots" meta-tag. Default value: 261 Text used as content for the "robots" meta-tag. Default value:
257 "index, nofollow". 262 "index, nofollow".
258 263
259root-desc:: 264root-desc::
260 Text printed below the heading on the repository index page. Default 265 Text printed below the heading on the repository index page. Default
261 value: "a fast webinterface for the git dscm". 266 value: "a fast webinterface for the git dscm".
262 267
263root-readme:: 268root-readme::
264 The content of the file specified with this option will be included 269 The content of the file specified with this option will be included
265 verbatim below the "about" link on the repository index page. Default 270 verbatim below the "about" link on the repository index page. Default
266 value: none. 271 value: none.
267 272
268root-title:: 273root-title::
269 Text printed as heading on the repository index page. Default value: 274 Text printed as heading on the repository index page. Default value:
270 "Git Repository Browser". 275 "Git Repository Browser".
271 276
272scan-path:: 277scan-path::
273 A path which will be scanned for repositories. If caching is enabled, 278 A path which will be scanned for repositories. If caching is enabled,
274 the result will be cached as a cgitrc include-file in the cache 279 the result will be cached as a cgitrc include-file in the cache
275 directory. If project-list has been defined prior to scan-path, 280 directory. If project-list has been defined prior to scan-path,
276 scan-path loads only the directories listed in the file pointed to by 281 scan-path loads only the directories listed in the file pointed to by
277 project-list. Default value: none. See also: cache-scanrc-ttl, 282 project-list. Default value: none. See also: cache-scanrc-ttl,
278 project-list. 283 project-list.
279 284
280section:: 285section::
281 The name of the current repository section - all repositories defined 286 The name of the current repository section - all repositories defined
282 after this option will inherit the current section name. Default value: 287 after this option will inherit the current section name. Default value:
283 none. 288 none.
284 289
285section-from-path:: 290section-from-path::
286 A number which, if specified before scan-path, specifies how many 291 A number which, if specified before scan-path, specifies how many
287 path elements from each repo path to use as a default section name. 292 path elements from each repo path to use as a default section name.
288 If negative, cgit will discard the specified number of path elements 293 If negative, cgit will discard the specified number of path elements
289 above the repo directory. Default value: 0. 294 above the repo directory. Default value: 0.
290 295
291side-by-side-diffs:: 296side-by-side-diffs::
292 If set to "1" shows side-by-side diffs instead of unidiffs per 297 If set to "1" shows side-by-side diffs instead of unidiffs per
293 default. Default value: "0". 298 default. Default value: "0".
294 299
295snapshots:: 300snapshots::
296 Text which specifies the default set of snapshot formats generated by 301 Text which specifies the default set of snapshot formats generated by
297 cgit. The value is a space-separated list of zero or more of the 302 cgit. The value is a space-separated list of zero or more of the
298 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 303 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
299 304
300source-filter:: 305source-filter::
301 Specifies a command which will be invoked to format plaintext blobs 306 Specifies a command which will be invoked to format plaintext blobs
302 in the tree view. The command will get the blob content on its STDIN 307 in the tree view. The command will get the blob content on its STDIN
303 and the name of the blob as its only command line argument. The STDOUT 308 and the name of the blob as its only command line argument. The STDOUT
304 from the command will be included verbatim as the blob contents, i.e. 309 from the command will be included verbatim as the blob contents, i.e.
305 this can be used to implement e.g. syntax highlighting. Default value: 310 this can be used to implement e.g. syntax highlighting. Default value:
306 none. 311 none.
307 312
308summary-branches:: 313summary-branches::
309 Specifies the number of branches to display in the repository "summary" 314 Specifies the number of branches to display in the repository "summary"
310 view. Default value: "10". 315 view. Default value: "10".
311 316
312summary-log:: 317summary-log::
313 Specifies the number of log entries to display in the repository 318 Specifies the number of log entries to display in the repository
314 "summary" view. Default value: "10". 319 "summary" view. Default value: "10".
315 320
316summary-tags:: 321summary-tags::
317 Specifies the number of tags to display in the repository "summary" 322 Specifies the number of tags to display in the repository "summary"
318 view. Default value: "10". 323 view. Default value: "10".
319 324
320strict-export:: 325strict-export::
321 Filename which, if specified, needs to be present within the repository 326 Filename which, if specified, needs to be present within the repository
322 for cgit to allow access to that repository. This can be used to emulate 327 for cgit to allow access to that repository. This can be used to emulate
323 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's 328 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's
324 repositories to match those exported by git-daemon. This option MUST come 329 repositories to match those exported by git-daemon. This option MUST come
325 before 'scan-path'. 330 before 'scan-path'.
326 331
327virtual-root:: 332virtual-root::
328 Url which, if specified, will be used as root for all cgit links. It 333 Url which, if specified, will be used as root for all cgit links. It
329 will also cause cgit to generate 'virtual urls', i.e. urls like 334 will also cause cgit to generate 'virtual urls', i.e. urls like
330 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 335 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
331 value: none. 336 value: none.
332 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 337 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
333 same kind of virtual urls, so this option will probably be deprecated. 338 same kind of virtual urls, so this option will probably be deprecated.
334 339
335REPOSITORY SETTINGS 340REPOSITORY SETTINGS
336------------------- 341-------------------
337repo.about-filter:: 342repo.about-filter::
338 Override the default about-filter. Default value: none. See also: 343 Override the default about-filter. Default value: none. See also:
339 "enable-filter-overrides". 344 "enable-filter-overrides".
340 345
341repo.clone-url:: 346repo.clone-url::
342 A list of space-separated urls which can be used to clone this repo. 347 A list of space-separated urls which can be used to clone this repo.
343 Default value: none. 348 Default value: none.
344 349
345repo.commit-filter:: 350repo.commit-filter::
346 Override the default commit-filter. Default value: none. See also: 351 Override the default commit-filter. Default value: none. See also:
347 "enable-filter-overrides". 352 "enable-filter-overrides".
348 353
349repo.defbranch:: 354repo.defbranch::
350 The name of the default branch for this repository. If no such branch 355 The name of the default branch for this repository. If no such branch
351 exists in the repository, the first branch name (when sorted) is used 356 exists in the repository, the first branch name (when sorted) is used
352 as default instead. Default value: "master". 357 as default instead. Default value: "master".
353 358
354repo.desc:: 359repo.desc::
355 The value to show as repository description. Default value: none. 360 The value to show as repository description. Default value: none.
356 361
362repo.enable-commit-graph::
363 A flag which can be used to disable the global setting
364 `enable-commit-graph'. Default value: none.
365
357repo.enable-log-filecount:: 366repo.enable-log-filecount::
358 A flag which can be used to disable the global setting 367 A flag which can be used to disable the global setting
359 `enable-log-filecount'. Default value: none. 368 `enable-log-filecount'. Default value: none.
360 369
361repo.enable-log-linecount:: 370repo.enable-log-linecount::
362 A flag which can be used to disable the global setting 371 A flag which can be used to disable the global setting
363 `enable-log-linecount'. Default value: none. 372 `enable-log-linecount'. Default value: none.
364 373
365repo.enable-remote-branches:: 374repo.enable-remote-branches::
366 Flag which, when set to "1", will make cgit display remote branches 375 Flag which, when set to "1", will make cgit display remote branches
367 in the summary and refs views. Default value: <enable-remote-branches>. 376 in the summary and refs views. Default value: <enable-remote-branches>.
368 377
369repo.enable-subject-links:: 378repo.enable-subject-links::
370 A flag which can be used to override the global setting 379 A flag which can be used to override the global setting
371 `enable-subject-links'. Default value: none. 380 `enable-subject-links'. Default value: none.
372 381
373repo.max-stats:: 382repo.max-stats::
374 Override the default maximum statistics period. Valid values are equal 383 Override the default maximum statistics period. Valid values are equal
375 to the values specified for the global "max-stats" setting. Default 384 to the values specified for the global "max-stats" setting. Default
376 value: none. 385 value: none.
377 386
378repo.name:: 387repo.name::
379 The value to show as repository name. Default value: <repo.url>. 388 The value to show as repository name. Default value: <repo.url>.
380 389
381repo.owner:: 390repo.owner::
382 A value used to identify the owner of the repository. Default value: 391 A value used to identify the owner of the repository. Default value:
383 none. 392 none.
384 393
385repo.path:: 394repo.path::
386 An absolute path to the repository directory. For non-bare repositories 395 An absolute path to the repository directory. For non-bare repositories
387 this is the .git-directory. Default value: none. 396 this is the .git-directory. Default value: none.
388 397
389repo.readme:: 398repo.readme::
390 A path (relative to <repo.path>) which specifies a file to include 399 A path (relative to <repo.path>) which specifies a file to include
391 verbatim as the "About" page for this repo. You may also specify a 400 verbatim as the "About" page for this repo. You may also specify a
392 git refspec by head or by hash by prepending the refspec followed by 401 git refspec by head or by hash by prepending the refspec followed by
393 a colon. For example, "master:docs/readme.mkd" Default value: <readme>. 402 a colon. For example, "master:docs/readme.mkd" Default value: <readme>.
394 403
395repo.snapshots:: 404repo.snapshots::
396 A mask of allowed snapshot-formats for this repo, restricted by the 405 A mask of allowed snapshot-formats for this repo, restricted by the
397 "snapshots" global setting. Default value: <snapshots>. 406 "snapshots" global setting. Default value: <snapshots>.
398 407
399repo.section:: 408repo.section::
400 Override the current section name for this repository. Default value: 409 Override the current section name for this repository. Default value:
401 none. 410 none.
402 411
403repo.source-filter:: 412repo.source-filter::
404 Override the default source-filter. Default value: none. See also: 413 Override the default source-filter. Default value: none. See also:
405 "enable-filter-overrides". 414 "enable-filter-overrides".
406 415
407repo.url:: 416repo.url::
408 The relative url used to access the repository. This must be the first 417 The relative url used to access the repository. This must be the first
409 setting specified for each repo. Default value: none. 418 setting specified for each repo. Default value: none.
410 419
411 420
412REPOSITORY-SPECIFIC CGITRC FILE 421REPOSITORY-SPECIFIC CGITRC FILE
413------------------------------- 422-------------------------------
414When the option "scan-path" is used to auto-discover git repositories, cgit 423When the option "scan-path" is used to auto-discover git repositories, cgit
415will try to parse the file "cgitrc" within any found repository. Such a 424will try to parse the file "cgitrc" within any found repository. Such a
416repo-specific config file may contain any of the repo-specific options 425repo-specific config file may contain any of the repo-specific options
417described above, except "repo.url" and "repo.path". Additionally, the "filter" 426described above, except "repo.url" and "repo.path". Additionally, the "filter"
418options are only acknowledged in repo-specific config files when 427options are only acknowledged in repo-specific config files when
419"enable-filter-overrides" is set to "1". 428"enable-filter-overrides" is set to "1".
420 429
421Note: the "repo." prefix is dropped from the option names in repo-specific 430Note: the "repo." prefix is dropped from the option names in repo-specific
422config files, e.g. "repo.desc" becomes "desc". 431config files, e.g. "repo.desc" becomes "desc".
423 432
424 433
425EXAMPLE CGITRC FILE 434EXAMPLE CGITRC FILE
426------------------- 435-------------------
427 436
428.... 437....
429# Enable caching of up to 1000 output entriess 438# Enable caching of up to 1000 output entriess
430cache-size=1000 439cache-size=1000
431 440
432 441
433# Specify some default clone prefixes 442# Specify some default clone prefixes
434clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git 443clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git
435 444
436# Specify the css url 445# Specify the css url
437css=/css/cgit.css 446css=/css/cgit.css
438 447
439 448
440# Show extra links for each repository on the index page 449# Show extra links for each repository on the index page
441enable-index-links=1 450enable-index-links=1
442 451
443 452
453# Enable ASCII art commit history graph on the log pages
454enable-commit-graph=1
455
456
444# Show number of affected files per commit on the log pages 457# Show number of affected files per commit on the log pages
445enable-log-filecount=1 458enable-log-filecount=1
446 459
447 460
448# Show number of added/removed lines per commit on the log pages 461# Show number of added/removed lines per commit on the log pages
449enable-log-linecount=1 462enable-log-linecount=1
450 463
451 464
452# Add a cgit favicon 465# Add a cgit favicon
453favicon=/favicon.ico 466favicon=/favicon.ico
454 467
455 468
456# Use a custom logo 469# Use a custom logo
457logo=/img/mylogo.png 470logo=/img/mylogo.png
458 471
459 472
460# Enable statistics per week, month and quarter 473# Enable statistics per week, month and quarter
461max-stats=quarter 474max-stats=quarter
462 475
463 476
464# Set the title and heading of the repository index page 477# Set the title and heading of the repository index page
465root-title=example.com git repositories 478root-title=example.com git repositories
466 479
467 480
468# Set a subheading for the repository index page 481# Set a subheading for the repository index page
469root-desc=tracking the foobar development 482root-desc=tracking the foobar development
470 483
471 484
472# Include some more info about example.com on the index page 485# Include some more info about example.com on the index page
473root-readme=/var/www/htdocs/about.html 486root-readme=/var/www/htdocs/about.html
474 487
475 488
476# Allow download of tar.gz, tar.bz2 and zip-files 489# Allow download of tar.gz, tar.bz2 and zip-files
477snapshots=tar.gz tar.bz2 zip 490snapshots=tar.gz tar.bz2 zip
478 491
479 492
480## 493##
481## List of common mimetypes 494## List of common mimetypes
482## 495##
483 496
484mimetype.gif=image/gif 497mimetype.gif=image/gif
485mimetype.html=text/html 498mimetype.html=text/html
486mimetype.jpg=image/jpeg 499mimetype.jpg=image/jpeg
487mimetype.jpeg=image/jpeg 500mimetype.jpeg=image/jpeg
488mimetype.pdf=application/pdf 501mimetype.pdf=application/pdf
489mimetype.png=image/png 502mimetype.png=image/png
490mimetype.svg=image/svg+xml 503mimetype.svg=image/svg+xml
491 504
492 505
493## 506##
494## List of repositories. 507## List of repositories.
495## PS: Any repositories listed when section is unset will not be 508## PS: Any repositories listed when section is unset will not be
496## displayed under a section heading 509## displayed under a section heading
497## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 510## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
498## and included like this: 511## and included like this:
499## include=/etc/cgitrepos 512## include=/etc/cgitrepos
500## 513##
501 514
502 515
503repo.url=foo 516repo.url=foo
504repo.path=/pub/git/foo.git 517repo.path=/pub/git/foo.git
505repo.desc=the master foo repository 518repo.desc=the master foo repository
506repo.owner=fooman@example.com 519repo.owner=fooman@example.com
507repo.readme=info/web/about.html 520repo.readme=info/web/about.html
508 521
509 522
510repo.url=bar 523repo.url=bar
511repo.path=/pub/git/bar.git 524repo.path=/pub/git/bar.git
512repo.desc=the bars for your foo 525repo.desc=the bars for your foo
513repo.owner=barman@example.com 526repo.owner=barman@example.com
514repo.readme=info/web/about.html 527repo.readme=info/web/about.html
515 528
516 529
517# The next repositories will be displayed under the 'extras' heading 530# The next repositories will be displayed under the 'extras' heading
518section=extras 531section=extras
519 532
520 533
521repo.url=baz 534repo.url=baz
522repo.path=/pub/git/baz.git 535repo.path=/pub/git/baz.git
523repo.desc=a set of extensions for bar users 536repo.desc=a set of extensions for bar users
524 537
525repo.url=wiz 538repo.url=wiz
526repo.path=/pub/git/wiz.git 539repo.path=/pub/git/wiz.git
527repo.desc=the wizard of foo 540repo.desc=the wizard of foo
528 541
529 542
530# Add some mirrored repositories 543# Add some mirrored repositories
531section=mirrors 544section=mirrors
532 545
533 546
534repo.url=git 547repo.url=git
535repo.path=/pub/git/git.git 548repo.path=/pub/git/git.git
536repo.desc=the dscm 549repo.desc=the dscm
537 550
538 551
539repo.url=linux 552repo.url=linux
540repo.path=/pub/git/linux.git 553repo.path=/pub/git/linux.git
541repo.desc=the kernel 554repo.desc=the kernel
542 555
543# Disable adhoc downloads of this repo 556# Disable adhoc downloads of this repo
544repo.snapshots=0 557repo.snapshots=0
545 558
546# Disable line-counts for this repo 559# Disable line-counts for this repo
547repo.enable-log-linecount=0 560repo.enable-log-linecount=0
548 561
549# Restrict the max statistics period for this repo 562# Restrict the max statistics period for this repo
550repo.max-stats=month 563repo.max-stats=month
551.... 564....
552 565
553 566
554BUGS 567BUGS
555---- 568----
556Comments currently cannot appear on the same line as a setting; the comment 569Comments currently cannot appear on the same line as a setting; the comment
557will be included as part of the value. E.g. this line: 570will be included as part of the value. E.g. this line:
558 571
559 robots=index # allow indexing 572 robots=index # allow indexing
560 573
561will generate the following html element: 574will generate the following html element:
562 575
563 <meta name='robots' content='index # allow indexing'/> 576 <meta name='robots' content='index # allow indexing'/>
564 577
565 578
566 579
567AUTHOR 580AUTHOR
568------ 581------
569Lars Hjemli <hjemli@gmail.com> 582Lars Hjemli <hjemli@gmail.com>
570Jason A. Donenfeld <Jason@zx2c4.com> 583Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/cmd.c b/cmd.c
index 6dc9f5e..536515b 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,171 +1,172 @@
1/* cmd.c: the cgit command dispatcher 1/* cmd.c: the cgit command dispatcher
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cmd.h" 10#include "cmd.h"
11#include "cache.h" 11#include "cache.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "ui-atom.h" 13#include "ui-atom.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15#include "ui-clone.h" 15#include "ui-clone.h"
16#include "ui-commit.h" 16#include "ui-commit.h"
17#include "ui-diff.h" 17#include "ui-diff.h"
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
40{ 40{
41 if (ctx->repo) 41 if (ctx->repo)
42 cgit_print_repo_readme(ctx->qry.path); 42 cgit_print_repo_readme(ctx->qry.path);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
67static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
68{ 68{
69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1,
71 ctx->repo->enable_commit_graph);
71} 72}
72 73
73static void ls_cache_fn(struct cgit_context *ctx) 74static void ls_cache_fn(struct cgit_context *ctx)
74{ 75{
75 ctx->page.mimetype = "text/plain"; 76 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 77 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 78 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 79 cache_ls(ctx->cfg.cache_root);
79} 80}
80 81
81static void objects_fn(struct cgit_context *ctx) 82static void objects_fn(struct cgit_context *ctx)
82{ 83{
83 cgit_clone_objects(ctx); 84 cgit_clone_objects(ctx);
84} 85}
85 86
86static void repolist_fn(struct cgit_context *ctx) 87static void repolist_fn(struct cgit_context *ctx)
87{ 88{
88 cgit_print_repolist(); 89 cgit_print_repolist();
89} 90}
90 91
91static void patch_fn(struct cgit_context *ctx) 92static void patch_fn(struct cgit_context *ctx)
92{ 93{
93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path); 94 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 95}
95 96
96static void plain_fn(struct cgit_context *ctx) 97static void plain_fn(struct cgit_context *ctx)
97{ 98{
98 cgit_print_plain(ctx); 99 cgit_print_plain(ctx);
99} 100}
100 101
101static void refs_fn(struct cgit_context *ctx) 102static void refs_fn(struct cgit_context *ctx)
102{ 103{
103 cgit_print_refs(); 104 cgit_print_refs();
104} 105}
105 106
106static void snapshot_fn(struct cgit_context *ctx) 107static void snapshot_fn(struct cgit_context *ctx)
107{ 108{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 109 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 110 ctx->repo->snapshots, ctx->qry.nohead);
110} 111}
111 112
112static void stats_fn(struct cgit_context *ctx) 113static void stats_fn(struct cgit_context *ctx)
113{ 114{
114 cgit_show_stats(ctx); 115 cgit_show_stats(ctx);
115} 116}
116 117
117static void summary_fn(struct cgit_context *ctx) 118static void summary_fn(struct cgit_context *ctx)
118{ 119{
119 cgit_print_summary(); 120 cgit_print_summary();
120} 121}
121 122
122static void tag_fn(struct cgit_context *ctx) 123static void tag_fn(struct cgit_context *ctx)
123{ 124{
124 cgit_print_tag(ctx->qry.sha1); 125 cgit_print_tag(ctx->qry.sha1);
125} 126}
126 127
127static void tree_fn(struct cgit_context *ctx) 128static void tree_fn(struct cgit_context *ctx)
128{ 129{
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 130 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 131}
131 132
132#define def_cmd(name, want_repo, want_layout, want_vpath) \ 133#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout, want_vpath} 134 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 135
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 136struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 137{
137 static struct cgit_cmd cmds[] = { 138 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0, 0), 139 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0, 0), 140 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1, 0), 141 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0, 0), 142 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1, 1), 143 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1, 1), 144 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0, 0), 145 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1, 1), 146 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0, 0), 147 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0, 0), 148 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0, 1), 149 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0, 0), 150 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1, 0), 151 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0, 0), 152 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0, 0), 153 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1, 1), 154 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1, 0), 155 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1, 0), 156 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1, 1), 157 def_cmd(tree, 1, 1, 1),
157 }; 158 };
158 int i; 159 int i;
159 160
160 if (ctx->qry.page == NULL) { 161 if (ctx->qry.page == NULL) {
161 if (ctx->repo) 162 if (ctx->repo)
162 ctx->qry.page = "summary"; 163 ctx->qry.page = "summary";
163 else 164 else
164 ctx->qry.page = "repolist"; 165 ctx->qry.page = "repolist";
165 } 166 }
166 167
167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 168 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
168 if (!strcmp(ctx->qry.page, cmds[i].name)) 169 if (!strcmp(ctx->qry.page, cmds[i].name))
169 return &cmds[i]; 170 return &cmds[i];
170 return NULL; 171 return NULL;
171} 172}
diff --git a/shared.c b/shared.c
index 765cd27..7ec2e19 100644
--- a/shared.c
+++ b/shared.c
@@ -1,510 +1,511 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13 13
14int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
15{ 15{
16 if (result != 0) 16 if (result != 0)
17 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
18 return result; 18 return result;
19} 19}
20 20
21int chk_positive(int result, char *msg) 21int chk_positive(int result, char *msg)
22{ 22{
23 if (result <= 0) 23 if (result <= 0)
24 die("%s: %s", msg, strerror(errno)); 24 die("%s: %s", msg, strerror(errno));
25 return result; 25 return result;
26} 26}
27 27
28int chk_non_negative(int result, char *msg) 28int chk_non_negative(int result, char *msg)
29{ 29{
30 if (result < 0) 30 if (result < 0)
31 die("%s: %s",msg, strerror(errno)); 31 die("%s: %s",msg, strerror(errno));
32 return result; 32 return result;
33} 33}
34 34
35struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
36{ 36{
37 struct cgit_repo *ret; 37 struct cgit_repo *ret;
38 38
39 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
40 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
41 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
42 else 42 else
43 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
44 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
45 cgit_repolist.length * 45 cgit_repolist.length *
46 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
47 } 47 }
48 48
49 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
50 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches; 62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links; 63 ret->enable_subject_links = ctx.cfg.enable_subject_links;
63 ret->max_stats = ctx.cfg.max_stats; 64 ret->max_stats = ctx.cfg.max_stats;
64 ret->module_link = ctx.cfg.module_link; 65 ret->module_link = ctx.cfg.module_link;
65 ret->readme = ctx.cfg.readme; 66 ret->readme = ctx.cfg.readme;
66 ret->mtime = -1; 67 ret->mtime = -1;
67 ret->about_filter = ctx.cfg.about_filter; 68 ret->about_filter = ctx.cfg.about_filter;
68 ret->commit_filter = ctx.cfg.commit_filter; 69 ret->commit_filter = ctx.cfg.commit_filter;
69 ret->source_filter = ctx.cfg.source_filter; 70 ret->source_filter = ctx.cfg.source_filter;
70 return ret; 71 return ret;
71} 72}
72 73
73struct cgit_repo *cgit_get_repoinfo(const char *url) 74struct cgit_repo *cgit_get_repoinfo(const char *url)
74{ 75{
75 int i; 76 int i;
76 struct cgit_repo *repo; 77 struct cgit_repo *repo;
77 78
78 for (i=0; i<cgit_repolist.count; i++) { 79 for (i=0; i<cgit_repolist.count; i++) {
79 repo = &cgit_repolist.repos[i]; 80 repo = &cgit_repolist.repos[i];
80 if (!strcmp(repo->url, url)) 81 if (!strcmp(repo->url, url))
81 return repo; 82 return repo;
82 } 83 }
83 return NULL; 84 return NULL;
84} 85}
85 86
86void *cgit_free_commitinfo(struct commitinfo *info) 87void *cgit_free_commitinfo(struct commitinfo *info)
87{ 88{
88 free(info->author); 89 free(info->author);
89 free(info->author_email); 90 free(info->author_email);
90 free(info->committer); 91 free(info->committer);
91 free(info->committer_email); 92 free(info->committer_email);
92 free(info->subject); 93 free(info->subject);
93 free(info->msg); 94 free(info->msg);
94 free(info->msg_encoding); 95 free(info->msg_encoding);
95 free(info); 96 free(info);
96 return NULL; 97 return NULL;
97} 98}
98 99
99char *trim_end(const char *str, char c) 100char *trim_end(const char *str, char c)
100{ 101{
101 int len; 102 int len;
102 char *s, *t; 103 char *s, *t;
103 104
104 if (str == NULL) 105 if (str == NULL)
105 return NULL; 106 return NULL;
106 t = (char *)str; 107 t = (char *)str;
107 len = strlen(t); 108 len = strlen(t);
108 while(len > 0 && t[len - 1] == c) 109 while(len > 0 && t[len - 1] == c)
109 len--; 110 len--;
110 111
111 if (len == 0) 112 if (len == 0)
112 return NULL; 113 return NULL;
113 114
114 c = t[len]; 115 c = t[len];
115 t[len] = '\0'; 116 t[len] = '\0';
116 s = xstrdup(t); 117 s = xstrdup(t);
117 t[len] = c; 118 t[len] = c;
118 return s; 119 return s;
119} 120}
120 121
121char *strlpart(char *txt, int maxlen) 122char *strlpart(char *txt, int maxlen)
122{ 123{
123 char *result; 124 char *result;
124 125
125 if (!txt) 126 if (!txt)
126 return txt; 127 return txt;
127 128
128 if (strlen(txt) <= maxlen) 129 if (strlen(txt) <= maxlen)
129 return txt; 130 return txt;
130 result = xmalloc(maxlen + 1); 131 result = xmalloc(maxlen + 1);
131 memcpy(result, txt, maxlen - 3); 132 memcpy(result, txt, maxlen - 3);
132 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 133 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
133 result[maxlen] = '\0'; 134 result[maxlen] = '\0';
134 return result; 135 return result;
135} 136}
136 137
137char *strrpart(char *txt, int maxlen) 138char *strrpart(char *txt, int maxlen)
138{ 139{
139 char *result; 140 char *result;
140 141
141 if (!txt) 142 if (!txt)
142 return txt; 143 return txt;
143 144
144 if (strlen(txt) <= maxlen) 145 if (strlen(txt) <= maxlen)
145 return txt; 146 return txt;
146 result = xmalloc(maxlen + 1); 147 result = xmalloc(maxlen + 1);
147 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 148 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
148 result[0] = result[1] = result[2] = '.'; 149 result[0] = result[1] = result[2] = '.';
149 return result; 150 return result;
150} 151}
151 152
152void cgit_add_ref(struct reflist *list, struct refinfo *ref) 153void cgit_add_ref(struct reflist *list, struct refinfo *ref)
153{ 154{
154 size_t size; 155 size_t size;
155 156
156 if (list->count >= list->alloc) { 157 if (list->count >= list->alloc) {
157 list->alloc += (list->alloc ? list->alloc : 4); 158 list->alloc += (list->alloc ? list->alloc : 4);
158 size = list->alloc * sizeof(struct refinfo *); 159 size = list->alloc * sizeof(struct refinfo *);
159 list->refs = xrealloc(list->refs, size); 160 list->refs = xrealloc(list->refs, size);
160 } 161 }
161 list->refs[list->count++] = ref; 162 list->refs[list->count++] = ref;
162} 163}
163 164
164struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) 165struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
165{ 166{
166 struct refinfo *ref; 167 struct refinfo *ref;
167 168
168 ref = xmalloc(sizeof (struct refinfo)); 169 ref = xmalloc(sizeof (struct refinfo));
169 ref->refname = xstrdup(refname); 170 ref->refname = xstrdup(refname);
170 ref->object = parse_object(sha1); 171 ref->object = parse_object(sha1);
171 switch (ref->object->type) { 172 switch (ref->object->type) {
172 case OBJ_TAG: 173 case OBJ_TAG:
173 ref->tag = cgit_parse_tag((struct tag *)ref->object); 174 ref->tag = cgit_parse_tag((struct tag *)ref->object);
174 break; 175 break;
175 case OBJ_COMMIT: 176 case OBJ_COMMIT:
176 ref->commit = cgit_parse_commit((struct commit *)ref->object); 177 ref->commit = cgit_parse_commit((struct commit *)ref->object);
177 break; 178 break;
178 } 179 }
179 return ref; 180 return ref;
180} 181}
181 182
182int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, 183int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
183 void *cb_data) 184 void *cb_data)
184{ 185{
185 struct reflist *list = (struct reflist *)cb_data; 186 struct reflist *list = (struct reflist *)cb_data;
186 struct refinfo *info = cgit_mk_refinfo(refname, sha1); 187 struct refinfo *info = cgit_mk_refinfo(refname, sha1);
187 188
188 if (info) 189 if (info)
189 cgit_add_ref(list, info); 190 cgit_add_ref(list, info);
190 return 0; 191 return 0;
191} 192}
192 193
193void cgit_diff_tree_cb(struct diff_queue_struct *q, 194void cgit_diff_tree_cb(struct diff_queue_struct *q,
194 struct diff_options *options, void *data) 195 struct diff_options *options, void *data)
195{ 196{
196 int i; 197 int i;
197 198
198 for (i = 0; i < q->nr; i++) { 199 for (i = 0; i < q->nr; i++) {
199 if (q->queue[i]->status == 'U') 200 if (q->queue[i]->status == 'U')
200 continue; 201 continue;
201 ((filepair_fn)data)(q->queue[i]); 202 ((filepair_fn)data)(q->queue[i]);
202 } 203 }
203} 204}
204 205
205static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 206static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
206{ 207{
207 enum object_type type; 208 enum object_type type;
208 209
209 if (is_null_sha1(sha1)) { 210 if (is_null_sha1(sha1)) {
210 file->ptr = (char *)""; 211 file->ptr = (char *)"";
211 file->size = 0; 212 file->size = 0;
212 } else { 213 } else {
213 file->ptr = read_sha1_file(sha1, &type, 214 file->ptr = read_sha1_file(sha1, &type,
214 (unsigned long *)&file->size); 215 (unsigned long *)&file->size);
215 } 216 }
216 return 1; 217 return 1;
217} 218}
218 219
219/* 220/*
220 * Receive diff-buffers from xdiff and concatenate them as 221 * Receive diff-buffers from xdiff and concatenate them as
221 * needed across multiple callbacks. 222 * needed across multiple callbacks.
222 * 223 *
223 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 224 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
224 * ripped from git and modified to use globals instead of 225 * ripped from git and modified to use globals instead of
225 * a special callback-struct. 226 * a special callback-struct.
226 */ 227 */
227char *diffbuf = NULL; 228char *diffbuf = NULL;
228int buflen = 0; 229int buflen = 0;
229 230
230int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 231int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
231{ 232{
232 int i; 233 int i;
233 234
234 for (i = 0; i < nbuf; i++) { 235 for (i = 0; i < nbuf; i++) {
235 if (mb[i].ptr[mb[i].size-1] != '\n') { 236 if (mb[i].ptr[mb[i].size-1] != '\n') {
236 /* Incomplete line */ 237 /* Incomplete line */
237 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 238 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
238 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 239 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
239 buflen += mb[i].size; 240 buflen += mb[i].size;
240 continue; 241 continue;
241 } 242 }
242 243
243 /* we have a complete line */ 244 /* we have a complete line */
244 if (!diffbuf) { 245 if (!diffbuf) {
245 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 246 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
246 continue; 247 continue;
247 } 248 }
248 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 249 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
249 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 250 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
250 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 251 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
251 free(diffbuf); 252 free(diffbuf);
252 diffbuf = NULL; 253 diffbuf = NULL;
253 buflen = 0; 254 buflen = 0;
254 } 255 }
255 if (diffbuf) { 256 if (diffbuf) {
256 ((linediff_fn)priv)(diffbuf, buflen); 257 ((linediff_fn)priv)(diffbuf, buflen);
257 free(diffbuf); 258 free(diffbuf);
258 diffbuf = NULL; 259 diffbuf = NULL;
259 buflen = 0; 260 buflen = 0;
260 } 261 }
261 return 0; 262 return 0;
262} 263}
263 264
264int cgit_diff_files(const unsigned char *old_sha1, 265int cgit_diff_files(const unsigned char *old_sha1,
265 const unsigned char *new_sha1, unsigned long *old_size, 266 const unsigned char *new_sha1, unsigned long *old_size,
266 unsigned long *new_size, int *binary, int context, 267 unsigned long *new_size, int *binary, int context,
267 int ignorews, linediff_fn fn) 268 int ignorews, linediff_fn fn)
268{ 269{
269 mmfile_t file1, file2; 270 mmfile_t file1, file2;
270 xpparam_t diff_params; 271 xpparam_t diff_params;
271 xdemitconf_t emit_params; 272 xdemitconf_t emit_params;
272 xdemitcb_t emit_cb; 273 xdemitcb_t emit_cb;
273 274
274 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 275 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
275 return 1; 276 return 1;
276 277
277 *old_size = file1.size; 278 *old_size = file1.size;
278 *new_size = file2.size; 279 *new_size = file2.size;
279 280
280 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 281 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
281 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 282 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
282 *binary = 1; 283 *binary = 1;
283 if (file1.size) 284 if (file1.size)
284 free(file1.ptr); 285 free(file1.ptr);
285 if (file2.size) 286 if (file2.size)
286 free(file2.ptr); 287 free(file2.ptr);
287 return 0; 288 return 0;
288 } 289 }
289 290
290 memset(&diff_params, 0, sizeof(diff_params)); 291 memset(&diff_params, 0, sizeof(diff_params));
291 memset(&emit_params, 0, sizeof(emit_params)); 292 memset(&emit_params, 0, sizeof(emit_params));
292 memset(&emit_cb, 0, sizeof(emit_cb)); 293 memset(&emit_cb, 0, sizeof(emit_cb));
293 diff_params.flags = XDF_NEED_MINIMAL; 294 diff_params.flags = XDF_NEED_MINIMAL;
294 if (ignorews) 295 if (ignorews)
295 diff_params.flags |= XDF_IGNORE_WHITESPACE; 296 diff_params.flags |= XDF_IGNORE_WHITESPACE;
296 emit_params.ctxlen = context > 0 ? context : 3; 297 emit_params.ctxlen = context > 0 ? context : 3;
297 emit_params.flags = XDL_EMIT_FUNCNAMES; 298 emit_params.flags = XDL_EMIT_FUNCNAMES;
298 emit_cb.outf = filediff_cb; 299 emit_cb.outf = filediff_cb;
299 emit_cb.priv = fn; 300 emit_cb.priv = fn;
300 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 301 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
301 if (file1.size) 302 if (file1.size)
302 free(file1.ptr); 303 free(file1.ptr);
303 if (file2.size) 304 if (file2.size)
304 free(file2.ptr); 305 free(file2.ptr);
305 return 0; 306 return 0;
306} 307}
307 308
308void cgit_diff_tree(const unsigned char *old_sha1, 309void cgit_diff_tree(const unsigned char *old_sha1,
309 const unsigned char *new_sha1, 310 const unsigned char *new_sha1,
310 filepair_fn fn, const char *prefix, int ignorews) 311 filepair_fn fn, const char *prefix, int ignorews)
311{ 312{
312 struct diff_options opt; 313 struct diff_options opt;
313 int ret; 314 int ret;
314 int prefixlen; 315 int prefixlen;
315 316
316 diff_setup(&opt); 317 diff_setup(&opt);
317 opt.output_format = DIFF_FORMAT_CALLBACK; 318 opt.output_format = DIFF_FORMAT_CALLBACK;
318 opt.detect_rename = 1; 319 opt.detect_rename = 1;
319 opt.rename_limit = ctx.cfg.renamelimit; 320 opt.rename_limit = ctx.cfg.renamelimit;
320 DIFF_OPT_SET(&opt, RECURSIVE); 321 DIFF_OPT_SET(&opt, RECURSIVE);
321 if (ignorews) 322 if (ignorews)
322 DIFF_XDL_SET(&opt, IGNORE_WHITESPACE); 323 DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
323 opt.format_callback = cgit_diff_tree_cb; 324 opt.format_callback = cgit_diff_tree_cb;
324 opt.format_callback_data = fn; 325 opt.format_callback_data = fn;
325 if (prefix) { 326 if (prefix) {
326 opt.nr_paths = 1; 327 opt.nr_paths = 1;
327 opt.paths = &prefix; 328 opt.paths = &prefix;
328 prefixlen = strlen(prefix); 329 prefixlen = strlen(prefix);
329 opt.pathlens = &prefixlen; 330 opt.pathlens = &prefixlen;
330 } 331 }
331 diff_setup_done(&opt); 332 diff_setup_done(&opt);
332 333
333 if (old_sha1 && !is_null_sha1(old_sha1)) 334 if (old_sha1 && !is_null_sha1(old_sha1))
334 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 335 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
335 else 336 else
336 ret = diff_root_tree_sha1(new_sha1, "", &opt); 337 ret = diff_root_tree_sha1(new_sha1, "", &opt);
337 diffcore_std(&opt); 338 diffcore_std(&opt);
338 diff_flush(&opt); 339 diff_flush(&opt);
339} 340}
340 341
341void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix) 342void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
342{ 343{
343 unsigned char *old_sha1 = NULL; 344 unsigned char *old_sha1 = NULL;
344 345
345 if (commit->parents) 346 if (commit->parents)
346 old_sha1 = commit->parents->item->object.sha1; 347 old_sha1 = commit->parents->item->object.sha1;
347 cgit_diff_tree(old_sha1, commit->object.sha1, fn, prefix, 348 cgit_diff_tree(old_sha1, commit->object.sha1, fn, prefix,
348 ctx.qry.ignorews); 349 ctx.qry.ignorews);
349} 350}
350 351
351int cgit_parse_snapshots_mask(const char *str) 352int cgit_parse_snapshots_mask(const char *str)
352{ 353{
353 const struct cgit_snapshot_format *f; 354 const struct cgit_snapshot_format *f;
354 static const char *delim = " \t,:/|;"; 355 static const char *delim = " \t,:/|;";
355 int tl, sl, rv = 0; 356 int tl, sl, rv = 0;
356 357
357 /* favor legacy setting */ 358 /* favor legacy setting */
358 if(atoi(str)) 359 if(atoi(str))
359 return 1; 360 return 1;
360 for(;;) { 361 for(;;) {
361 str += strspn(str,delim); 362 str += strspn(str,delim);
362 tl = strcspn(str,delim); 363 tl = strcspn(str,delim);
363 if (!tl) 364 if (!tl)
364 break; 365 break;
365 for (f = cgit_snapshot_formats; f->suffix; f++) { 366 for (f = cgit_snapshot_formats; f->suffix; f++) {
366 sl = strlen(f->suffix); 367 sl = strlen(f->suffix);
367 if((tl == sl && !strncmp(f->suffix, str, tl)) || 368 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
368 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 369 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
369 rv |= f->bit; 370 rv |= f->bit;
370 break; 371 break;
371 } 372 }
372 } 373 }
373 str += tl; 374 str += tl;
374 } 375 }
375 return rv; 376 return rv;
376} 377}
377 378
378int cgit_open_filter(struct cgit_filter *filter) 379int cgit_open_filter(struct cgit_filter *filter)
379{ 380{
380 381
381 filter->old_stdout = chk_positive(dup(STDOUT_FILENO), 382 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
382 "Unable to duplicate STDOUT"); 383 "Unable to duplicate STDOUT");
383 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess"); 384 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
384 filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); 385 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
385 if (filter->pid == 0) { 386 if (filter->pid == 0) {
386 close(filter->pipe_fh[1]); 387 close(filter->pipe_fh[1]);
387 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO), 388 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
388 "Unable to use pipe as STDIN"); 389 "Unable to use pipe as STDIN");
389 execvp(filter->cmd, filter->argv); 390 execvp(filter->cmd, filter->argv);
390 die("Unable to exec subprocess %s: %s (%d)", filter->cmd, 391 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
391 strerror(errno), errno); 392 strerror(errno), errno);
392 } 393 }
393 close(filter->pipe_fh[0]); 394 close(filter->pipe_fh[0]);
394 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), 395 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
395 "Unable to use pipe as STDOUT"); 396 "Unable to use pipe as STDOUT");
396 close(filter->pipe_fh[1]); 397 close(filter->pipe_fh[1]);
397 return 0; 398 return 0;
398} 399}
399 400
400int cgit_close_filter(struct cgit_filter *filter) 401int cgit_close_filter(struct cgit_filter *filter)
401{ 402{
402 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), 403 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
403 "Unable to restore STDOUT"); 404 "Unable to restore STDOUT");
404 close(filter->old_stdout); 405 close(filter->old_stdout);
405 if (filter->pid < 0) 406 if (filter->pid < 0)
406 return 0; 407 return 0;
407 waitpid(filter->pid, &filter->exitstatus, 0); 408 waitpid(filter->pid, &filter->exitstatus, 0);
408 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus)) 409 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
409 return 0; 410 return 0;
410 die("Subprocess %s exited abnormally", filter->cmd); 411 die("Subprocess %s exited abnormally", filter->cmd);
411} 412}
412 413
413/* Read the content of the specified file into a newly allocated buffer, 414/* Read the content of the specified file into a newly allocated buffer,
414 * zeroterminate the buffer and return 0 on success, errno otherwise. 415 * zeroterminate the buffer and return 0 on success, errno otherwise.
415 */ 416 */
416int readfile(const char *path, char **buf, size_t *size) 417int readfile(const char *path, char **buf, size_t *size)
417{ 418{
418 int fd, e; 419 int fd, e;
419 struct stat st; 420 struct stat st;
420 421
421 fd = open(path, O_RDONLY); 422 fd = open(path, O_RDONLY);
422 if (fd == -1) 423 if (fd == -1)
423 return errno; 424 return errno;
424 if (fstat(fd, &st)) { 425 if (fstat(fd, &st)) {
425 e = errno; 426 e = errno;
426 close(fd); 427 close(fd);
427 return e; 428 return e;
428 } 429 }
429 if (!S_ISREG(st.st_mode)) { 430 if (!S_ISREG(st.st_mode)) {
430 close(fd); 431 close(fd);
431 return EISDIR; 432 return EISDIR;
432 } 433 }
433 *buf = xmalloc(st.st_size + 1); 434 *buf = xmalloc(st.st_size + 1);
434 *size = read_in_full(fd, *buf, st.st_size); 435 *size = read_in_full(fd, *buf, st.st_size);
435 e = errno; 436 e = errno;
436 (*buf)[*size] = '\0'; 437 (*buf)[*size] = '\0';
437 close(fd); 438 close(fd);
438 return (*size == st.st_size ? 0 : e); 439 return (*size == st.st_size ? 0 : e);
439} 440}
440 441
441int is_token_char(char c) 442int is_token_char(char c)
442{ 443{
443 return isalnum(c) || c == '_'; 444 return isalnum(c) || c == '_';
444} 445}
445 446
446/* Replace name with getenv(name), return pointer to zero-terminating char 447/* Replace name with getenv(name), return pointer to zero-terminating char
447 */ 448 */
448char *expand_macro(char *name, int maxlength) 449char *expand_macro(char *name, int maxlength)
449{ 450{
450 char *value; 451 char *value;
451 int len; 452 int len;
452 453
453 len = 0; 454 len = 0;
454 value = getenv(name); 455 value = getenv(name);
455 if (value) { 456 if (value) {
456 len = strlen(value); 457 len = strlen(value);
457 if (len > maxlength) 458 if (len > maxlength)
458 len = maxlength; 459 len = maxlength;
459 strncpy(name, value, len); 460 strncpy(name, value, len);
460 } 461 }
461 return name + len; 462 return name + len;
462} 463}
463 464
464#define EXPBUFSIZE (1024 * 8) 465#define EXPBUFSIZE (1024 * 8)
465 466
466/* Replace all tokens prefixed by '$' in the specified text with the 467/* Replace all tokens prefixed by '$' in the specified text with the
467 * value of the named environment variable. 468 * value of the named environment variable.
468 * NB: the return value is a static buffer, i.e. it must be strdup'd 469 * NB: the return value is a static buffer, i.e. it must be strdup'd
469 * by the caller. 470 * by the caller.
470 */ 471 */
471char *expand_macros(const char *txt) 472char *expand_macros(const char *txt)
472{ 473{
473 static char result[EXPBUFSIZE]; 474 static char result[EXPBUFSIZE];
474 char *p, *start; 475 char *p, *start;
475 int len; 476 int len;
476 477
477 p = result; 478 p = result;
478 start = NULL; 479 start = NULL;
479 while (p < result + EXPBUFSIZE - 1 && txt && *txt) { 480 while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
480 *p = *txt; 481 *p = *txt;
481 if (start) { 482 if (start) {
482 if (!is_token_char(*txt)) { 483 if (!is_token_char(*txt)) {
483 if (p - start > 0) { 484 if (p - start > 0) {
484 *p = '\0'; 485 *p = '\0';
485 len = result + EXPBUFSIZE - start - 1; 486 len = result + EXPBUFSIZE - start - 1;
486 p = expand_macro(start, len) - 1; 487 p = expand_macro(start, len) - 1;
487 } 488 }
488 start = NULL; 489 start = NULL;
489 txt--; 490 txt--;
490 } 491 }
491 p++; 492 p++;
492 txt++; 493 txt++;
493 continue; 494 continue;
494 } 495 }
495 if (*txt == '$') { 496 if (*txt == '$') {
496 start = p; 497 start = p;
497 txt++; 498 txt++;
498 continue; 499 continue;
499 } 500 }
500 p++; 501 p++;
501 txt++; 502 txt++;
502 } 503 }
503 *p = '\0'; 504 *p = '\0';
504 if (start && p - start > 0) { 505 if (start && p - start > 0) {
505 len = result + EXPBUFSIZE - start - 1; 506 len = result + EXPBUFSIZE - start - 1;
506 p = expand_macro(start, len); 507 p = expand_macro(start, len);
507 *p = '\0'; 508 *p = '\0';
508 } 509 }
509 return result; 510 return result;
510} 511}
diff --git a/ui-log.c b/ui-log.c
index 27f5a1a..8add66a 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,301 +1,428 @@
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#include "vector.h" 12#include "vector.h"
13 13
14int files, add_lines, rem_lines; 14int files, add_lines, rem_lines;
15 15
16/*
17 * The list of available column colors in the commit graph.
18 */
19static const char *column_colors_html[] = {
20 "<span class='column1'>",
21 "<span class='column2'>",
22 "<span class='column3'>",
23 "<span class='column4'>",
24 "<span class='column5'>",
25 "<span class='column6'>",
26 "</span>",
27};
28
29#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
30
16void count_lines(char *line, int size) 31void count_lines(char *line, int size)
17{ 32{
18 if (size <= 0) 33 if (size <= 0)
19 return; 34 return;
20 35
21 if (line[0] == '+') 36 if (line[0] == '+')
22 add_lines++; 37 add_lines++;
23 38
24 else if (line[0] == '-') 39 else if (line[0] == '-')
25 rem_lines++; 40 rem_lines++;
26} 41}
27 42
28void inspect_files(struct diff_filepair *pair) 43void inspect_files(struct diff_filepair *pair)
29{ 44{
30 unsigned long old_size = 0; 45 unsigned long old_size = 0;
31 unsigned long new_size = 0; 46 unsigned long new_size = 0;
32 int binary = 0; 47 int binary = 0;
33 48
34 files++; 49 files++;
35 if (ctx.repo->enable_log_linecount) 50 if (ctx.repo->enable_log_linecount)
36 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 51 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
37 &new_size, &binary, 0, ctx.qry.ignorews, 52 &new_size, &binary, 0, ctx.qry.ignorews,
38 count_lines); 53 count_lines);
39} 54}
40 55
41void show_commit_decorations(struct commit *commit) 56void show_commit_decorations(struct commit *commit)
42{ 57{
43 struct name_decoration *deco; 58 struct name_decoration *deco;
44 static char buf[1024]; 59 static char buf[1024];
45 60
46 buf[sizeof(buf) - 1] = 0; 61 buf[sizeof(buf) - 1] = 0;
47 deco = lookup_decoration(&name_decoration, &commit->object); 62 deco = lookup_decoration(&name_decoration, &commit->object);
48 while (deco) { 63 while (deco) {
49 if (!prefixcmp(deco->name, "refs/heads/")) { 64 if (!prefixcmp(deco->name, "refs/heads/")) {
50 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 65 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
51 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, 66 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
52 ctx.qry.vpath, 0, NULL, NULL, 67 ctx.qry.vpath, 0, NULL, NULL,
53 ctx.qry.showmsg); 68 ctx.qry.showmsg);
54 } 69 }
55 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 70 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
56 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 71 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
57 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 72 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
58 } 73 }
59 else if (!prefixcmp(deco->name, "refs/tags/")) { 74 else if (!prefixcmp(deco->name, "refs/tags/")) {
60 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 75 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
61 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 76 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
62 } 77 }
63 else if (!prefixcmp(deco->name, "refs/remotes/")) { 78 else if (!prefixcmp(deco->name, "refs/remotes/")) {
64 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 79 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
65 cgit_log_link(buf, NULL, "remote-deco", NULL, 80 cgit_log_link(buf, NULL, "remote-deco", NULL,
66 sha1_to_hex(commit->object.sha1), 81 sha1_to_hex(commit->object.sha1),
67 ctx.qry.vpath, 0, NULL, NULL, 82 ctx.qry.vpath, 0, NULL, NULL,
68 ctx.qry.showmsg); 83 ctx.qry.showmsg);
69 } 84 }
70 else { 85 else {
71 strncpy(buf, deco->name, sizeof(buf) - 1); 86 strncpy(buf, deco->name, sizeof(buf) - 1);
72 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 87 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
73 sha1_to_hex(commit->object.sha1), 88 sha1_to_hex(commit->object.sha1),
74 ctx.qry.vpath, 0); 89 ctx.qry.vpath, 0);
75 } 90 }
76 deco = deco->next; 91 deco = deco->next;
77 } 92 }
78} 93}
79 94
80void print_commit(struct commit *commit) 95void print_commit(struct commit *commit, struct rev_info *revs)
81{ 96{
82 struct commitinfo *info; 97 struct commitinfo *info;
83 char *tmp; 98 char *tmp;
84 int cols = 2; 99 int cols = revs->graph ? 3 : 2;
100 struct strbuf graphbuf = STRBUF_INIT;
101 struct strbuf msgbuf = STRBUF_INIT;
102
103 if (ctx.repo->enable_log_filecount) {
104 cols++;
105 if (ctx.repo->enable_log_linecount)
106 cols++;
107 }
108
109 if (revs->graph) {
110 /* Advance graph until current commit */
111 while (!graph_next_line(revs->graph, &graphbuf)) {
112 /* Print graph segment in otherwise empty table row */
113 html("<tr class='nohover'><td class='commitgraph'>");
114 html(graphbuf.buf);
115 htmlf("</td><td colspan='%d' /></tr>\n", cols);
116 strbuf_setlen(&graphbuf, 0);
117 }
118 /* Current commit's graph segment is now ready in graphbuf */
119 }
85 120
86 info = cgit_parse_commit(commit); 121 info = cgit_parse_commit(commit);
87 htmlf("<tr%s><td>", 122 htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
88 ctx.qry.showmsg ? " class='logheader'" : ""); 123
89 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 124 if (revs->graph) {
90 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 125 /* Print graph segment for current commit */
91 html_link_open(tmp, NULL, NULL); 126 html("<td class='commitgraph'>");
92 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 127 html(graphbuf.buf);
93 html_link_close(); 128 html("</td>");
94 htmlf("</td><td%s>", 129 strbuf_setlen(&graphbuf, 0);
95 ctx.qry.showmsg ? " class='logsubject'" : ""); 130 }
131 else {
132 html("<td>");
133 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
134 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
135 html_link_open(tmp, NULL, NULL);
136 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
137 html_link_close();
138 html("</td>");
139 }
140
141 htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
142 if (ctx.qry.showmsg) {
143 /* line-wrap long commit subjects instead of truncating them */
144 size_t subject_len = strlen(info->subject);
145
146 if (subject_len > ctx.cfg.max_msg_len &&
147 ctx.cfg.max_msg_len >= 15) {
148 /* symbol for signaling line-wrap (in PAGE_ENCODING) */
149 const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
150 int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
151
152 /* Rewind i to preceding space character */
153 while (i > 0 && !isspace(info->subject[i]))
154 --i;
155 if (!i) /* Oops, zero spaces. Reset i */
156 i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
157
158 /* add remainder starting at i to msgbuf */
159 strbuf_add(&msgbuf, info->subject + i, subject_len - i);
160 strbuf_trim(&msgbuf);
161 strbuf_add(&msgbuf, "\n\n", 2);
162
163 /* Place wrap_symbol at position i in info->subject */
164 strcpy(info->subject + i, wrap_symbol);
165 }
166 }
96 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 167 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
97 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 168 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
98 show_commit_decorations(commit); 169 show_commit_decorations(commit);
99 html("</td><td>"); 170 html("</td><td>");
100 html_txt(info->author); 171 html_txt(info->author);
172
173 if (revs->graph) {
174 html("</td><td>");
175 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
176 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
177 html_link_open(tmp, NULL, NULL);
178 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
179 html_link_close();
180 }
181
101 if (ctx.repo->enable_log_filecount) { 182 if (ctx.repo->enable_log_filecount) {
102 files = 0; 183 files = 0;
103 add_lines = 0; 184 add_lines = 0;
104 rem_lines = 0; 185 rem_lines = 0;
105 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); 186 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
106 html("</td><td>"); 187 html("</td><td>");
107 htmlf("%d", files); 188 htmlf("%d", files);
108 if (ctx.repo->enable_log_linecount) { 189 if (ctx.repo->enable_log_linecount) {
109 html("</td><td>"); 190 html("</td><td>");
110 htmlf("-%d/+%d", rem_lines, add_lines); 191 htmlf("-%d/+%d", rem_lines, add_lines);
111 } 192 }
112 } 193 }
113 html("</td></tr>\n"); 194 html("</td></tr>\n");
114 if (ctx.qry.showmsg) {
115 struct strbuf notes = STRBUF_INIT;
116 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0);
117 195
118 if (ctx.repo->enable_log_filecount) { 196 if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
119 cols++; 197 html("<tr class='nohover'>");
120 if (ctx.repo->enable_log_linecount) 198
121 cols++; 199 if (ctx.qry.showmsg) {
200 /* Concatenate commit message + notes in msgbuf */
201 if (info->msg && *(info->msg)) {
202 strbuf_addstr(&msgbuf, info->msg);
203 strbuf_addch(&msgbuf, '\n');
204 }
205 format_note(NULL, commit->object.sha1, &msgbuf,
206 PAGE_ENCODING,
207 NOTES_SHOW_HEADER | NOTES_INDENT);
208 strbuf_addch(&msgbuf, '\n');
209 strbuf_ltrim(&msgbuf);
122 } 210 }
123 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 211
124 cols); 212 if (revs->graph) {
125 html_txt(info->msg); 213 int lines = 0;
126 html("</td></tr>\n"); 214
127 if (notes.len != 0) { 215 /* Calculate graph padding */
128 html("<tr class='nohover'>"); 216 if (ctx.qry.showmsg) {
129 html("<td class='lognotes-label'>Notes:</td>"); 217 /* Count #lines in commit message + notes */
130 htmlf("<td colspan='%d' class='lognotes'>", 218 const char *p = msgbuf.buf;
131 cols); 219 lines = 1;
132 html_txt(notes.buf); 220 while ((p = strchr(p, '\n'))) {
133 html("</td></tr>\n"); 221 p++;
222 lines++;
223 }
224 }
225
226 /* Print graph padding */
227 html("<td class='commitgraph'>");
228 while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
229 if (graphbuf.len)
230 html("\n");
231 strbuf_setlen(&graphbuf, 0);
232 graph_next_line(revs->graph, &graphbuf);
233 html(graphbuf.buf);
234 lines--;
235 }
236 html("</td>\n");
134 } 237 }
135 strbuf_release(&notes); 238 else
239 html("<td/>"); /* Empty 'Age' column */
240
241 /* Print msgbuf into remainder of table row */
242 htmlf("<td colspan='%d'%s>\n", cols,
243 ctx.qry.showmsg ? " class='logmsg'" : "");
244 html_txt(msgbuf.buf);
245 html("</td></tr>\n");
136 } 246 }
247
248 strbuf_release(&msgbuf);
249 strbuf_release(&graphbuf);
137 cgit_free_commitinfo(info); 250 cgit_free_commitinfo(info);
138} 251}
139 252
140static const char *disambiguate_ref(const char *ref) 253static const char *disambiguate_ref(const char *ref)
141{ 254{
142 unsigned char sha1[20]; 255 unsigned char sha1[20];
143 const char *longref; 256 const char *longref;
144 257
145 longref = fmt("refs/heads/%s", ref); 258 longref = fmt("refs/heads/%s", ref);
146 if (get_sha1(longref, sha1) == 0) 259 if (get_sha1(longref, sha1) == 0)
147 return longref; 260 return longref;
148 261
149 return ref; 262 return ref;
150} 263}
151 264
152static char *next_token(char **src) 265static char *next_token(char **src)
153{ 266{
154 char *result; 267 char *result;
155 268
156 if (!src || !*src) 269 if (!src || !*src)
157 return NULL; 270 return NULL;
158 while (isspace(**src)) 271 while (isspace(**src))
159 (*src)++; 272 (*src)++;
160 if (!**src) 273 if (!**src)
161 return NULL; 274 return NULL;
162 result = *src; 275 result = *src;
163 while (**src) { 276 while (**src) {
164 if (isspace(**src)) { 277 if (isspace(**src)) {
165 **src = '\0'; 278 **src = '\0';
166 (*src)++; 279 (*src)++;
167 break; 280 break;
168 } 281 }
169 (*src)++; 282 (*src)++;
170 } 283 }
171 return result; 284 return result;
172} 285}
173 286
174void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 287void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
175 char *path, int pager) 288 char *path, int pager, int commit_graph)
176{ 289{
177 struct rev_info rev; 290 struct rev_info rev;
178 struct commit *commit; 291 struct commit *commit;
179 struct vector vec = VECTOR_INIT(char *); 292 struct vector vec = VECTOR_INIT(char *);
180 int i, columns = 3; 293 int i, columns = 3;
181 char *arg; 294 char *arg;
182 295
183 /* First argv is NULL */ 296 /* First argv is NULL */
184 vector_push(&vec, NULL, 0); 297 vector_push(&vec, NULL, 0);
185 298
186 if (!tip) 299 if (!tip)
187 tip = ctx.qry.head; 300 tip = ctx.qry.head;
188 tip = disambiguate_ref(tip); 301 tip = disambiguate_ref(tip);
189 vector_push(&vec, &tip, 0); 302 vector_push(&vec, &tip, 0);
190 303
191 if (grep && pattern && *pattern) { 304 if (grep && pattern && *pattern) {
192 pattern = xstrdup(pattern); 305 pattern = xstrdup(pattern);
193 if (!strcmp(grep, "grep") || !strcmp(grep, "author") || 306 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
194 !strcmp(grep, "committer")) { 307 !strcmp(grep, "committer")) {
195 arg = fmt("--%s=%s", grep, pattern); 308 arg = fmt("--%s=%s", grep, pattern);
196 vector_push(&vec, &arg, 0); 309 vector_push(&vec, &arg, 0);
197 } 310 }
198 if (!strcmp(grep, "range")) { 311 if (!strcmp(grep, "range")) {
199 /* Split the pattern at whitespace and add each token 312 /* Split the pattern at whitespace and add each token
200 * as a revision expression. Do not accept other 313 * as a revision expression. Do not accept other
201 * rev-list options. Also, replace the previously 314 * rev-list options. Also, replace the previously
202 * pushed tip (it's no longer relevant). 315 * pushed tip (it's no longer relevant).
203 */ 316 */
204 vec.count--; 317 vec.count--;
205 while ((arg = next_token(&pattern))) { 318 while ((arg = next_token(&pattern))) {
206 if (*arg == '-') { 319 if (*arg == '-') {
207 fprintf(stderr, "Bad range expr: %s\n", 320 fprintf(stderr, "Bad range expr: %s\n",
208 arg); 321 arg);
209 break; 322 break;
210 } 323 }
211 vector_push(&vec, &arg, 0); 324 vector_push(&vec, &arg, 0);
212 } 325 }
213 } 326 }
214 } 327 }
328 if (commit_graph) {
329 static const char *graph_arg = "--graph";
330 static const char *color_arg = "--color";
331 vector_push(&vec, &graph_arg, 0);
332 vector_push(&vec, &color_arg, 0);
333 graph_set_column_colors(column_colors_html,
334 COLUMN_COLORS_HTML_MAX);
335 }
215 336
216 if (path) { 337 if (path) {
217 arg = "--"; 338 arg = "--";
218 vector_push(&vec, &arg, 0); 339 vector_push(&vec, &arg, 0);
219 vector_push(&vec, &path, 0); 340 vector_push(&vec, &path, 0);
220 } 341 }
221 342
222 /* Make sure the vector is NULL-terminated */ 343 /* Make sure the vector is NULL-terminated */
223 vector_push(&vec, NULL, 0); 344 vector_push(&vec, NULL, 0);
224 vec.count--; 345 vec.count--;
225 346
226 init_revisions(&rev, NULL); 347 init_revisions(&rev, NULL);
227 rev.abbrev = DEFAULT_ABBREV; 348 rev.abbrev = DEFAULT_ABBREV;
228 rev.commit_format = CMIT_FMT_DEFAULT; 349 rev.commit_format = CMIT_FMT_DEFAULT;
229 rev.verbose_header = 1; 350 rev.verbose_header = 1;
230 rev.show_root_diff = 0; 351 rev.show_root_diff = 0;
231 setup_revisions(vec.count, vec.data, &rev, NULL); 352 setup_revisions(vec.count, vec.data, &rev, NULL);
232 load_ref_decorations(DECORATE_FULL_REFS); 353 load_ref_decorations(DECORATE_FULL_REFS);
233 rev.show_decorations = 1; 354 rev.show_decorations = 1;
234 rev.grep_filter.regflags |= REG_ICASE; 355 rev.grep_filter.regflags |= REG_ICASE;
235 compile_grep_patterns(&rev.grep_filter); 356 compile_grep_patterns(&rev.grep_filter);
236 prepare_revision_walk(&rev); 357 prepare_revision_walk(&rev);
237 358
238 if (pager) 359 if (pager)
239 html("<table class='list nowrap'>"); 360 html("<table class='list nowrap'>");
240 361
241 html("<tr class='nohover'><th class='left'>Age</th>" 362 html("<tr class='nohover'>");
242 "<th class='left'>Commit message"); 363 if (commit_graph)
364 html("<th></th>");
365 else
366 html("<th class='left'>Age</th>");
367 html("<th class='left'>Commit message");
243 if (pager) { 368 if (pager) {
244 html(" ("); 369 html(" (");
245 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 370 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
246 NULL, ctx.qry.head, ctx.qry.sha1, 371 NULL, ctx.qry.head, ctx.qry.sha1,
247 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 372 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
248 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 373 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
249 html(")"); 374 html(")");
250 } 375 }
251 html("</th><th class='left'>Author</th>"); 376 html("</th><th class='left'>Author</th>");
377 if (commit_graph)
378 html("<th class='left'>Age</th>");
252 if (ctx.repo->enable_log_filecount) { 379 if (ctx.repo->enable_log_filecount) {
253 html("<th class='left'>Files</th>"); 380 html("<th class='left'>Files</th>");
254 columns++; 381 columns++;
255 if (ctx.repo->enable_log_linecount) { 382 if (ctx.repo->enable_log_linecount) {
256 html("<th class='left'>Lines</th>"); 383 html("<th class='left'>Lines</th>");
257 columns++; 384 columns++;
258 } 385 }
259 } 386 }
260 html("</tr>\n"); 387 html("</tr>\n");
261 388
262 if (ofs<0) 389 if (ofs<0)
263 ofs = 0; 390 ofs = 0;
264 391
265 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 392 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
266 free(commit->buffer); 393 free(commit->buffer);
267 commit->buffer = NULL; 394 commit->buffer = NULL;
268 free_commit_list(commit->parents); 395 free_commit_list(commit->parents);
269 commit->parents = NULL; 396 commit->parents = NULL;
270 } 397 }
271 398
272 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 399 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
273 print_commit(commit); 400 print_commit(commit, &rev);
274 free(commit->buffer); 401 free(commit->buffer);
275 commit->buffer = NULL; 402 commit->buffer = NULL;
276 free_commit_list(commit->parents); 403 free_commit_list(commit->parents);
277 commit->parents = NULL; 404 commit->parents = NULL;
278 } 405 }
279 if (pager) { 406 if (pager) {
280 html("</table><div class='pager'>"); 407 html("</table><div class='pager'>");
281 if (ofs > 0) { 408 if (ofs > 0) {
282 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 409 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
283 ctx.qry.sha1, ctx.qry.vpath, 410 ctx.qry.sha1, ctx.qry.vpath,
284 ofs - cnt, ctx.qry.grep, 411 ofs - cnt, ctx.qry.grep,
285 ctx.qry.search, ctx.qry.showmsg); 412 ctx.qry.search, ctx.qry.showmsg);
286 html("&nbsp;"); 413 html("&nbsp;");
287 } 414 }
288 if ((commit = get_revision(&rev)) != NULL) { 415 if ((commit = get_revision(&rev)) != NULL) {
289 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 416 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
290 ctx.qry.sha1, ctx.qry.vpath, 417 ctx.qry.sha1, ctx.qry.vpath,
291 ofs + cnt, ctx.qry.grep, 418 ofs + cnt, ctx.qry.grep,
292 ctx.qry.search, ctx.qry.showmsg); 419 ctx.qry.search, ctx.qry.showmsg);
293 } 420 }
294 html("</div>"); 421 html("</div>");
295 } else if ((commit = get_revision(&rev)) != NULL) { 422 } else if ((commit = get_revision(&rev)) != NULL) {
296 html("<tr class='nohover'><td colspan='3'>"); 423 html("<tr class='nohover'><td colspan='3'>");
297 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, 424 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
298 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); 425 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
299 html("</td></tr>\n"); 426 html("</td></tr>\n");
300 } 427 }
301} 428}
diff --git a/ui-log.h b/ui-log.h
index 6034055..d0cb779 100644
--- a/ui-log.h
+++ b/ui-log.h
@@ -1,8 +1,9 @@
1#ifndef UI_LOG_H 1#ifndef UI_LOG_H
2#define UI_LOG_H 2#define UI_LOG_H
3 3
4extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, 4extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep,
5 char *pattern, char *path, int pager); 5 char *pattern, char *path, int pager,
6 int commit_graph);
6extern void show_commit_decorations(struct commit *commit); 7extern void show_commit_decorations(struct commit *commit);
7 8
8#endif /* UI_LOG_H */ 9#endif /* UI_LOG_H */
diff --git a/ui-summary.c b/ui-summary.c
index b203bcc..5be2545 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -1,124 +1,124 @@
1/* ui-summary.c: functions for generating repo summary page 1/* ui-summary.c: functions for generating repo summary page
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> 4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-log.h" 12#include "ui-log.h"
13#include "ui-refs.h" 13#include "ui-refs.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15 15
16int urls = 0; 16int urls = 0;
17 17
18static void print_url(char *base, char *suffix) 18static void print_url(char *base, char *suffix)
19{ 19{
20 if (!base || !*base) 20 if (!base || !*base)
21 return; 21 return;
22 if (urls++ == 0) { 22 if (urls++ == 0) {
23 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 23 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
24 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n"); 24 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n");
25 } 25 }
26 if (suffix && *suffix) 26 if (suffix && *suffix)
27 base = fmt("%s/%s", base, suffix); 27 base = fmt("%s/%s", base, suffix);
28 html("<tr><td colspan='4'><a href='"); 28 html("<tr><td colspan='4'><a href='");
29 html_url_path(base); 29 html_url_path(base);
30 html("'>"); 30 html("'>");
31 html_txt(base); 31 html_txt(base);
32 html("</a></td></tr>\n"); 32 html("</a></td></tr>\n");
33} 33}
34 34
35static void print_urls(char *txt, char *suffix) 35static void print_urls(char *txt, char *suffix)
36{ 36{
37 char *h = txt, *t, c; 37 char *h = txt, *t, c;
38 38
39 while (h && *h) { 39 while (h && *h) {
40 while (h && *h == ' ') 40 while (h && *h == ' ')
41 h++; 41 h++;
42 t = h; 42 t = h;
43 while (t && *t && *t != ' ') 43 while (t && *t && *t != ' ')
44 t++; 44 t++;
45 c = *t; 45 c = *t;
46 *t = 0; 46 *t = 0;
47 print_url(h, suffix); 47 print_url(h, suffix);
48 *t = c; 48 *t = c;
49 h = t; 49 h = t;
50 } 50 }
51} 51}
52 52
53void cgit_print_summary() 53void cgit_print_summary()
54{ 54{
55 html("<table summary='repository info' class='list nowrap'>"); 55 html("<table summary='repository info' class='list nowrap'>");
56 cgit_print_branches(ctx.cfg.summary_branches); 56 cgit_print_branches(ctx.cfg.summary_branches);
57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
58 cgit_print_tags(ctx.cfg.summary_tags); 58 cgit_print_tags(ctx.cfg.summary_tags);
59 if (ctx.cfg.summary_log > 0) { 59 if (ctx.cfg.summary_log > 0) {
60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
61 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, 61 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
62 NULL, NULL, 0); 62 NULL, NULL, 0, 0);
63 } 63 }
64 if (ctx.repo->clone_url) 64 if (ctx.repo->clone_url)
65 print_urls(ctx.repo->clone_url, NULL); 65 print_urls(ctx.repo->clone_url, NULL);
66 else if (ctx.cfg.clone_prefix) 66 else if (ctx.cfg.clone_prefix)
67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url); 67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
68 html("</table>"); 68 html("</table>");
69} 69}
70 70
71void cgit_print_repo_readme(char *path) 71void cgit_print_repo_readme(char *path)
72{ 72{
73 char *slash, *tmp, *colon, *ref; 73 char *slash, *tmp, *colon, *ref;
74 74
75 if (!ctx.repo->readme || !(*ctx.repo->readme)) 75 if (!ctx.repo->readme || !(*ctx.repo->readme))
76 return; 76 return;
77 77
78 ref = NULL; 78 ref = NULL;
79 79
80 /* Check if the readme is tracked in the git repo. */ 80 /* Check if the readme is tracked in the git repo. */
81 colon = strchr(ctx.repo->readme, ':'); 81 colon = strchr(ctx.repo->readme, ':');
82 if (colon && strlen(colon) > 1) { 82 if (colon && strlen(colon) > 1) {
83 *colon = '\0'; 83 *colon = '\0';
84 ref = ctx.repo->readme; 84 ref = ctx.repo->readme;
85 ctx.repo->readme = colon + 1; 85 ctx.repo->readme = colon + 1;
86 if (!(*ctx.repo->readme)) 86 if (!(*ctx.repo->readme))
87 return; 87 return;
88 } 88 }
89 89
90 /* Prepend repo path to relative readme path unless tracked. */ 90 /* Prepend repo path to relative readme path unless tracked. */
91 if (!ref && *ctx.repo->readme != '/') 91 if (!ref && *ctx.repo->readme != '/')
92 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, 92 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path,
93 ctx.repo->readme)); 93 ctx.repo->readme));
94 94
95 /* If a subpath is specified for the about page, make it relative 95 /* If a subpath is specified for the about page, make it relative
96 * to the directory containing the configured readme. 96 * to the directory containing the configured readme.
97 */ 97 */
98 if (path) { 98 if (path) {
99 slash = strrchr(ctx.repo->readme, '/'); 99 slash = strrchr(ctx.repo->readme, '/');
100 if (!slash) { 100 if (!slash) {
101 if (!colon) 101 if (!colon)
102 return; 102 return;
103 slash = colon; 103 slash = colon;
104 } 104 }
105 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1); 105 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
106 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1); 106 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
107 strcpy(tmp + (slash - ctx.repo->readme + 1), path); 107 strcpy(tmp + (slash - ctx.repo->readme + 1), path);
108 } else 108 } else
109 tmp = ctx.repo->readme; 109 tmp = ctx.repo->readme;
110 110
111 /* Print the calculated readme, either from the git repo or from the 111 /* Print the calculated readme, either from the git repo or from the
112 * filesystem, while applying the about-filter. 112 * filesystem, while applying the about-filter.
113 */ 113 */
114 html("<div id='summary'>"); 114 html("<div id='summary'>");
115 if (ctx.repo->about_filter) 115 if (ctx.repo->about_filter)
116 cgit_open_filter(ctx.repo->about_filter); 116 cgit_open_filter(ctx.repo->about_filter);
117 if (ref) 117 if (ref)
118 cgit_print_file(tmp, ref); 118 cgit_print_file(tmp, ref);
119 else 119 else
120 html_include(tmp); 120 html_include(tmp);
121 if (ctx.repo->about_filter) 121 if (ctx.repo->about_filter)
122 cgit_close_filter(ctx.repo->about_filter); 122 cgit_close_filter(ctx.repo->about_filter);
123 html("</div>"); 123 html("</div>");
124} 124}