summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2008-04-13 10:48:44 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2008-04-13 10:48:44 (UTC)
commit76ba6287bfb533baca7285b107b5d975581d449d (patch) (unidiff)
tree22445a77f5b87280ec980f9b4da5a511f1f27faf
parent4a842288260a0b0c4a3d4032d441f7fd2afee699 (diff)
parent28d781f34b2c2d4c2b994ef3953d1cf37d8f28f0 (diff)
downloadcgit-76ba6287bfb533baca7285b107b5d975581d449d.zip
cgit-76ba6287bfb533baca7285b107b5d975581d449d.tar.gz
cgit-76ba6287bfb533baca7285b107b5d975581d449d.tar.bz2
Merge branch 'lh/layout'
* lh/layout: Make repository search case insensitive Remove 'patch' link from tab, add to commit view Implement minimal freetext search in the repolist More layout fixes Minor fixup in tree-view css Reintroduce the branch switcher Add fixed link to index page from repo header Include diff in commit view Replace sidebar/logo
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.css190
-rw-r--r--cgit.h7
-rw-r--r--cgit.pngbin5406 -> 1840 bytes
-rw-r--r--ui-commit.c14
-rw-r--r--ui-repolist.c51
-rw-r--r--ui-shared.c154
-rw-r--r--ui-shared.h2
7 files changed, 238 insertions, 180 deletions
diff --git a/cgit.css b/cgit.css
index 17c2712..8f3d00c 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,447 +1,445 @@
1body, table { 1body, table {
2 padding: 0em; 2 padding: 0em;
3 margin: 0em; 3 margin: 0em;
4} 4}
5 5
6body { 6body {
7 font-family: sans; 7 font-family: sans;
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 {
15 color: blue;
16 text-decoration: none;
17}
18
19a:hover {
20 text-decoration: underline;
21}
22
14table { 23table {
15 border-collapse: collapse; 24 border-collapse: collapse;
16} 25}
17 26
18h2 { 27table#header {
19 font-size: 120%; 28 width: 100%;
20 font-weight: bold; 29 margin-bottom: 1em;
21 margin-top: 0em;
22 margin-bottom: 0.25em;
23} 30}
24 31
25h3 { 32table#header td.logo {
26 margin-top: 0em; 33 width: 96px;
27 font-size: 100%;
28 font-weight: normal;
29} 34}
30 35
31h4 { 36table#header td.main {
32 margin-top: 1.5em; 37 font-size: 250%;
33 margin-bottom: 0.1em; 38 padding-left: 10px;
34 font-size: 100%;
35 font-weight: bold;
36} 39}
37 40
38a { 41table#header td.main a {
39 color: #600; 42 color: #000;
40 text-decoration: none;
41} 43}
42 44
43a:hover { 45table#header td.form {
44 background-color: #ddd; 46 text-align: right;
45 text-decoration: none; 47 vertical-align: bottom;
48 padding-right: 1em;
49 padding-bottom: 2px;
46} 50}
47 51
48table.list { 52table#header td.form form,
49 border: none; 53table#header td.form input,
50 border-collapse: collapse; 54table#header td.form select {
55 font-size: 90%;
51} 56}
52 57
53table.list tr { 58table#header td.sub {
54 background: white; 59 color: #777;
60 border-top: solid 1px #ccc;
61 padding-left: 10px;
55} 62}
56 63
57table.list tr:hover { 64table.tabs {
58 background: #f8f8f8; 65 /* border-bottom: solid 2px #ccc; */
66 border-collapse: collapse;
67 margin-top: 2em;
68 margin-bottom: 0px;
69 width: 100%;
59} 70}
60 71
61table.list tr.nohover:hover { 72table.tabs td {
62 background: white; 73 padding: 0px 1em;
74 vertical-align: bottom;
63} 75}
64 76
65table.list th { 77table.tabs td a {
66 font-weight: bold; 78 padding: 2px 0.75em;
67 border-bottom: solid 1px #777; 79 color: #777;
68 padding: 0.1em 0.5em 0.1em 0.5em; 80 font-size: 110%;
69 vertical-align: baseline;
70} 81}
71 82
72table.list td { 83table.tabs td a.active {
73 border: none; 84 color: #000;
74 padding: 0.1em 0.5em 0.1em 0.5em; 85 background-color: #ccc;
75} 86}
76 87
77img { 88table.tabs td.form {
78 border: none; 89 text-align: right;
79} 90}
80 91
81table#layout { 92table.tabs td.form form {
82 border-collapse: collapse; 93 padding-bottom: 2px;
83 border: none; 94 font-size: 90%;
84 margin: 0px;
85} 95}
86 96
87td#sidebar { 97table.tabs td.form input,
88 vertical-align: top; 98table.tabs td.form select {
89 width: 162px; 99 font-size: 90%;
90 padding: 0px 0px 0px 0px;
91 margin: 0px;
92} 100}
93 101
94td#sidebar table { 102div.content {
95 border-collapse: separate;
96 border-spacing: 0px;
97 margin: 0px; 103 margin: 0px;
98 padding: 0px; 104 padding: 2em;
99 background-color: #ccc; 105 border-top: solid 3px #ccc;
106 border-bottom: solid 3px #ccc;
100} 107}
101 108
102td#sidebar table.sidebar td.sidebar { 109
103 padding: 4px; 110table.list {
104 border-top: solid 1px #eee; 111 width: 100%;
105 border-left: solid 1px #eee; 112 border: none;
106 border-right: solid 1px #aaa; 113 border-collapse: collapse;
107 border-bottom: solid 1px #aaa;
108} 114}
109 115
110div#logo { 116table.list tr {
111 margin: 0px; 117 background: white;
112 padding: 4px 0px 4px 0px;
113 text-align: center;
114 background-color: #ccc;
115 border-top: solid 1px #eee;
116 border-left: solid 1px #eee;
117 border-right: solid 1px #aaa;
118 border-bottom: solid 1px #aaa;
119} 118}
120 119
121td#sidebar h1 { 120table.list tr:hover {
122 font-size: 10pt; 121 background: #eee;
123 font-weight: bold;
124 margin: 8px 0px 0px 0px;
125} 122}
126 123
127td#sidebar h1.first { 124table.list tr.nohover:hover {
128 margin-top: 0px; 125 background: white;
129} 126}
130 127
131td#sidebar a.menu { 128table.list th {
132 display: block; 129 font-weight: bold;
133 background-color: #ccc; 130 /* color: #888;
134 padding: 0.1em 0.5em; 131 border-top: dashed 1px #888;
135 text-decoration: none; 132 border-bottom: dashed 1px #888;
133 */
134 padding: 0.1em 0.5em 0.05em 0.5em;
135 vertical-align: baseline;
136} 136}
137 137
138td#sidebar a.menu:hover { 138table.list td {
139 background-color: #bbb; 139 border: none;
140 text-decoration: none; 140 padding: 0.1em 0.5em 0.1em 0.5em;
141} 141}
142 142
143td#sidebar select { 143table.list td a {
144 width: 100%; 144 color: black;
145 margin: 2px 0px 0px 0px;
146} 145}
147 146
148td#sidebar form { 147img {
149 text-align: right; 148 border: none;
150} 149}
151 150
152input#switch-btn { 151input#switch-btn {
153 margin: 2px 0px 0px 0px; 152 margin: 2px 0px 0px 0px;
154} 153}
155 154
156td#sidebar input.txt { 155td#sidebar input.txt {
157 width: 100%; 156 width: 100%;
158 margin: 2px 0px 0px 0px; 157 margin: 2px 0px 0px 0px;
159} 158}
160 159
161table#grid { 160table#grid {
162 margin: 0px; 161 margin: 0px;
163} 162}
164 163
165td#content { 164td#content {
166 vertical-align: top; 165 vertical-align: top;
167 padding: 1em 2em 1em 1em; 166 padding: 1em 2em 1em 1em;
168 border: none; 167 border: none;
169} 168}
170 169
171div#summary { 170div#summary {
172 vertical-align: top; 171 vertical-align: top;
173 margin-bottom: 1em; 172 margin-bottom: 1em;
174} 173}
175 174
176table#downloads { 175table#downloads {
177 float: right; 176 float: right;
178 border-collapse: collapse; 177 border-collapse: collapse;
179 border: solid 1px #777; 178 border: solid 1px #777;
180 margin-left: 0.5em; 179 margin-left: 0.5em;
181 margin-bottom: 0.5em; 180 margin-bottom: 0.5em;
182} 181}
183 182
184table#downloads th { 183table#downloads th {
185 background-color: #ccc; 184 background-color: #ccc;
186} 185}
187 186
188div#blob { 187div#blob {
189 border: solid 1px black; 188 border: solid 1px black;
190} 189}
191 190
192div.error { 191div.error {
193 color: red; 192 color: red;
194 font-weight: bold; 193 font-weight: bold;
195 margin: 1em 2em; 194 margin: 1em 2em;
196} 195}
197 196
198a.ls-blob, a.ls-dir, a.ls-mod { 197a.ls-blob, a.ls-dir, a.ls-mod {
199 font-family: monospace; 198 font-family: monospace;
200} 199}
201 200
202td.ls-size { 201td.ls-size {
203 text-align: right; 202 text-align: right;
204}
205
206td.ls-size {
207 font-family: monospace; 203 font-family: monospace;
204 width: 10em;
208} 205}
209 206
210td.ls-mode { 207td.ls-mode {
211 font-family: monospace; 208 font-family: monospace;
209 width: 10em;
212} 210}
213 211
214table.blob { 212table.blob {
215 margin-top: 0.5em; 213 margin-top: 0.5em;
216 border-top: solid 1px black; 214 border-top: solid 1px black;
217} 215}
218 216
219table.blob td.no { 217table.blob td.no {
220 border-right: solid 1px black; 218 border-right: solid 1px black;
221 color: black; 219 color: black;
222 background-color: #eee; 220 background-color: #eee;
223 text-align: right; 221 text-align: right;
224} 222}
225 223
226table.blob td.no a { 224table.blob td.no a {
227 color: black; 225 color: black;
228} 226}
229 227
230table.blob td.no a:hover { 228table.blob td.no a:hover {
231 color: black; 229 color: black;
232 text-decoration: none; 230 text-decoration: none;
233} 231}
234 232
235table.blob td.txt { 233table.blob td.txt {
236 white-space: pre; 234 white-space: pre;
237 font-family: monospace; 235 font-family: monospace;
238 padding-left: 0.5em; 236 padding-left: 0.5em;
239} 237}
240 238
241table.nowrap td { 239table.nowrap td {
242 white-space: nowrap; 240 white-space: nowrap;
243} 241}
244 242
245table.commit-info { 243table.commit-info {
246 border-collapse: collapse; 244 border-collapse: collapse;
247 margin-top: 1.5em; 245 margin-top: 1.5em;
248} 246}
249 247
250table.commit-info th { 248table.commit-info th {
251 text-align: left; 249 text-align: left;
252 font-weight: normal; 250 font-weight: normal;
253 padding: 0.1em 1em 0.1em 0.1em; 251 padding: 0.1em 1em 0.1em 0.1em;
254 vertical-align: top; 252 vertical-align: top;
255} 253}
256 254
257table.commit-info td { 255table.commit-info td {
258 font-weight: normal; 256 font-weight: normal;
259 padding: 0.1em 1em 0.1em 0.1em; 257 padding: 0.1em 1em 0.1em 0.1em;
260} 258}
261 259
262div.commit-subject { 260div.commit-subject {
263 font-weight: bold; 261 font-weight: bold;
264 font-size: 125%; 262 font-size: 125%;
265 margin: 1.5em 0em 0.5em 0em; 263 margin: 1.5em 0em 0.5em 0em;
266 padding: 0em; 264 padding: 0em;
267} 265}
268 266
269div.commit-msg { 267div.commit-msg {
270 white-space: pre; 268 white-space: pre;
271 font-family: monospace; 269 font-family: monospace;
272} 270}
273 271
274div.diffstat-header { 272div.diffstat-header {
275 font-weight: bold; 273 font-weight: bold;
276 padding-top: 1.5em; 274 padding-top: 1.5em;
277} 275}
278 276
279table.diffstat { 277table.diffstat {
280 border-collapse: collapse; 278 border-collapse: collapse;
281 border: solid 1px #aaa; 279 border: solid 1px #aaa;
282 background-color: #eee; 280 background-color: #eee;
283} 281}
284 282
285table.diffstat th { 283table.diffstat th {
286 font-weight: normal; 284 font-weight: normal;
287 text-align: left; 285 text-align: left;
288 text-decoration: underline; 286 text-decoration: underline;
289 padding: 0.1em 1em 0.1em 0.1em; 287 padding: 0.1em 1em 0.1em 0.1em;
290 font-size: 100%; 288 font-size: 100%;
291} 289}
292 290
293table.diffstat td { 291table.diffstat td {
294 padding: 0.2em 0.2em 0.1em 0.1em; 292 padding: 0.2em 0.2em 0.1em 0.1em;
295 font-size: 100%; 293 font-size: 100%;
296 border: none; 294 border: none;
297} 295}
298 296
299table.diffstat td.mode { 297table.diffstat td.mode {
300 white-space: nowrap; 298 white-space: nowrap;
301} 299}
302 300
303table.diffstat td span.modechange { 301table.diffstat td span.modechange {
304 padding-left: 1em; 302 padding-left: 1em;
305 color: red; 303 color: red;
306} 304}
307 305
308table.diffstat td.add a { 306table.diffstat td.add a {
309 color: green; 307 color: green;
310} 308}
311 309
312table.diffstat td.del a { 310table.diffstat td.del a {
313 color: red; 311 color: red;
314} 312}
315 313
316table.diffstat td.upd a { 314table.diffstat td.upd a {
317 color: blue; 315 color: blue;
318} 316}
319 317
320table.diffstat td.graph { 318table.diffstat td.graph {
321 width: 500px; 319 width: 500px;
322 vertical-align: middle; 320 vertical-align: middle;
323} 321}
324 322
325table.diffstat td.graph table { 323table.diffstat td.graph table {
326 border: none; 324 border: none;
327} 325}
328 326
329table.diffstat td.graph td { 327table.diffstat td.graph td {
330 padding: 0px; 328 padding: 0px;
331 border: 0px; 329 border: 0px;
332 height: 7pt; 330 height: 7pt;
333} 331}
334 332
335table.diffstat td.graph td.add { 333table.diffstat td.graph td.add {
336 background-color: #5c5; 334 background-color: #5c5;
337} 335}
338 336
339table.diffstat td.graph td.rem { 337table.diffstat td.graph td.rem {
340 background-color: #c55; 338 background-color: #c55;
341} 339}
342 340
343div.diffstat-summary { 341div.diffstat-summary {
344 color: #888; 342 color: #888;
345 padding-top: 0.5em; 343 padding-top: 0.5em;
346} 344}
347 345
348table.diff { 346table.diff {
349 width: 100%; 347 width: 100%;
350} 348}
351 349
352table.diff td { 350table.diff td {
353 font-family: monospace; 351 font-family: monospace;
354 white-space: pre; 352 white-space: pre;
355} 353}
356 354
357table.diff td div.head { 355table.diff td div.head {
358 font-weight: bold; 356 font-weight: bold;
359 margin-top: 1em; 357 margin-top: 1em;
360 background-color: #eee; 358 color: black;
361} 359}
362 360
363table.diff td div.hunk { 361table.diff td div.hunk {
364 color: #009; 362 color: #009;
365} 363}
366 364
367table.diff td div.add { 365table.diff td div.add {
368 color: green; 366 color: green;
369} 367}
370 368
371table.diff td div.del { 369table.diff td div.del {
372 color: red; 370 color: red;
373} 371}
374 372
375.sha1 { 373.sha1 {
376 font-family: monospace; 374 font-family: monospace;
377 font-size: 90%; 375 font-size: 90%;
378} 376}
379 377
380.left { 378.left {
381 text-align: left; 379 text-align: left;
382} 380}
383 381
384.right { 382.right {
385 text-align: right; 383 text-align: right;
386} 384}
387 385
388table.list td.repogroup { 386table.list td.repogroup {
389 font-style: italic; 387 font-style: italic;
390 color: #888; 388 color: #888;
391} 389}
392 390
393a.button { 391a.button {
394 font-size: 80%; 392 font-size: 80%;
395 color: #aaa; 393 color: #33c;
394/*
396 background-color: #eee; 395 background-color: #eee;
397 border: solid 1px #aaa; 396 border: solid 1px #aaa;
398 padding: 0em 0.5em;
399 margin: 0.1em 0.25em; 397 margin: 0.1em 0.25em;
398*/
399 padding: 0em 0.5em;
400} 400}
401 401
402a.button:hover { 402a.button:hover {
403 text-decoration: none; 403 text-decoration: underline;
404 color: #333;
405 background-color: #ccc;
406} 404}
407 405
408a.primary { 406a.primary {
409 font-size: 100%; 407 font-size: 100%;
410} 408}
411 409
412a.secondary { 410a.secondary {
413 font-size: 90%; 411 font-size: 90%;
414} 412}
415 413
416td.toplevel-repo { 414td.toplevel-repo {
417 415
418} 416}
419 417
420table.list td.sublevel-repo { 418table.list td.sublevel-repo {
421 padding-left: 1.5em; 419 padding-left: 1.5em;
422} 420}
423 421
424span.age-mins { 422span.age-mins {
425 font-weight: bold; 423 font-weight: bold;
426 color: #080; 424 color: #080;
427} 425}
428 426
429span.age-hours { 427span.age-hours {
430 color: #080; 428 color: #080;
431} 429}
432 430
433span.age-days { 431span.age-days {
434 color: #040; 432 color: #040;
435} 433}
436 434
437span.age-weeks { 435span.age-weeks {
438 color: #444; 436 color: #444;
439} 437}
440 438
441span.age-months { 439span.age-months {
442 color: #888; 440 color: #888;
443} 441}
444 442
445span.age-years { 443span.age-years {
446 color: #bbb; 444 color: #bbb;
447} 445}
diff --git a/cgit.h b/cgit.h
index ee8c716..a3b6535 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,224 +1,231 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <xdiff/xdiff.h> 18#include <xdiff/xdiff.h>
19#include <utf8.h> 19#include <utf8.h>
20 20
21 21
22/* 22/*
23 * Dateformats used on misc. pages 23 * Dateformats used on misc. pages
24 */ 24 */
25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S" 25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S"
26#define FMT_SHORTDATE "%Y-%m-%d" 26#define FMT_SHORTDATE "%Y-%m-%d"
27 27
28 28
29/* 29/*
30 * Limits used for relative dates 30 * Limits used for relative dates
31 */ 31 */
32#define TM_MIN 60 32#define TM_MIN 60
33#define TM_HOUR (TM_MIN * 60) 33#define TM_HOUR (TM_MIN * 60)
34#define TM_DAY (TM_HOUR * 24) 34#define TM_DAY (TM_HOUR * 24)
35#define TM_WEEK (TM_DAY * 7) 35#define TM_WEEK (TM_DAY * 7)
36#define TM_YEAR (TM_DAY * 365) 36#define TM_YEAR (TM_DAY * 365)
37#define TM_MONTH (TM_YEAR / 12.0) 37#define TM_MONTH (TM_YEAR / 12.0)
38 38
39 39
40/* 40/*
41 * Default encoding 41 * Default encoding
42 */ 42 */
43#define PAGE_ENCODING "UTF-8" 43#define PAGE_ENCODING "UTF-8"
44 44
45typedef void (*configfn)(const char *name, const char *value); 45typedef void (*configfn)(const char *name, const char *value);
46typedef void (*filepair_fn)(struct diff_filepair *pair); 46typedef void (*filepair_fn)(struct diff_filepair *pair);
47typedef void (*linediff_fn)(char *line, int len); 47typedef void (*linediff_fn)(char *line, int len);
48 48
49struct cgit_repo { 49struct cgit_repo {
50 char *url; 50 char *url;
51 char *name; 51 char *name;
52 char *path; 52 char *path;
53 char *desc; 53 char *desc;
54 char *owner; 54 char *owner;
55 char *defbranch; 55 char *defbranch;
56 char *group; 56 char *group;
57 char *module_link; 57 char *module_link;
58 char *readme; 58 char *readme;
59 char *clone_url; 59 char *clone_url;
60 int snapshots; 60 int snapshots;
61 int enable_log_filecount; 61 int enable_log_filecount;
62 int enable_log_linecount; 62 int enable_log_linecount;
63}; 63};
64 64
65struct cgit_repolist { 65struct cgit_repolist {
66 int length; 66 int length;
67 int count; 67 int count;
68 struct cgit_repo *repos; 68 struct cgit_repo *repos;
69}; 69};
70 70
71struct commitinfo { 71struct commitinfo {
72 struct commit *commit; 72 struct commit *commit;
73 char *author; 73 char *author;
74 char *author_email; 74 char *author_email;
75 unsigned long author_date; 75 unsigned long author_date;
76 char *committer; 76 char *committer;
77 char *committer_email; 77 char *committer_email;
78 unsigned long committer_date; 78 unsigned long committer_date;
79 char *subject; 79 char *subject;
80 char *msg; 80 char *msg;
81 char *msg_encoding; 81 char *msg_encoding;
82}; 82};
83 83
84struct taginfo { 84struct taginfo {
85 char *tagger; 85 char *tagger;
86 char *tagger_email; 86 char *tagger_email;
87 int tagger_date; 87 int tagger_date;
88 char *msg; 88 char *msg;
89}; 89};
90 90
91struct refinfo { 91struct refinfo {
92 const char *refname; 92 const char *refname;
93 struct object *object; 93 struct object *object;
94 union { 94 union {
95 struct taginfo *tag; 95 struct taginfo *tag;
96 struct commitinfo *commit; 96 struct commitinfo *commit;
97 }; 97 };
98}; 98};
99 99
100struct reflist { 100struct reflist {
101 struct refinfo **refs; 101 struct refinfo **refs;
102 int alloc; 102 int alloc;
103 int count; 103 int count;
104}; 104};
105 105
106struct cgit_query { 106struct cgit_query {
107 int has_symref; 107 int has_symref;
108 int has_sha1; 108 int has_sha1;
109 char *raw; 109 char *raw;
110 char *repo; 110 char *repo;
111 char *page; 111 char *page;
112 char *search; 112 char *search;
113 char *grep; 113 char *grep;
114 char *head; 114 char *head;
115 char *sha1; 115 char *sha1;
116 char *sha2; 116 char *sha2;
117 char *path; 117 char *path;
118 char *name; 118 char *name;
119 int ofs; 119 int ofs;
120}; 120};
121 121
122struct cgit_config { 122struct cgit_config {
123 char *agefile; 123 char *agefile;
124 char *cache_root; 124 char *cache_root;
125 char *clone_prefix; 125 char *clone_prefix;
126 char *css; 126 char *css;
127 char *index_header; 127 char *index_header;
128 char *index_info; 128 char *index_info;
129 char *logo; 129 char *logo;
130 char *logo_link; 130 char *logo_link;
131 char *module_link; 131 char *module_link;
132 char *repo_group; 132 char *repo_group;
133 char *robots; 133 char *robots;
134 char *root_title; 134 char *root_title;
135 char *script_name; 135 char *script_name;
136 char *virtual_root; 136 char *virtual_root;
137 int cache_dynamic_ttl; 137 int cache_dynamic_ttl;
138 int cache_max_create_time; 138 int cache_max_create_time;
139 int cache_repo_ttl; 139 int cache_repo_ttl;
140 int cache_root_ttl; 140 int cache_root_ttl;
141 int cache_static_ttl; 141 int cache_static_ttl;
142 int enable_index_links; 142 int enable_index_links;
143 int enable_log_filecount; 143 int enable_log_filecount;
144 int enable_log_linecount; 144 int enable_log_linecount;
145 int max_commit_count; 145 int max_commit_count;
146 int max_lock_attempts; 146 int max_lock_attempts;
147 int max_msg_len; 147 int max_msg_len;
148 int max_repodesc_len; 148 int max_repodesc_len;
149 int nocache; 149 int nocache;
150 int renamelimit; 150 int renamelimit;
151 int snapshots; 151 int snapshots;
152 int summary_branches; 152 int summary_branches;
153 int summary_log; 153 int summary_log;
154 int summary_tags; 154 int summary_tags;
155}; 155};
156 156
157struct cgit_page { 157struct cgit_page {
158 time_t modified; 158 time_t modified;
159 time_t expires; 159 time_t expires;
160 char *mimetype; 160 char *mimetype;
161 char *charset; 161 char *charset;
162 char *filename; 162 char *filename;
163 char *title; 163 char *title;
164}; 164};
165 165
166struct cgit_context { 166struct cgit_context {
167 struct cgit_query qry; 167 struct cgit_query qry;
168 struct cgit_config cfg; 168 struct cgit_config cfg;
169 struct cgit_repo *repo; 169 struct cgit_repo *repo;
170 struct cgit_page page; 170 struct cgit_page page;
171}; 171};
172 172
173struct cgit_snapshot_format { 173struct cgit_snapshot_format {
174 const char *suffix; 174 const char *suffix;
175 const char *mimetype; 175 const char *mimetype;
176 write_archive_fn_t write_func; 176 write_archive_fn_t write_func;
177 int bit; 177 int bit;
178}; 178};
179 179
180extern const char *cgit_version; 180extern const char *cgit_version;
181 181
182extern struct cgit_repolist cgit_repolist; 182extern struct cgit_repolist cgit_repolist;
183extern struct cgit_context ctx; 183extern struct cgit_context ctx;
184extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 184extern const struct cgit_snapshot_format cgit_snapshot_formats[];
185 185
186extern struct cgit_repo *cgit_add_repo(const char *url); 186extern struct cgit_repo *cgit_add_repo(const char *url);
187extern struct cgit_repo *cgit_get_repoinfo(const char *url); 187extern struct cgit_repo *cgit_get_repoinfo(const char *url);
188extern void cgit_repo_config_cb(const char *name, const char *value); 188extern void cgit_repo_config_cb(const char *name, const char *value);
189 189
190extern int chk_zero(int result, char *msg); 190extern int chk_zero(int result, char *msg);
191extern int chk_positive(int result, char *msg); 191extern int chk_positive(int result, char *msg);
192extern int chk_non_negative(int result, char *msg); 192extern int chk_non_negative(int result, char *msg);
193 193
194extern char *trim_end(const char *str, char c); 194extern char *trim_end(const char *str, char c);
195extern char *strlpart(char *txt, int maxlen); 195extern char *strlpart(char *txt, int maxlen);
196extern char *strrpart(char *txt, int maxlen); 196extern char *strrpart(char *txt, int maxlen);
197 197
198extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 198extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
199extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 199extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
200 int flags, void *cb_data); 200 int flags, void *cb_data);
201 201
202extern void *cgit_free_commitinfo(struct commitinfo *info); 202extern void *cgit_free_commitinfo(struct commitinfo *info);
203 203
204extern int cgit_diff_files(const unsigned char *old_sha1, 204extern int cgit_diff_files(const unsigned char *old_sha1,
205 const unsigned char *new_sha1, 205 const unsigned char *new_sha1,
206 linediff_fn fn); 206 linediff_fn fn);
207 207
208extern void cgit_diff_tree(const unsigned char *old_sha1, 208extern void cgit_diff_tree(const unsigned char *old_sha1,
209 const unsigned char *new_sha1, 209 const unsigned char *new_sha1,
210 filepair_fn fn, const char *prefix); 210 filepair_fn fn, const char *prefix);
211 211
212extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 212extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
213 213
214extern char *fmt(const char *format,...); 214extern char *fmt(const char *format,...);
215 215
216extern struct commitinfo *cgit_parse_commit(struct commit *commit); 216extern struct commitinfo *cgit_parse_commit(struct commit *commit);
217extern struct taginfo *cgit_parse_tag(struct tag *tag); 217extern struct taginfo *cgit_parse_tag(struct tag *tag);
218extern void cgit_parse_url(const char *url); 218extern void cgit_parse_url(const char *url);
219 219
220extern const char *cgit_repobasename(const char *reponame); 220extern const char *cgit_repobasename(const char *reponame);
221 221
222extern int cgit_parse_snapshots_mask(const char *str); 222extern int cgit_parse_snapshots_mask(const char *str);
223 223
224/* libgit.a either links against or compiles its own implementation of
225 * strcasestr(), and we'd like to reuse it. Simply re-declaring it
226 * seems to do the trick.
227 */
228extern char *strcasestr(const char *haystack, const char *needle);
229
230
224#endif /* CGIT_H */ 231#endif /* CGIT_H */
diff --git a/cgit.png b/cgit.png
index 22f7e95..d7f70bc 100644
--- a/cgit.png
+++ b/cgit.png
Binary files differ
diff --git a/ui-commit.c b/ui-commit.c
index 8019e36..dd36cc0 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,228 +1,236 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h"
12 13
13static int files, slots; 14static int files, slots;
14static int total_adds, total_rems, max_changes; 15static int total_adds, total_rems, max_changes;
15static int lines_added, lines_removed; 16static int lines_added, lines_removed;
16static char *curr_rev; 17static char *curr_rev;
17 18
18static struct fileinfo { 19static struct fileinfo {
19 char status; 20 char status;
20 unsigned char old_sha1[20]; 21 unsigned char old_sha1[20];
21 unsigned char new_sha1[20]; 22 unsigned char new_sha1[20];
22 unsigned short old_mode; 23 unsigned short old_mode;
23 unsigned short new_mode; 24 unsigned short new_mode;
24 char *old_path; 25 char *old_path;
25 char *new_path; 26 char *new_path;
26 unsigned int added; 27 unsigned int added;
27 unsigned int removed; 28 unsigned int removed;
28} *items; 29} *items;
29 30
30 31
31void print_fileinfo(struct fileinfo *info) 32void print_fileinfo(struct fileinfo *info)
32{ 33{
33 char *class; 34 char *class;
34 35
35 switch (info->status) { 36 switch (info->status) {
36 case DIFF_STATUS_ADDED: 37 case DIFF_STATUS_ADDED:
37 class = "add"; 38 class = "add";
38 break; 39 break;
39 case DIFF_STATUS_COPIED: 40 case DIFF_STATUS_COPIED:
40 class = "cpy"; 41 class = "cpy";
41 break; 42 break;
42 case DIFF_STATUS_DELETED: 43 case DIFF_STATUS_DELETED:
43 class = "del"; 44 class = "del";
44 break; 45 break;
45 case DIFF_STATUS_MODIFIED: 46 case DIFF_STATUS_MODIFIED:
46 class = "upd"; 47 class = "upd";
47 break; 48 break;
48 case DIFF_STATUS_RENAMED: 49 case DIFF_STATUS_RENAMED:
49 class = "mov"; 50 class = "mov";
50 break; 51 break;
51 case DIFF_STATUS_TYPE_CHANGED: 52 case DIFF_STATUS_TYPE_CHANGED:
52 class = "typ"; 53 class = "typ";
53 break; 54 break;
54 case DIFF_STATUS_UNKNOWN: 55 case DIFF_STATUS_UNKNOWN:
55 class = "unk"; 56 class = "unk";
56 break; 57 break;
57 case DIFF_STATUS_UNMERGED: 58 case DIFF_STATUS_UNMERGED:
58 class = "stg"; 59 class = "stg";
59 break; 60 break;
60 default: 61 default:
61 die("bug: unhandled diff status %c", info->status); 62 die("bug: unhandled diff status %c", info->status);
62 } 63 }
63 64
64 html("<tr>"); 65 html("<tr>");
65 htmlf("<td class='mode'>"); 66 htmlf("<td class='mode'>");
66 if (is_null_sha1(info->new_sha1)) { 67 if (is_null_sha1(info->new_sha1)) {
67 cgit_print_filemode(info->old_mode); 68 cgit_print_filemode(info->old_mode);
68 } else { 69 } else {
69 cgit_print_filemode(info->new_mode); 70 cgit_print_filemode(info->new_mode);
70 } 71 }
71 72
72 if (info->old_mode != info->new_mode && 73 if (info->old_mode != info->new_mode &&
73 !is_null_sha1(info->old_sha1) && 74 !is_null_sha1(info->old_sha1) &&
74 !is_null_sha1(info->new_sha1)) { 75 !is_null_sha1(info->new_sha1)) {
75 html("<span class='modechange'>["); 76 html("<span class='modechange'>[");
76 cgit_print_filemode(info->old_mode); 77 cgit_print_filemode(info->old_mode);
77 html("]</span>"); 78 html("]</span>");
78 } 79 }
79 htmlf("</td><td class='%s'>", class); 80 htmlf("</td><td class='%s'>", class);
80 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, curr_rev, 81 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, curr_rev,
81 NULL, info->new_path); 82 NULL, info->new_path);
82 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 83 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
83 htmlf(" (%s from %s)", 84 htmlf(" (%s from %s)",
84 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 85 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
85 info->old_path); 86 info->old_path);
86 html("</td><td class='right'>"); 87 html("</td><td class='right'>");
87 htmlf("%d", info->added + info->removed); 88 htmlf("%d", info->added + info->removed);
88 html("</td><td class='graph'>"); 89 html("</td><td class='graph'>");
89 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 90 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
90 htmlf("<td class='add' style='width: %.1f%%;'/>", 91 htmlf("<td class='add' style='width: %.1f%%;'/>",
91 info->added * 100.0 / max_changes); 92 info->added * 100.0 / max_changes);
92 htmlf("<td class='rem' style='width: %.1f%%;'/>", 93 htmlf("<td class='rem' style='width: %.1f%%;'/>",
93 info->removed * 100.0 / max_changes); 94 info->removed * 100.0 / max_changes);
94 htmlf("<td class='none' style='width: %.1f%%;'/>", 95 htmlf("<td class='none' style='width: %.1f%%;'/>",
95 (max_changes - info->removed - info->added) * 100.0 / max_changes); 96 (max_changes - info->removed - info->added) * 100.0 / max_changes);
96 html("</tr></table></td></tr>\n"); 97 html("</tr></table></td></tr>\n");
97} 98}
98 99
99void cgit_count_diff_lines(char *line, int len) 100void cgit_count_diff_lines(char *line, int len)
100{ 101{
101 if (line && (len > 0)) { 102 if (line && (len > 0)) {
102 if (line[0] == '+') 103 if (line[0] == '+')
103 lines_added++; 104 lines_added++;
104 else if (line[0] == '-') 105 else if (line[0] == '-')
105 lines_removed++; 106 lines_removed++;
106 } 107 }
107} 108}
108 109
109void inspect_filepair(struct diff_filepair *pair) 110void inspect_filepair(struct diff_filepair *pair)
110{ 111{
111 files++; 112 files++;
112 lines_added = 0; 113 lines_added = 0;
113 lines_removed = 0; 114 lines_removed = 0;
114 cgit_diff_files(pair->one->sha1, pair->two->sha1, cgit_count_diff_lines); 115 cgit_diff_files(pair->one->sha1, pair->two->sha1, cgit_count_diff_lines);
115 if (files >= slots) { 116 if (files >= slots) {
116 if (slots == 0) 117 if (slots == 0)
117 slots = 4; 118 slots = 4;
118 else 119 else
119 slots = slots * 2; 120 slots = slots * 2;
120 items = xrealloc(items, slots * sizeof(struct fileinfo)); 121 items = xrealloc(items, slots * sizeof(struct fileinfo));
121 } 122 }
122 items[files-1].status = pair->status; 123 items[files-1].status = pair->status;
123 hashcpy(items[files-1].old_sha1, pair->one->sha1); 124 hashcpy(items[files-1].old_sha1, pair->one->sha1);
124 hashcpy(items[files-1].new_sha1, pair->two->sha1); 125 hashcpy(items[files-1].new_sha1, pair->two->sha1);
125 items[files-1].old_mode = pair->one->mode; 126 items[files-1].old_mode = pair->one->mode;
126 items[files-1].new_mode = pair->two->mode; 127 items[files-1].new_mode = pair->two->mode;
127 items[files-1].old_path = xstrdup(pair->one->path); 128 items[files-1].old_path = xstrdup(pair->one->path);
128 items[files-1].new_path = xstrdup(pair->two->path); 129 items[files-1].new_path = xstrdup(pair->two->path);
129 items[files-1].added = lines_added; 130 items[files-1].added = lines_added;
130 items[files-1].removed = lines_removed; 131 items[files-1].removed = lines_removed;
131 if (lines_added + lines_removed > max_changes) 132 if (lines_added + lines_removed > max_changes)
132 max_changes = lines_added + lines_removed; 133 max_changes = lines_added + lines_removed;
133 total_adds += lines_added; 134 total_adds += lines_added;
134 total_rems += lines_removed; 135 total_rems += lines_removed;
135} 136}
136 137
137 138
138void cgit_print_commit(char *hex) 139void cgit_print_commit(char *hex)
139{ 140{
140 struct commit *commit, *parent; 141 struct commit *commit, *parent;
141 struct commitinfo *info; 142 struct commitinfo *info;
142 struct commit_list *p; 143 struct commit_list *p;
143 unsigned char sha1[20]; 144 unsigned char sha1[20];
144 char *tmp; 145 char *tmp;
145 int i; 146 int i;
146 147
147 if (!hex) 148 if (!hex)
148 hex = ctx.qry.head; 149 hex = ctx.qry.head;
149 curr_rev = hex; 150 curr_rev = hex;
150 151
151 if (get_sha1(hex, sha1)) { 152 if (get_sha1(hex, sha1)) {
152 cgit_print_error(fmt("Bad object id: %s", hex)); 153 cgit_print_error(fmt("Bad object id: %s", hex));
153 return; 154 return;
154 } 155 }
155 commit = lookup_commit_reference(sha1); 156 commit = lookup_commit_reference(sha1);
156 if (!commit) { 157 if (!commit) {
157 cgit_print_error(fmt("Bad commit reference: %s", hex)); 158 cgit_print_error(fmt("Bad commit reference: %s", hex));
158 return; 159 return;
159 } 160 }
160 info = cgit_parse_commit(commit); 161 info = cgit_parse_commit(commit);
161 162
162 html("<table summary='commit info' class='commit-info'>\n"); 163 html("<table summary='commit info' class='commit-info'>\n");
163 html("<tr><th>author</th><td>"); 164 html("<tr><th>author</th><td>");
164 html_txt(info->author); 165 html_txt(info->author);
165 html(" "); 166 html(" ");
166 html_txt(info->author_email); 167 html_txt(info->author_email);
167 html("</td><td class='right'>"); 168 html("</td><td class='right'>");
168 cgit_print_date(info->author_date, FMT_LONGDATE); 169 cgit_print_date(info->author_date, FMT_LONGDATE);
169 html("</td></tr>\n"); 170 html("</td></tr>\n");
170 html("<tr><th>committer</th><td>"); 171 html("<tr><th>committer</th><td>");
171 html_txt(info->committer); 172 html_txt(info->committer);
172 html(" "); 173 html(" ");
173 html_txt(info->committer_email); 174 html_txt(info->committer_email);
174 html("</td><td class='right'>"); 175 html("</td><td class='right'>");
175 cgit_print_date(info->committer_date, FMT_LONGDATE); 176 cgit_print_date(info->committer_date, FMT_LONGDATE);
176 html("</td></tr>\n"); 177 html("</td></tr>\n");
178 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
179 tmp = sha1_to_hex(commit->object.sha1);
180 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp);
181 html(" (");
182 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
183 html(")</td></tr>\n");
177 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 184 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
178 tmp = xstrdup(hex); 185 tmp = xstrdup(hex);
179 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 186 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
180 ctx.qry.head, tmp, NULL); 187 ctx.qry.head, tmp, NULL);
181 html("</td></tr>\n"); 188 html("</td></tr>\n");
182 for (p = commit->parents; p ; p = p->next) { 189 for (p = commit->parents; p ; p = p->next) {
183 parent = lookup_commit_reference(p->item->object.sha1); 190 parent = lookup_commit_reference(p->item->object.sha1);
184 if (!parent) { 191 if (!parent) {
185 html("<tr><td colspan='3'>"); 192 html("<tr><td colspan='3'>");
186 cgit_print_error("Error reading parent commit"); 193 cgit_print_error("Error reading parent commit");
187 html("</td></tr>"); 194 html("</td></tr>");
188 continue; 195 continue;
189 } 196 }
190 html("<tr><th>parent</th>" 197 html("<tr><th>parent</th>"
191 "<td colspan='2' class='sha1'>"); 198 "<td colspan='2' class='sha1'>");
192 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 199 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
193 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 200 ctx.qry.head, sha1_to_hex(p->item->object.sha1));
194 html(" ("); 201 html(" (");
195 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 202 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
196 sha1_to_hex(p->item->object.sha1), NULL); 203 sha1_to_hex(p->item->object.sha1), NULL);
197 html(")</td></tr>"); 204 html(")</td></tr>");
198 } 205 }
199 if (ctx.repo->snapshots) { 206 if (ctx.repo->snapshots) {
200 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 207 html("<tr><th>download</th><td colspan='2' class='sha1'>");
201 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 208 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
202 hex, ctx.repo->snapshots); 209 hex, ctx.repo->snapshots);
203 html("</td></tr>"); 210 html("</td></tr>");
204 } 211 }
205 html("</table>\n"); 212 html("</table>\n");
206 html("<div class='commit-subject'>"); 213 html("<div class='commit-subject'>");
207 html_txt(info->subject); 214 html_txt(info->subject);
208 html("</div>"); 215 html("</div>");
209 html("<div class='commit-msg'>"); 216 html("<div class='commit-msg'>");
210 html_txt(info->msg); 217 html_txt(info->msg);
211 html("</div>"); 218 html("</div>");
212 if (!(commit->parents && commit->parents->next && commit->parents->next->next)) { 219 if (!(commit->parents && commit->parents->next && commit->parents->next->next)) {
213 html("<div class='diffstat-header'>Diffstat</div>"); 220 html("<div class='diffstat-header'>Diffstat</div>");
214 html("<table summary='diffstat' class='diffstat'>"); 221 html("<table summary='diffstat' class='diffstat'>");
215 max_changes = 0; 222 max_changes = 0;
216 cgit_diff_commit(commit, inspect_filepair); 223 cgit_diff_commit(commit, inspect_filepair);
217 for(i = 0; i<files; i++) 224 for(i = 0; i<files; i++)
218 print_fileinfo(&items[i]); 225 print_fileinfo(&items[i]);
219 html("</table>"); 226 html("</table>");
220 html("<div class='diffstat-summary'>"); 227 html("<div class='diffstat-summary'>");
221 htmlf("%d files changed, %d insertions, %d deletions (", 228 htmlf("%d files changed, %d insertions, %d deletions",
222 files, total_adds, total_rems); 229 files, total_adds, total_rems);
223 cgit_diff_link("show diff", NULL, NULL, ctx.qry.head, hex, 230 cgit_print_diff(ctx.qry.sha1,
224 NULL, NULL); 231 sha1_to_hex(commit->parents->item->object.sha1),
232 NULL);
225 html(")</div>"); 233 html(")</div>");
226 } 234 }
227 cgit_free_commitinfo(info); 235 cgit_free_commitinfo(info);
228} 236}
diff --git a/ui-repolist.c b/ui-repolist.c
index eeeaf3d..7a7e95a 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,114 +1,139 @@
1/* ui-repolist.c: functions for generating the repolist page 1/* ui-repolist.c: functions for generating the repolist page
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <time.h> 9#include <time.h>
10 10
11#include "cgit.h" 11#include "cgit.h"
12#include "html.h" 12#include "html.h"
13#include "ui-shared.h" 13#include "ui-shared.h"
14 14
15time_t read_agefile(char *path) 15time_t read_agefile(char *path)
16{ 16{
17 FILE *f; 17 FILE *f;
18 static char buf[64], buf2[64]; 18 static char buf[64], buf2[64];
19 19
20 if (!(f = fopen(path, "r"))) 20 if (!(f = fopen(path, "r")))
21 return -1; 21 return -1;
22 fgets(buf, sizeof(buf), f); 22 fgets(buf, sizeof(buf), f);
23 fclose(f); 23 fclose(f);
24 if (parse_date(buf, buf2, sizeof(buf2))) 24 if (parse_date(buf, buf2, sizeof(buf2)))
25 return strtoul(buf2, NULL, 10); 25 return strtoul(buf2, NULL, 10);
26 else 26 else
27 return 0; 27 return 0;
28} 28}
29 29
30static void print_modtime(struct cgit_repo *repo) 30static void print_modtime(struct cgit_repo *repo)
31{ 31{
32 char *path; 32 char *path;
33 struct stat s; 33 struct stat s;
34 34
35 path = fmt("%s/%s", repo->path, ctx.cfg.agefile); 35 path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
36 if (stat(path, &s) == 0) { 36 if (stat(path, &s) == 0) {
37 cgit_print_age(read_agefile(path), -1, NULL); 37 cgit_print_age(read_agefile(path), -1, NULL);
38 return; 38 return;
39 } 39 }
40 40
41 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); 41 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
42 if (stat(path, &s) != 0) 42 if (stat(path, &s) != 0)
43 return; 43 return;
44 cgit_print_age(s.st_mtime, -1, NULL); 44 cgit_print_age(s.st_mtime, -1, NULL);
45} 45}
46 46
47void cgit_print_repolist() 47int is_match(struct cgit_repo *repo)
48{ 48{
49 int i, columns = 4; 49 if (!ctx.qry.search)
50 char *last_group = NULL; 50 return 1;
51 51 if (repo->url && strcasestr(repo->url, ctx.qry.search))
52 if (ctx.cfg.enable_index_links) 52 return 1;
53 columns++; 53 if (repo->name && strcasestr(repo->name, ctx.qry.search))
54 54 return 1;
55 ctx.page.title = ctx.cfg.root_title; 55 if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
56 cgit_print_http_headers(&ctx); 56 return 1;
57 cgit_print_docstart(&ctx); 57 if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
58 cgit_print_pageheader(&ctx); 58 return 1;
59 return 0;
60}
59 61
60 html("<table summary='repository list' class='list nowrap'>"); 62void print_header(int columns)
63{
61 if (ctx.cfg.index_header) { 64 if (ctx.cfg.index_header) {
62 htmlf("<tr class='nohover'><td colspan='%d' class='include-block'>", 65 htmlf("<tr class='nohover'><td colspan='%d' class='include-block'>",
63 columns); 66 columns);
64 html_include(ctx.cfg.index_header); 67 html_include(ctx.cfg.index_header);
65 html("</td></tr>"); 68 html("</td></tr>");
66 } 69 }
67 html("<tr class='nohover'>" 70 html("<tr class='nohover'>"
68 "<th class='left'>Name</th>" 71 "<th class='left'>Name</th>"
69 "<th class='left'>Description</th>" 72 "<th class='left'>Description</th>"
70 "<th class='left'>Owner</th>" 73 "<th class='left'>Owner</th>"
71 "<th class='left'>Idle</th>"); 74 "<th class='left'>Idle</th>");
72 if (ctx.cfg.enable_index_links) 75 if (ctx.cfg.enable_index_links)
73 html("<th>Links</th>"); 76 html("<th class='left'>Links</th>");
74 html("</tr>\n"); 77 html("</tr>\n");
78}
75 79
80void cgit_print_repolist()
81{
82 int i, columns = 4, hits = 0, header = 0;
83 char *last_group = NULL;
84
85 if (ctx.cfg.enable_index_links)
86 columns++;
87
88 ctx.page.title = ctx.cfg.root_title;
89 cgit_print_http_headers(&ctx);
90 cgit_print_docstart(&ctx);
91 cgit_print_pageheader(&ctx);
92
93 html("<table summary='repository list' class='list nowrap'>");
76 for (i=0; i<cgit_repolist.count; i++) { 94 for (i=0; i<cgit_repolist.count; i++) {
77 ctx.repo = &cgit_repolist.repos[i]; 95 ctx.repo = &cgit_repolist.repos[i];
96 if (!is_match(ctx.repo))
97 continue;
98 if (!header++)
99 print_header(columns);
100 hits++;
78 if ((last_group == NULL && ctx.repo->group != NULL) || 101 if ((last_group == NULL && ctx.repo->group != NULL) ||
79 (last_group != NULL && ctx.repo->group == NULL) || 102 (last_group != NULL && ctx.repo->group == NULL) ||
80 (last_group != NULL && ctx.repo->group != NULL && 103 (last_group != NULL && ctx.repo->group != NULL &&
81 strcmp(ctx.repo->group, last_group))) { 104 strcmp(ctx.repo->group, last_group))) {
82 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 105 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>",
83 columns); 106 columns);
84 html_txt(ctx.repo->group); 107 html_txt(ctx.repo->group);
85 html("</td></tr>"); 108 html("</td></tr>");
86 last_group = ctx.repo->group; 109 last_group = ctx.repo->group;
87 } 110 }
88 htmlf("<tr><td class='%s'>", 111 htmlf("<tr><td class='%s'>",
89 ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 112 ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
90 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); 113 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
91 html_txt(ctx.repo->name); 114 html_txt(ctx.repo->name);
92 html_link_close(); 115 html_link_close();
93 html("</td><td>"); 116 html("</td><td>");
94 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); 117 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc);
95 html("</td><td>"); 118 html("</td><td>");
96 html_txt(ctx.repo->owner); 119 html_txt(ctx.repo->owner);
97 html("</td><td>"); 120 html("</td><td>");
98 print_modtime(ctx.repo); 121 print_modtime(ctx.repo);
99 html("</td>"); 122 html("</td>");
100 if (ctx.cfg.enable_index_links) { 123 if (ctx.cfg.enable_index_links) {
101 html("<td>"); 124 html("<td>");
102 html_link_open(cgit_repourl(ctx.repo->url), 125 html_link_open(cgit_repourl(ctx.repo->url),
103 NULL, "button"); 126 NULL, "button");
104 html("summary</a>"); 127 html("summary</a>");
105 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 128 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
106 0, NULL, NULL); 129 0, NULL, NULL);
107 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 130 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
108 html("</td>"); 131 html("</td>");
109 } 132 }
110 html("</tr>\n"); 133 html("</tr>\n");
111 } 134 }
112 html("</table>"); 135 html("</table>");
136 if (!hits)
137 cgit_print_error("No repositories found");
113 cgit_print_docend(); 138 cgit_print_docend();
114} 139}
diff --git a/ui-shared.c b/ui-shared.c
index aa65988..bb08c4a 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,591 +1,609 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cmd.h"
10#include "html.h" 11#include "html.h"
11 12
12const char cgit_doctype[] = 13const char cgit_doctype[] =
13"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
14" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
15 16
16static char *http_date(time_t t) 17static char *http_date(time_t t)
17{ 18{
18 static char day[][4] = 19 static char day[][4] =
19 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
20 static char month[][4] = 21 static char month[][4] =
21 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
22 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
23 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
24 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
25 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
26 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
27} 28}
28 29
29void cgit_print_error(char *msg) 30void cgit_print_error(char *msg)
30{ 31{
31 html("<div class='error'>"); 32 html("<div class='error'>");
32 html_txt(msg); 33 html_txt(msg);
33 html("</div>\n"); 34 html("</div>\n");
34} 35}
35 36
36char *cgit_rooturl() 37char *cgit_rooturl()
37{ 38{
38 if (ctx.cfg.virtual_root) 39 if (ctx.cfg.virtual_root)
39 return fmt("%s/", ctx.cfg.virtual_root); 40 return fmt("%s/", ctx.cfg.virtual_root);
40 else 41 else
41 return ctx.cfg.script_name; 42 return ctx.cfg.script_name;
42} 43}
43 44
44char *cgit_repourl(const char *reponame) 45char *cgit_repourl(const char *reponame)
45{ 46{
46 if (ctx.cfg.virtual_root) { 47 if (ctx.cfg.virtual_root) {
47 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 48 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
48 } else { 49 } else {
49 return fmt("?r=%s", reponame); 50 return fmt("?r=%s", reponame);
50 } 51 }
51} 52}
52 53
53char *cgit_fileurl(const char *reponame, const char *pagename, 54char *cgit_fileurl(const char *reponame, const char *pagename,
54 const char *filename, const char *query) 55 const char *filename, const char *query)
55{ 56{
56 char *tmp; 57 char *tmp;
57 char *delim; 58 char *delim;
58 59
59 if (ctx.cfg.virtual_root) { 60 if (ctx.cfg.virtual_root) {
60 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 61 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
61 pagename, (filename ? filename:"")); 62 pagename, (filename ? filename:""));
62 delim = "?"; 63 delim = "?";
63 } else { 64 } else {
64 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 65 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
65 (filename ? filename : "")); 66 (filename ? filename : ""));
66 delim = "&"; 67 delim = "&";
67 } 68 }
68 if (query) 69 if (query)
69 tmp = fmt("%s%s%s", tmp, delim, query); 70 tmp = fmt("%s%s%s", tmp, delim, query);
70 return tmp; 71 return tmp;
71} 72}
72 73
73char *cgit_pageurl(const char *reponame, const char *pagename, 74char *cgit_pageurl(const char *reponame, const char *pagename,
74 const char *query) 75 const char *query)
75{ 76{
76 return cgit_fileurl(reponame,pagename,0,query); 77 return cgit_fileurl(reponame,pagename,0,query);
77} 78}
78 79
79const char *cgit_repobasename(const char *reponame) 80const char *cgit_repobasename(const char *reponame)
80{ 81{
81 /* I assume we don't need to store more than one repo basename */ 82 /* I assume we don't need to store more than one repo basename */
82 static char rvbuf[1024]; 83 static char rvbuf[1024];
83 int p; 84 int p;
84 const char *rv; 85 const char *rv;
85 strncpy(rvbuf,reponame,sizeof(rvbuf)); 86 strncpy(rvbuf,reponame,sizeof(rvbuf));
86 if(rvbuf[sizeof(rvbuf)-1]) 87 if(rvbuf[sizeof(rvbuf)-1])
87 die("cgit_repobasename: truncated repository name '%s'", reponame); 88 die("cgit_repobasename: truncated repository name '%s'", reponame);
88 p = strlen(rvbuf)-1; 89 p = strlen(rvbuf)-1;
89 /* strip trailing slashes */ 90 /* strip trailing slashes */
90 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 91 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
91 /* strip trailing .git */ 92 /* strip trailing .git */
92 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 93 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
93 p -= 3; rvbuf[p--] = 0; 94 p -= 3; rvbuf[p--] = 0;
94 } 95 }
95 /* strip more trailing slashes if any */ 96 /* strip more trailing slashes if any */
96 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 97 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
97 /* find last slash in the remaining string */ 98 /* find last slash in the remaining string */
98 rv = strrchr(rvbuf,'/'); 99 rv = strrchr(rvbuf,'/');
99 if(rv) 100 if(rv)
100 return ++rv; 101 return ++rv;
101 return rvbuf; 102 return rvbuf;
102} 103}
103 104
104char *cgit_currurl() 105char *cgit_currurl()
105{ 106{
106 if (!ctx.cfg.virtual_root) 107 if (!ctx.cfg.virtual_root)
107 return ctx.cfg.script_name; 108 return ctx.cfg.script_name;
108 else if (ctx.qry.page) 109 else if (ctx.qry.page)
109 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 110 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
110 else if (ctx.qry.repo) 111 else if (ctx.qry.repo)
111 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 112 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
112 else 113 else
113 return fmt("%s/", ctx.cfg.virtual_root); 114 return fmt("%s/", ctx.cfg.virtual_root);
114} 115}
115 116
116static char *repolink(char *title, char *class, char *page, char *head, 117static char *repolink(char *title, char *class, char *page, char *head,
117 char *path) 118 char *path)
118{ 119{
119 char *delim = "?"; 120 char *delim = "?";
120 121
121 html("<a"); 122 html("<a");
122 if (title) { 123 if (title) {
123 html(" title='"); 124 html(" title='");
124 html_attr(title); 125 html_attr(title);
125 html("'"); 126 html("'");
126 } 127 }
127 if (class) { 128 if (class) {
128 html(" class='"); 129 html(" class='");
129 html_attr(class); 130 html_attr(class);
130 html("'"); 131 html("'");
131 } 132 }
132 html(" href='"); 133 html(" href='");
133 if (ctx.cfg.virtual_root) { 134 if (ctx.cfg.virtual_root) {
134 html_attr(ctx.cfg.virtual_root); 135 html_attr(ctx.cfg.virtual_root);
135 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 136 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
136 html("/"); 137 html("/");
137 html_attr(ctx.repo->url); 138 html_attr(ctx.repo->url);
138 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 139 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
139 html("/"); 140 html("/");
140 if (page) { 141 if (page) {
141 html(page); 142 html(page);
142 html("/"); 143 html("/");
143 if (path) 144 if (path)
144 html_attr(path); 145 html_attr(path);
145 } 146 }
146 } else { 147 } else {
147 html(ctx.cfg.script_name); 148 html(ctx.cfg.script_name);
148 html("?url="); 149 html("?url=");
149 html_attr(ctx.repo->url); 150 html_attr(ctx.repo->url);
150 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 151 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
151 html("/"); 152 html("/");
152 if (page) { 153 if (page) {
153 html(page); 154 html(page);
154 html("/"); 155 html("/");
155 if (path) 156 if (path)
156 html_attr(path); 157 html_attr(path);
157 } 158 }
158 delim = "&amp;"; 159 delim = "&amp;";
159 } 160 }
160 if (head && strcmp(head, ctx.repo->defbranch)) { 161 if (head && strcmp(head, ctx.repo->defbranch)) {
161 html(delim); 162 html(delim);
162 html("h="); 163 html("h=");
163 html_attr(head); 164 html_attr(head);
164 delim = "&amp;"; 165 delim = "&amp;";
165 } 166 }
166 return fmt("%s", delim); 167 return fmt("%s", delim);
167} 168}
168 169
169static void reporevlink(char *page, char *name, char *title, char *class, 170static void reporevlink(char *page, char *name, char *title, char *class,
170 char *head, char *rev, char *path) 171 char *head, char *rev, char *path)
171{ 172{
172 char *delim; 173 char *delim;
173 174
174 delim = repolink(title, class, page, head, path); 175 delim = repolink(title, class, page, head, path);
175 if (rev && strcmp(rev, ctx.qry.head)) { 176 if (rev && strcmp(rev, ctx.qry.head)) {
176 html(delim); 177 html(delim);
177 html("id="); 178 html("id=");
178 html_attr(rev); 179 html_attr(rev);
179 } 180 }
180 html("'>"); 181 html("'>");
181 html_txt(name); 182 html_txt(name);
182 html("</a>"); 183 html("</a>");
183} 184}
184 185
185void cgit_tree_link(char *name, char *title, char *class, char *head, 186void cgit_tree_link(char *name, char *title, char *class, char *head,
186 char *rev, char *path) 187 char *rev, char *path)
187{ 188{
188 reporevlink("tree", name, title, class, head, rev, path); 189 reporevlink("tree", name, title, class, head, rev, path);
189} 190}
190 191
191void cgit_log_link(char *name, char *title, char *class, char *head, 192void cgit_log_link(char *name, char *title, char *class, char *head,
192 char *rev, char *path, int ofs, char *grep, char *pattern) 193 char *rev, char *path, int ofs, char *grep, char *pattern)
193{ 194{
194 char *delim; 195 char *delim;
195 196
196 delim = repolink(title, class, "log", head, path); 197 delim = repolink(title, class, "log", head, path);
197 if (rev && strcmp(rev, ctx.qry.head)) { 198 if (rev && strcmp(rev, ctx.qry.head)) {
198 html(delim); 199 html(delim);
199 html("id="); 200 html("id=");
200 html_attr(rev); 201 html_attr(rev);
201 delim = "&"; 202 delim = "&";
202 } 203 }
203 if (grep && pattern) { 204 if (grep && pattern) {
204 html(delim); 205 html(delim);
205 html("qt="); 206 html("qt=");
206 html_attr(grep); 207 html_attr(grep);
207 delim = "&"; 208 delim = "&";
208 html(delim); 209 html(delim);
209 html("q="); 210 html("q=");
210 html_attr(pattern); 211 html_attr(pattern);
211 } 212 }
212 if (ofs > 0) { 213 if (ofs > 0) {
213 html(delim); 214 html(delim);
214 html("ofs="); 215 html("ofs=");
215 htmlf("%d", ofs); 216 htmlf("%d", ofs);
216 } 217 }
217 html("'>"); 218 html("'>");
218 html_txt(name); 219 html_txt(name);
219 html("</a>"); 220 html("</a>");
220} 221}
221 222
222void cgit_commit_link(char *name, char *title, char *class, char *head, 223void cgit_commit_link(char *name, char *title, char *class, char *head,
223 char *rev) 224 char *rev)
224{ 225{
225 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 226 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
226 name[ctx.cfg.max_msg_len] = '\0'; 227 name[ctx.cfg.max_msg_len] = '\0';
227 name[ctx.cfg.max_msg_len - 1] = '.'; 228 name[ctx.cfg.max_msg_len - 1] = '.';
228 name[ctx.cfg.max_msg_len - 2] = '.'; 229 name[ctx.cfg.max_msg_len - 2] = '.';
229 name[ctx.cfg.max_msg_len - 3] = '.'; 230 name[ctx.cfg.max_msg_len - 3] = '.';
230 } 231 }
231 reporevlink("commit", name, title, class, head, rev, NULL); 232 reporevlink("commit", name, title, class, head, rev, NULL);
232} 233}
233 234
234void cgit_refs_link(char *name, char *title, char *class, char *head, 235void cgit_refs_link(char *name, char *title, char *class, char *head,
235 char *rev, char *path) 236 char *rev, char *path)
236{ 237{
237 reporevlink("refs", name, title, class, head, rev, path); 238 reporevlink("refs", name, title, class, head, rev, path);
238} 239}
239 240
240void cgit_snapshot_link(char *name, char *title, char *class, char *head, 241void cgit_snapshot_link(char *name, char *title, char *class, char *head,
241 char *rev, char *archivename) 242 char *rev, char *archivename)
242{ 243{
243 reporevlink("snapshot", name, title, class, head, rev, archivename); 244 reporevlink("snapshot", name, title, class, head, rev, archivename);
244} 245}
245 246
246void cgit_diff_link(char *name, char *title, char *class, char *head, 247void cgit_diff_link(char *name, char *title, char *class, char *head,
247 char *new_rev, char *old_rev, char *path) 248 char *new_rev, char *old_rev, char *path)
248{ 249{
249 char *delim; 250 char *delim;
250 251
251 delim = repolink(title, class, "diff", head, path); 252 delim = repolink(title, class, "diff", head, path);
252 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 253 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
253 html(delim); 254 html(delim);
254 html("id="); 255 html("id=");
255 html_attr(new_rev); 256 html_attr(new_rev);
256 delim = "&amp;"; 257 delim = "&amp;";
257 } 258 }
258 if (old_rev) { 259 if (old_rev) {
259 html(delim); 260 html(delim);
260 html("id2="); 261 html("id2=");
261 html_attr(old_rev); 262 html_attr(old_rev);
262 } 263 }
263 html("'>"); 264 html("'>");
264 html_txt(name); 265 html_txt(name);
265 html("</a>"); 266 html("</a>");
266} 267}
267 268
268void cgit_patch_link(char *name, char *title, char *class, char *head, 269void cgit_patch_link(char *name, char *title, char *class, char *head,
269 char *rev) 270 char *rev)
270{ 271{
271 reporevlink("patch", name, title, class, head, rev, NULL); 272 reporevlink("patch", name, title, class, head, rev, NULL);
272} 273}
273 274
274void cgit_object_link(struct object *obj) 275void cgit_object_link(struct object *obj)
275{ 276{
276 char *page, *arg, *url; 277 char *page, *arg, *url;
277 278
278 if (obj->type == OBJ_COMMIT) { 279 if (obj->type == OBJ_COMMIT) {
279 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, 280 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
280 ctx.qry.head, sha1_to_hex(obj->sha1)); 281 ctx.qry.head, sha1_to_hex(obj->sha1));
281 return; 282 return;
282 } else if (obj->type == OBJ_TREE) { 283 } else if (obj->type == OBJ_TREE) {
283 page = "tree"; 284 page = "tree";
284 arg = "id"; 285 arg = "id";
285 } else if (obj->type == OBJ_TAG) { 286 } else if (obj->type == OBJ_TAG) {
286 page = "tag"; 287 page = "tag";
287 arg = "id"; 288 arg = "id";
288 } else { 289 } else {
289 page = "blob"; 290 page = "blob";
290 arg = "id"; 291 arg = "id";
291 } 292 }
292 293
293 url = cgit_pageurl(ctx.qry.repo, page, 294 url = cgit_pageurl(ctx.qry.repo, page,
294 fmt("%s=%s", arg, sha1_to_hex(obj->sha1))); 295 fmt("%s=%s", arg, sha1_to_hex(obj->sha1)));
295 html_link_open(url, NULL, NULL); 296 html_link_open(url, NULL, NULL);
296 htmlf("%s %s", typename(obj->type), 297 htmlf("%s %s", typename(obj->type),
297 sha1_to_hex(obj->sha1)); 298 sha1_to_hex(obj->sha1));
298 html_link_close(); 299 html_link_close();
299} 300}
300 301
301void cgit_print_date(time_t secs, char *format) 302void cgit_print_date(time_t secs, char *format)
302{ 303{
303 char buf[64]; 304 char buf[64];
304 struct tm *time; 305 struct tm *time;
305 306
306 if (!secs) 307 if (!secs)
307 return; 308 return;
308 time = gmtime(&secs); 309 time = gmtime(&secs);
309 strftime(buf, sizeof(buf)-1, format, time); 310 strftime(buf, sizeof(buf)-1, format, time);
310 html_txt(buf); 311 html_txt(buf);
311} 312}
312 313
313void cgit_print_age(time_t t, time_t max_relative, char *format) 314void cgit_print_age(time_t t, time_t max_relative, char *format)
314{ 315{
315 time_t now, secs; 316 time_t now, secs;
316 317
317 if (!t) 318 if (!t)
318 return; 319 return;
319 time(&now); 320 time(&now);
320 secs = now - t; 321 secs = now - t;
321 322
322 if (secs > max_relative && max_relative >= 0) { 323 if (secs > max_relative && max_relative >= 0) {
323 cgit_print_date(t, format); 324 cgit_print_date(t, format);
324 return; 325 return;
325 } 326 }
326 327
327 if (secs < TM_HOUR * 2) { 328 if (secs < TM_HOUR * 2) {
328 htmlf("<span class='age-mins'>%.0f min.</span>", 329 htmlf("<span class='age-mins'>%.0f min.</span>",
329 secs * 1.0 / TM_MIN); 330 secs * 1.0 / TM_MIN);
330 return; 331 return;
331 } 332 }
332 if (secs < TM_DAY * 2) { 333 if (secs < TM_DAY * 2) {
333 htmlf("<span class='age-hours'>%.0f hours</span>", 334 htmlf("<span class='age-hours'>%.0f hours</span>",
334 secs * 1.0 / TM_HOUR); 335 secs * 1.0 / TM_HOUR);
335 return; 336 return;
336 } 337 }
337 if (secs < TM_WEEK * 2) { 338 if (secs < TM_WEEK * 2) {
338 htmlf("<span class='age-days'>%.0f days</span>", 339 htmlf("<span class='age-days'>%.0f days</span>",
339 secs * 1.0 / TM_DAY); 340 secs * 1.0 / TM_DAY);
340 return; 341 return;
341 } 342 }
342 if (secs < TM_MONTH * 2) { 343 if (secs < TM_MONTH * 2) {
343 htmlf("<span class='age-weeks'>%.0f weeks</span>", 344 htmlf("<span class='age-weeks'>%.0f weeks</span>",
344 secs * 1.0 / TM_WEEK); 345 secs * 1.0 / TM_WEEK);
345 return; 346 return;
346 } 347 }
347 if (secs < TM_YEAR * 2) { 348 if (secs < TM_YEAR * 2) {
348 htmlf("<span class='age-months'>%.0f months</span>", 349 htmlf("<span class='age-months'>%.0f months</span>",
349 secs * 1.0 / TM_MONTH); 350 secs * 1.0 / TM_MONTH);
350 return; 351 return;
351 } 352 }
352 htmlf("<span class='age-years'>%.0f years</span>", 353 htmlf("<span class='age-years'>%.0f years</span>",
353 secs * 1.0 / TM_YEAR); 354 secs * 1.0 / TM_YEAR);
354} 355}
355 356
356void cgit_print_http_headers(struct cgit_context *ctx) 357void cgit_print_http_headers(struct cgit_context *ctx)
357{ 358{
358 if (ctx->page.mimetype && ctx->page.charset) 359 if (ctx->page.mimetype && ctx->page.charset)
359 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 360 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
360 ctx->page.charset); 361 ctx->page.charset);
361 else if (ctx->page.mimetype) 362 else if (ctx->page.mimetype)
362 htmlf("Content-Type: %s\n", ctx->page.mimetype); 363 htmlf("Content-Type: %s\n", ctx->page.mimetype);
363 if (ctx->page.filename) 364 if (ctx->page.filename)
364 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 365 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
365 ctx->page.filename); 366 ctx->page.filename);
366 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 367 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
367 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 368 htmlf("Expires: %s\n", http_date(ctx->page.expires));
368 html("\n"); 369 html("\n");
369} 370}
370 371
371void cgit_print_docstart(struct cgit_context *ctx) 372void cgit_print_docstart(struct cgit_context *ctx)
372{ 373{
373 html(cgit_doctype); 374 html(cgit_doctype);
374 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 375 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
375 html("<head>\n"); 376 html("<head>\n");
376 html("<title>"); 377 html("<title>");
377 html_txt(ctx->page.title); 378 html_txt(ctx->page.title);
378 html("</title>\n"); 379 html("</title>\n");
379 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 380 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
380 if (ctx->cfg.robots && *ctx->cfg.robots) 381 if (ctx->cfg.robots && *ctx->cfg.robots)
381 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 382 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
382 html("<link rel='stylesheet' type='text/css' href='"); 383 html("<link rel='stylesheet' type='text/css' href='");
383 html_attr(ctx->cfg.css); 384 html_attr(ctx->cfg.css);
384 html("'/>\n"); 385 html("'/>\n");
385 html("</head>\n"); 386 html("</head>\n");
386 html("<body>\n"); 387 html("<body>\n");
387} 388}
388 389
389void cgit_print_docend() 390void cgit_print_docend()
390{ 391{
391 html("</td>\n</tr>\n</table>\n</body>\n</html>\n"); 392 html("</div>\n</body>\n</html>\n");
392} 393}
393 394
394int print_branch_option(const char *refname, const unsigned char *sha1, 395int print_branch_option(const char *refname, const unsigned char *sha1,
395 int flags, void *cb_data) 396 int flags, void *cb_data)
396{ 397{
397 char *name = (char *)refname; 398 char *name = (char *)refname;
398 html_option(name, name, ctx.qry.head); 399 html_option(name, name, ctx.qry.head);
399 return 0; 400 return 0;
400} 401}
401 402
402int print_archive_ref(const char *refname, const unsigned char *sha1, 403int print_archive_ref(const char *refname, const unsigned char *sha1,
403 int flags, void *cb_data) 404 int flags, void *cb_data)
404{ 405{
405 struct tag *tag; 406 struct tag *tag;
406 struct taginfo *info; 407 struct taginfo *info;
407 struct object *obj; 408 struct object *obj;
408 char buf[256], *url; 409 char buf[256], *url;
409 unsigned char fileid[20]; 410 unsigned char fileid[20];
410 int *header = (int *)cb_data; 411 int *header = (int *)cb_data;
411 412
412 if (prefixcmp(refname, "refs/archives")) 413 if (prefixcmp(refname, "refs/archives"))
413 return 0; 414 return 0;
414 strncpy(buf, refname+14, sizeof(buf)); 415 strncpy(buf, refname+14, sizeof(buf));
415 obj = parse_object(sha1); 416 obj = parse_object(sha1);
416 if (!obj) 417 if (!obj)
417 return 1; 418 return 1;
418 if (obj->type == OBJ_TAG) { 419 if (obj->type == OBJ_TAG) {
419 tag = lookup_tag(sha1); 420 tag = lookup_tag(sha1);
420 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 421 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
421 return 0; 422 return 0;
422 hashcpy(fileid, tag->tagged->sha1); 423 hashcpy(fileid, tag->tagged->sha1);
423 } else if (obj->type != OBJ_BLOB) { 424 } else if (obj->type != OBJ_BLOB) {
424 return 0; 425 return 0;
425 } else { 426 } else {
426 hashcpy(fileid, sha1); 427 hashcpy(fileid, sha1);
427 } 428 }
428 if (!*header) { 429 if (!*header) {
429 html("<h1>download</h1>\n"); 430 html("<h1>download</h1>\n");
430 *header = 1; 431 *header = 1;
431 } 432 }
432 url = cgit_pageurl(ctx.qry.repo, "blob", 433 url = cgit_pageurl(ctx.qry.repo, "blob",
433 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 434 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
434 buf)); 435 buf));
435 html_link_open(url, NULL, "menu"); 436 html_link_open(url, NULL, "menu");
436 html_txt(strlpart(buf, 20)); 437 html_txt(strlpart(buf, 20));
437 html_link_close(); 438 html_link_close();
438 return 0; 439 return 0;
439} 440}
440 441
441void add_hidden_formfields(int incl_head, int incl_search, char *page) 442void add_hidden_formfields(int incl_head, int incl_search, char *page)
442{ 443{
443 char *url; 444 char *url;
444 445
445 if (!ctx.cfg.virtual_root) { 446 if (!ctx.cfg.virtual_root) {
446 url = fmt("%s/%s", ctx.qry.repo, page); 447 url = fmt("%s/%s", ctx.qry.repo, page);
447 if (ctx.qry.path) 448 if (ctx.qry.path)
448 url = fmt("%s/%s", url, ctx.qry.path); 449 url = fmt("%s/%s", url, ctx.qry.path);
449 html_hidden("url", url); 450 html_hidden("url", url);
450 } 451 }
451 452
452 if (incl_head && strcmp(ctx.qry.head, ctx.repo->defbranch)) 453 if (incl_head && strcmp(ctx.qry.head, ctx.repo->defbranch))
453 html_hidden("h", ctx.qry.head); 454 html_hidden("h", ctx.qry.head);
454 455
455 if (ctx.qry.sha1) 456 if (ctx.qry.sha1)
456 html_hidden("id", ctx.qry.sha1); 457 html_hidden("id", ctx.qry.sha1);
457 if (ctx.qry.sha2) 458 if (ctx.qry.sha2)
458 html_hidden("id2", ctx.qry.sha2); 459 html_hidden("id2", ctx.qry.sha2);
459 460
460 if (incl_search) { 461 if (incl_search) {
461 if (ctx.qry.grep) 462 if (ctx.qry.grep)
462 html_hidden("qt", ctx.qry.grep); 463 html_hidden("qt", ctx.qry.grep);
463 if (ctx.qry.search) 464 if (ctx.qry.search)
464 html_hidden("q", ctx.qry.search); 465 html_hidden("q", ctx.qry.search);
465 } 466 }
466} 467}
467 468
469char *hc(struct cgit_cmd *cmd, const char *page)
470{
471 return (strcmp(cmd->name, page) ? NULL : "active");
472}
473
468void cgit_print_pageheader(struct cgit_context *ctx) 474void cgit_print_pageheader(struct cgit_context *ctx)
469{ 475{
470 static const char *default_info = "This is cgit, a fast webinterface for git repositories"; 476 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
471 int header = 0;
472 char *url;
473 477
474 html("<table id='layout' summary=''>\n"); 478 html("<table id='header'>\n");
475 html("<tr><td id='sidebar'>\n"); 479 html("<tr>\n");
476 html("<table class='sidebar' cellspacing='0' summary=''>\n"); 480 html("<td class='logo' rowspan='2'><a href='");
477 html("<tr><td class='sidebar'>\n<a href='"); 481 if (ctx->cfg.logo_link)
478 html_attr(cgit_rooturl()); 482 html_attr(ctx->cfg.logo_link);
479 htmlf("'><img src='%s' alt='cgit'/></a>\n", 483 else
480 ctx->cfg.logo); 484 html_attr(cgit_rooturl());
481 html("</td></tr>\n<tr><td class='sidebar'>\n"); 485 html("'><img src='");
482 if (ctx->repo) { 486 html_attr(ctx->cfg.logo);
483 html("<h1 class='first'>"); 487 html("'/></a></td>\n");
484 html_txt(strrpart(ctx->repo->name, 20));
485 html("</h1>\n");
486 html_txt(ctx->repo->desc);
487 if (ctx->repo->owner) {
488 html("<h1>owner</h1>\n");
489 html_txt(ctx->repo->owner);
490 }
491 html("<h1>navigate</h1>\n");
492 reporevlink(NULL, "summary", NULL, "menu", ctx->qry.head,
493 NULL, NULL);
494 cgit_log_link("log", NULL, "menu", ctx->qry.head, NULL, NULL,
495 0, NULL, NULL);
496 cgit_tree_link("tree", NULL, "menu", ctx->qry.head,
497 ctx->qry.sha1, NULL);
498 cgit_commit_link("commit", NULL, "menu", ctx->qry.head,
499 ctx->qry.sha1);
500 cgit_diff_link("diff", NULL, "menu", ctx->qry.head,
501 ctx->qry.sha1, ctx->qry.sha2, NULL);
502 cgit_patch_link("patch", NULL, "menu", ctx->qry.head,
503 ctx->qry.sha1);
504
505 for_each_ref(print_archive_ref, &header);
506
507 if (ctx->repo->clone_url || ctx->cfg.clone_prefix) {
508 html("<h1>clone</h1>\n");
509 if (ctx->repo->clone_url)
510 url = ctx->repo->clone_url;
511 else
512 url = fmt("%s%s", ctx->cfg.clone_prefix,
513 ctx->repo->url);
514 html("<a class='menu' href='");
515 html_attr(url);
516 html("' title='");
517 html_attr(url);
518 html("'>\n");
519 html_txt(strrpart(url, 20));
520 html("</a>\n");
521 }
522 488
523 html("<h1>branch</h1>\n"); 489 html("<td class='main'>");
490 if (ctx->repo) {
491/*
492 html("<a href='");
493 html_attr(cgit_rooturl());
494 html("'>index</a> : ");
495*/
496 reporevlink(NULL, ctx->repo->name, NULL, hc(cmd, "summary"),
497 ctx->qry.head, NULL, NULL);
498 html(" : ");
499 html_txt(ctx->qry.page);
500 html("</td><td class='form'>");
524 html("<form method='get' action=''>\n"); 501 html("<form method='get' action=''>\n");
525 add_hidden_formfields(0, 1, ctx->qry.page); 502 add_hidden_formfields(0, 1, ctx->qry.page);
526 // html("<table summary='branch selector' class='grid'><tr><td id='branch-dropdown-cell'>");
527 html("<select name='h' onchange='this.form.submit();'>\n"); 503 html("<select name='h' onchange='this.form.submit();'>\n");
528 for_each_branch_ref(print_branch_option, ctx->qry.head); 504 for_each_branch_ref(print_branch_option, ctx->qry.head);
529 html("</select>\n"); 505 html("</select> ");
530 // html("</td><td>"); 506 html("<input type='submit' name='' value='switch'/>");
531 html("<noscript><input type='submit' id='switch-btn' value='switch'/></noscript>\n"); 507 html("</form>");
532 // html("</td></tr></table>"); 508 } else
533 html("</form>\n"); 509 html_txt(ctx->cfg.root_title);
510 html("</td>\n");
511
512 html("<tr><td class='sub'");
513 if (ctx->repo) {
514 html(" colspan='2'>");
515 html_txt(ctx->repo->desc);
516 }
517/*
518 else if (ctx->cfg.root_subtitle)
519 html_txt(ctx->cfg.root_subtitle);
520*/
521 else {
522 html(">");
523 html_txt("a fast webinterface for the git dscm");
524 }
525 html("</td></tr>\n");
534 526
535 html("<h1>search</h1>\n"); 527 html("</tr>\n");
536 html("<form method='get' action='"); 528 html("</table>\n");
529
530 html("<table class='tabs'><tr><td>\n");
531 if (ctx->repo) {
532 reporevlink(NULL, "summary", NULL, hc(cmd, "summary"),
533 ctx->qry.head, NULL, NULL);
534 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
535 ctx->qry.sha1, NULL);
536 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
537 NULL, NULL, 0, NULL, NULL);
538 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
539 ctx->qry.sha1, NULL);
540 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
541 ctx->qry.head, ctx->qry.sha1);
542 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
543 ctx->qry.sha1, ctx->qry.sha2, NULL);
544 html("</td><td class='form'>");
545 html("<form class='right' method='get' action='");
537 if (ctx->cfg.virtual_root) 546 if (ctx->cfg.virtual_root)
538 html_attr(cgit_fileurl(ctx->qry.repo, "log", 547 html_attr(cgit_fileurl(ctx->qry.repo, "log",
539 ctx->qry.path, NULL)); 548 ctx->qry.path, NULL));
540 html("'>\n"); 549 html("'>\n");
541 add_hidden_formfields(1, 0, "log"); 550 add_hidden_formfields(1, 0, "log");
542 html("<select name='qt'>\n"); 551 html("<select name='qt'>\n");
543 html_option("grep", "log msg", ctx->qry.grep); 552 html_option("grep", "log msg", ctx->qry.grep);
544 html_option("author", "author", ctx->qry.grep); 553 html_option("author", "author", ctx->qry.grep);
545 html_option("committer", "committer", ctx->qry.grep); 554 html_option("committer", "committer", ctx->qry.grep);
546 html("</select>\n"); 555 html("</select>\n");
547 html("<input class='txt' type='text' name='q' value='"); 556 html("<input class='txt' type='text' size='10' name='q' value='");
548 html_attr(ctx->qry.search); 557 html_attr(ctx->qry.search);
549 html("'/>\n"); 558 html("'/>\n");
559 html("<input type='submit' value='search'/>\n");
550 html("</form>\n"); 560 html("</form>\n");
551 } else { 561 } else {
552 if (!ctx->cfg.index_info || html_include(ctx->cfg.index_info)) 562 html("<a class='active' href='");
553 html(default_info); 563 html_attr(cgit_rooturl());
564 html("'>index</a>\n");
565 html("</td><td class='form'>");
566 html("<form method='get' action='");
567 html_attr(cgit_rooturl());
568 html("'>\n");
569 html("<input type='text' name='q' size='10' value='");
570 html_attr(ctx->qry.search);
571 html("'/>\n");
572 html("<input type='submit' value='search'/>\n");
573 html("</form>");
554 } 574 }
555 575 html("</td></tr></table>\n");
556 html("</td></tr></table></td>\n"); 576 html("<div class='content'>");
557
558 html("<td id='content'>\n");
559} 577}
560 578
561void cgit_print_filemode(unsigned short mode) 579void cgit_print_filemode(unsigned short mode)
562{ 580{
563 if (S_ISDIR(mode)) 581 if (S_ISDIR(mode))
564 html("d"); 582 html("d");
565 else if (S_ISLNK(mode)) 583 else if (S_ISLNK(mode))
566 html("l"); 584 html("l");
567 else if (S_ISGITLINK(mode)) 585 else if (S_ISGITLINK(mode))
568 html("m"); 586 html("m");
569 else 587 else
570 html("-"); 588 html("-");
571 html_fileperm(mode >> 6); 589 html_fileperm(mode >> 6);
572 html_fileperm(mode >> 3); 590 html_fileperm(mode >> 3);
573 html_fileperm(mode); 591 html_fileperm(mode);
574} 592}
575 593
576void cgit_print_snapshot_links(const char *repo, const char *head, 594void cgit_print_snapshot_links(const char *repo, const char *head,
577 const char *hex, int snapshots) 595 const char *hex, int snapshots)
578{ 596{
579 const struct cgit_snapshot_format* f; 597 const struct cgit_snapshot_format* f;
580 char *filename; 598 char *filename;
581 599
582 for (f = cgit_snapshot_formats; f->suffix; f++) { 600 for (f = cgit_snapshot_formats; f->suffix; f++) {
583 if (!(snapshots & f->bit)) 601 if (!(snapshots & f->bit))
584 continue; 602 continue;
585 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 603 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
586 f->suffix); 604 f->suffix);
587 cgit_snapshot_link(filename, NULL, NULL, (char *)head, 605 cgit_snapshot_link(filename, NULL, NULL, (char *)head,
588 (char *)hex, filename); 606 (char *)hex, filename);
589 html("<br/>"); 607 html("<br/>");
590 } 608 }
591} 609}
diff --git a/ui-shared.h b/ui-shared.h
index 94de884..76c2b1f 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,36 +1,38 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_repourl(const char *reponame); 4extern char *cgit_repourl(const char *reponame);
5extern char *cgit_fileurl(const char *reponame, const char *pagename, 5extern char *cgit_fileurl(const char *reponame, const char *pagename,
6 const char *filename, const char *query); 6 const char *filename, const char *query);
7extern char *cgit_pageurl(const char *reponame, const char *pagename, 7extern char *cgit_pageurl(const char *reponame, const char *pagename,
8 const char *query); 8 const char *query);
9 9
10extern void cgit_tree_link(char *name, char *title, char *class, char *head, 10extern void cgit_tree_link(char *name, char *title, char *class, char *head,
11 char *rev, char *path); 11 char *rev, char *path);
12extern void cgit_log_link(char *name, char *title, char *class, char *head, 12extern void cgit_log_link(char *name, char *title, char *class, char *head,
13 char *rev, char *path, int ofs, char *grep, 13 char *rev, char *path, int ofs, char *grep,
14 char *pattern); 14 char *pattern);
15extern void cgit_commit_link(char *name, char *title, char *class, char *head, 15extern void cgit_commit_link(char *name, char *title, char *class, char *head,
16 char *rev); 16 char *rev);
17extern void cgit_patch_link(char *name, char *title, char *class, char *head,
18 char *rev);
17extern void cgit_refs_link(char *name, char *title, char *class, char *head, 19extern void cgit_refs_link(char *name, char *title, char *class, char *head,
18 char *rev, char *path); 20 char *rev, char *path);
19extern void cgit_snapshot_link(char *name, char *title, char *class, 21extern void cgit_snapshot_link(char *name, char *title, char *class,
20 char *head, char *rev, char *archivename); 22 char *head, char *rev, char *archivename);
21extern void cgit_diff_link(char *name, char *title, char *class, char *head, 23extern void cgit_diff_link(char *name, char *title, char *class, char *head,
22 char *new_rev, char *old_rev, char *path); 24 char *new_rev, char *old_rev, char *path);
23extern void cgit_object_link(struct object *obj); 25extern void cgit_object_link(struct object *obj);
24 26
25extern void cgit_print_error(char *msg); 27extern void cgit_print_error(char *msg);
26extern void cgit_print_date(time_t secs, char *format); 28extern void cgit_print_date(time_t secs, char *format);
27extern void cgit_print_age(time_t t, time_t max_relative, char *format); 29extern void cgit_print_age(time_t t, time_t max_relative, char *format);
28extern void cgit_print_http_headers(struct cgit_context *ctx); 30extern void cgit_print_http_headers(struct cgit_context *ctx);
29extern void cgit_print_docstart(struct cgit_context *ctx); 31extern void cgit_print_docstart(struct cgit_context *ctx);
30extern void cgit_print_docend(); 32extern void cgit_print_docend();
31extern void cgit_print_pageheader(struct cgit_context *ctx); 33extern void cgit_print_pageheader(struct cgit_context *ctx);
32extern void cgit_print_filemode(unsigned short mode); 34extern void cgit_print_filemode(unsigned short mode);
33extern void cgit_print_snapshot_links(const char *repo, const char *head, 35extern void cgit_print_snapshot_links(const char *repo, const char *head,
34 const char *hex, int snapshots); 36 const char *hex, int snapshots);
35 37
36#endif /* UI_SHARED_H */ 38#endif /* UI_SHARED_H */