summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c6
-rw-r--r--cgit.css10
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt2
-rw-r--r--cmd.c46
-rw-r--r--cmd.h3
-rw-r--r--shared.c1
-rw-r--r--ui-commit.c20
-rw-r--r--ui-commit.h2
-rw-r--r--ui-diff.c8
-rw-r--r--ui-log.c27
-rw-r--r--ui-patch.c6
-rw-r--r--ui-patch.h2
-rw-r--r--ui-refs.c2
-rw-r--r--ui-shared.c216
-rw-r--r--ui-shared.h72
-rw-r--r--ui-tree.c15
17 files changed, 275 insertions, 164 deletions
diff --git a/cgit.c b/cgit.c
index 38bc136..d4fcfa7 100644
--- a/cgit.c
+++ b/cgit.c
@@ -412,48 +412,54 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
412 cgit_print_pageheader(ctx); 412 cgit_print_pageheader(ctx);
413 cgit_print_error(fmt("Invalid branch: %s", tmp)); 413 cgit_print_error(fmt("Invalid branch: %s", tmp));
414 cgit_print_docend(); 414 cgit_print_docend();
415 return 1; 415 return 1;
416 } 416 }
417 return 0; 417 return 0;
418} 418}
419 419
420static void process_request(void *cbdata) 420static void process_request(void *cbdata)
421{ 421{
422 struct cgit_context *ctx = cbdata; 422 struct cgit_context *ctx = cbdata;
423 struct cgit_cmd *cmd; 423 struct cgit_cmd *cmd;
424 424
425 cmd = cgit_get_cmd(ctx); 425 cmd = cgit_get_cmd(ctx);
426 if (!cmd) { 426 if (!cmd) {
427 ctx->page.title = "cgit error"; 427 ctx->page.title = "cgit error";
428 cgit_print_http_headers(ctx); 428 cgit_print_http_headers(ctx);
429 cgit_print_docstart(ctx); 429 cgit_print_docstart(ctx);
430 cgit_print_pageheader(ctx); 430 cgit_print_pageheader(ctx);
431 cgit_print_error("Invalid request"); 431 cgit_print_error("Invalid request");
432 cgit_print_docend(); 432 cgit_print_docend();
433 return; 433 return;
434 } 434 }
435 435
436 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
437 * in-project path limit to be made available at ctx->qry.vpath.
438 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
439 */
440 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
441
436 if (cmd->want_repo && !ctx->repo) { 442 if (cmd->want_repo && !ctx->repo) {
437 cgit_print_http_headers(ctx); 443 cgit_print_http_headers(ctx);
438 cgit_print_docstart(ctx); 444 cgit_print_docstart(ctx);
439 cgit_print_pageheader(ctx); 445 cgit_print_pageheader(ctx);
440 cgit_print_error(fmt("No repository selected")); 446 cgit_print_error(fmt("No repository selected"));
441 cgit_print_docend(); 447 cgit_print_docend();
442 return; 448 return;
443 } 449 }
444 450
445 if (ctx->repo && prepare_repo_cmd(ctx)) 451 if (ctx->repo && prepare_repo_cmd(ctx))
446 return; 452 return;
447 453
448 if (cmd->want_layout) { 454 if (cmd->want_layout) {
449 cgit_print_http_headers(ctx); 455 cgit_print_http_headers(ctx);
450 cgit_print_docstart(ctx); 456 cgit_print_docstart(ctx);
451 cgit_print_pageheader(ctx); 457 cgit_print_pageheader(ctx);
452 } 458 }
453 459
454 cmd->fn(ctx); 460 cmd->fn(ctx);
455 461
456 if (cmd->want_layout) 462 if (cmd->want_layout)
457 cgit_print_docend(); 463 cgit_print_docend();
458} 464}
459 465
diff --git a/cgit.css b/cgit.css
index 6198403..6e47eb3 100644
--- a/cgit.css
+++ b/cgit.css
@@ -43,90 +43,96 @@ table#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 2px #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 {
106 margin: 0px;
107 padding: 5px 2em 2px 2em;
108 color: #000;
109 background-color: #eee;
110}
111
105div.content { 112div.content {
106 margin: 0px; 113 margin: 0px;
107 padding: 2em; 114 padding: 2em;
108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
110} 116}
111 117
112 118
113table.list { 119table.list {
114 width: 100%; 120 width: 100%;
115 border: none; 121 border: none;
116 border-collapse: collapse; 122 border-collapse: collapse;
117} 123}
118 124
119table.list tr { 125table.list tr {
120 background: white; 126 background: white;
121} 127}
122 128
123table.list tr.logheader { 129table.list tr.logheader {
124 background: #eee; 130 background: #eee;
125} 131}
126 132
127table.list tr:hover { 133table.list tr:hover {
128 background: #eee; 134 background: #eee;
129} 135}
130 136
131table.list tr.nohover:hover { 137table.list tr.nohover:hover {
132 background: white; 138 background: white;
diff --git a/cgit.h b/cgit.h
index 8884f9e..80c3902 100644
--- a/cgit.h
+++ b/cgit.h
@@ -126,48 +126,49 @@ struct reflist {
126}; 126};
127 127
128struct cgit_query { 128struct cgit_query {
129 int has_symref; 129 int has_symref;
130 int has_sha1; 130 int has_sha1;
131 char *raw; 131 char *raw;
132 char *repo; 132 char *repo;
133 char *page; 133 char *page;
134 char *search; 134 char *search;
135 char *grep; 135 char *grep;
136 char *head; 136 char *head;
137 char *sha1; 137 char *sha1;
138 char *sha2; 138 char *sha2;
139 char *path; 139 char *path;
140 char *name; 140 char *name;
141 char *mimetype; 141 char *mimetype;
142 char *url; 142 char *url;
143 char *period; 143 char *period;
144 int ofs; 144 int ofs;
145 int nohead; 145 int nohead;
146 char *sort; 146 char *sort;
147 int showmsg; 147 int showmsg;
148 int ssdiff; 148 int ssdiff;
149 int show_all; 149 int show_all;
150 char *vpath;
150}; 151};
151 152
152struct cgit_config { 153struct cgit_config {
153 char *agefile; 154 char *agefile;
154 char *cache_root; 155 char *cache_root;
155 char *clone_prefix; 156 char *clone_prefix;
156 char *css; 157 char *css;
157 char *favicon; 158 char *favicon;
158 char *footer; 159 char *footer;
159 char *head_include; 160 char *head_include;
160 char *header; 161 char *header;
161 char *index_header; 162 char *index_header;
162 char *index_info; 163 char *index_info;
163 char *logo; 164 char *logo;
164 char *logo_link; 165 char *logo_link;
165 char *module_link; 166 char *module_link;
166 char *robots; 167 char *robots;
167 char *root_title; 168 char *root_title;
168 char *root_desc; 169 char *root_desc;
169 char *root_readme; 170 char *root_readme;
170 char *script_name; 171 char *script_name;
171 char *section; 172 char *section;
172 char *virtual_root; 173 char *virtual_root;
173 int cache_size; 174 int cache_size;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 5c24381..a853522 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -423,49 +423,49 @@ logo=/img/mylogo.png
423# Enable statistics per week, month and quarter 423# Enable statistics per week, month and quarter
424max-stats=quarter 424max-stats=quarter
425 425
426 426
427# Set the title and heading of the repository index page 427# Set the title and heading of the repository index page
428root-title=foobar.com git repositories 428root-title=foobar.com git repositories
429 429
430 430
431# Set a subheading for the repository index page 431# Set a subheading for the repository index page
432root-desc=tracking the foobar development 432root-desc=tracking the foobar development
433 433
434 434
435# Include some more info about foobar.com on the index page 435# Include some more info about foobar.com on the index page
436root-readme=/var/www/htdocs/about.html 436root-readme=/var/www/htdocs/about.html
437 437
438 438
439# Allow download of tar.gz, tar.bz2 and zip-files 439# Allow download of tar.gz, tar.bz2 and zip-files
440snapshots=tar.gz tar.bz2 zip 440snapshots=tar.gz tar.bz2 zip
441 441
442 442
443## 443##
444## List of common mimetypes 444## List of common mimetypes
445## 445##
446 446
447mimetype.git=image/git 447mimetype.gif=image/gif
448mimetype.html=text/html 448mimetype.html=text/html
449mimetype.jpg=image/jpeg 449mimetype.jpg=image/jpeg
450mimetype.jpeg=image/jpeg 450mimetype.jpeg=image/jpeg
451mimetype.pdf=application/pdf 451mimetype.pdf=application/pdf
452mimetype.png=image/png 452mimetype.png=image/png
453mimetype.svg=image/svg+xml 453mimetype.svg=image/svg+xml
454 454
455 455
456## 456##
457## List of repositories. 457## List of repositories.
458## PS: Any repositories listed when section is unset will not be 458## PS: Any repositories listed when section is unset will not be
459## displayed under a section heading 459## displayed under a section heading
460## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 460## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
461## and included like this: 461## and included like this:
462## include=/etc/cgitrepos 462## include=/etc/cgitrepos
463## 463##
464 464
465 465
466repo.url=foo 466repo.url=foo
467repo.path=/pub/git/foo.git 467repo.path=/pub/git/foo.git
468repo.desc=the master foo repository 468repo.desc=the master foo repository
469repo.owner=fooman@foobar.com 469repo.owner=fooman@foobar.com
470repo.readme=info/web/about.html 470repo.readme=info/web/about.html
471 471
diff --git a/cmd.c b/cmd.c
index ad784fc..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -30,142 +30,142 @@ static 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); 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} 71}
72 72
73static void ls_cache_fn(struct cgit_context *ctx) 73static void ls_cache_fn(struct cgit_context *ctx)
74{ 74{
75 ctx->page.mimetype = "text/plain"; 75 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 76 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 77 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 78 cache_ls(ctx->cfg.cache_root);
79} 79}
80 80
81static void objects_fn(struct cgit_context *ctx) 81static void objects_fn(struct cgit_context *ctx)
82{ 82{
83 cgit_clone_objects(ctx); 83 cgit_clone_objects(ctx);
84} 84}
85 85
86static void repolist_fn(struct cgit_context *ctx) 86static void repolist_fn(struct cgit_context *ctx)
87{ 87{
88 cgit_print_repolist(); 88 cgit_print_repolist();
89} 89}
90 90
91static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
92{ 92{
93 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 94}
95 95
96static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
97{ 97{
98 cgit_print_plain(ctx); 98 cgit_print_plain(ctx);
99} 99}
100 100
101static void refs_fn(struct cgit_context *ctx) 101static void refs_fn(struct cgit_context *ctx)
102{ 102{
103 cgit_print_refs(); 103 cgit_print_refs();
104} 104}
105 105
106static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
107{ 107{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 109 ctx->repo->snapshots, ctx->qry.nohead);
110} 110}
111 111
112static void stats_fn(struct cgit_context *ctx) 112static void stats_fn(struct cgit_context *ctx)
113{ 113{
114 cgit_show_stats(ctx); 114 cgit_show_stats(ctx);
115} 115}
116 116
117static void summary_fn(struct cgit_context *ctx) 117static void summary_fn(struct cgit_context *ctx)
118{ 118{
119 cgit_print_summary(); 119 cgit_print_summary();
120} 120}
121 121
122static void tag_fn(struct cgit_context *ctx) 122static void tag_fn(struct cgit_context *ctx)
123{ 123{
124 cgit_print_tag(ctx->qry.sha1); 124 cgit_print_tag(ctx->qry.sha1);
125} 125}
126 126
127static void tree_fn(struct cgit_context *ctx) 127static void tree_fn(struct cgit_context *ctx)
128{ 128{
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 130}
131 131
132#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 134
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 136{
137 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1), 153 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1, 1),
157 }; 157 };
158 int i; 158 int i;
159 159
160 if (ctx->qry.page == NULL) { 160 if (ctx->qry.page == NULL) {
161 if (ctx->repo) 161 if (ctx->repo)
162 ctx->qry.page = "summary"; 162 ctx->qry.page = "summary";
163 else 163 else
164 ctx->qry.page = "repolist"; 164 ctx->qry.page = "repolist";
165 } 165 }
166 166
167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
168 if (!strcmp(ctx->qry.page, cmds[i].name)) 168 if (!strcmp(ctx->qry.page, cmds[i].name))
169 return &cmds[i]; 169 return &cmds[i];
170 return NULL; 170 return NULL;
171} 171}
diff --git a/cmd.h b/cmd.h
index ec9e691..8dc01bd 100644
--- a/cmd.h
+++ b/cmd.h
@@ -1,15 +1,16 @@
1#ifndef CMD_H 1#ifndef CMD_H
2#define CMD_H 2#define CMD_H
3 3
4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx); 4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx);
5 5
6struct cgit_cmd { 6struct cgit_cmd {
7 const char *name; 7 const char *name;
8 cgit_cmd_fn fn; 8 cgit_cmd_fn fn;
9 unsigned int want_repo:1, 9 unsigned int want_repo:1,
10 want_layout:1; 10 want_layout:1,
11 want_vpath:1;
11}; 12};
12 13
13extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); 14extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx);
14 15
15#endif /* CMD_H */ 16#endif /* CMD_H */
diff --git a/shared.c b/shared.c
index 8b3a045..58837dc 100644
--- a/shared.c
+++ b/shared.c
@@ -1,37 +1,36 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd;
14 13
15int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
16{ 15{
17 if (result != 0) 16 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
19 return result; 18 return result;
20} 19}
21 20
22int chk_positive(int result, char *msg) 21int chk_positive(int result, char *msg)
23{ 22{
24 if (result <= 0) 23 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 24 die("%s: %s", msg, strerror(errno));
26 return result; 25 return result;
27} 26}
28 27
29int chk_non_negative(int result, char *msg) 28int chk_non_negative(int result, char *msg)
30{ 29{
31 if (result < 0) 30 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 31 die("%s: %s",msg, strerror(errno));
33 return result; 32 return result;
34} 33}
35 34
36struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
37{ 36{
diff --git a/ui-commit.c b/ui-commit.c
index 41313b9..a11bc5f 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,127 +1,131 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info, *parent_info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp, *tmp2; 21 char *tmp, *tmp2;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
64 html(") ("); 64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1); 66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
67 else 67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1); 68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
69 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
70 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
71 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
73 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
74 if (prefix) {
75 html(" /");
76 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
77 }
74 html("</td></tr>\n"); 78 html("</td></tr>\n");
75 for (p = commit->parents; p ; p = p->next) { 79 for (p = commit->parents; p ; p = p->next) {
76 parent = lookup_commit_reference(p->item->object.sha1); 80 parent = lookup_commit_reference(p->item->object.sha1);
77 if (!parent) { 81 if (!parent) {
78 html("<tr><td colspan='3'>"); 82 html("<tr><td colspan='3'>");
79 cgit_print_error("Error reading parent commit"); 83 cgit_print_error("Error reading parent commit");
80 html("</td></tr>"); 84 html("</td></tr>");
81 continue; 85 continue;
82 } 86 }
83 html("<tr><th>parent</th>" 87 html("<tr><th>parent</th>"
84 "<td colspan='2' class='sha1'>"); 88 "<td colspan='2' class='sha1'>");
85 tmp = tmp2 = sha1_to_hex(p->item->object.sha1); 89 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
86 if (ctx.repo->enable_subject_links) { 90 if (ctx.repo->enable_subject_links) {
87 parent_info = cgit_parse_commit(parent); 91 parent_info = cgit_parse_commit(parent);
88 tmp2 = parent_info->subject; 92 tmp2 = parent_info->subject;
89 } 93 }
90 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, 0); 94 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
91 html(" ("); 95 html(" (");
92 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 96 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
93 sha1_to_hex(p->item->object.sha1), NULL, 0); 97 sha1_to_hex(p->item->object.sha1), prefix, 0);
94 html(")</td></tr>"); 98 html(")</td></tr>");
95 parents++; 99 parents++;
96 } 100 }
97 if (ctx.repo->snapshots) { 101 if (ctx.repo->snapshots) {
98 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 102 html("<tr><th>download</th><td colspan='2' class='sha1'>");
99 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 103 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
100 hex, ctx.repo->snapshots); 104 hex, ctx.repo->snapshots);
101 html("</td></tr>"); 105 html("</td></tr>");
102 } 106 }
103 html("</table>\n"); 107 html("</table>\n");
104 html("<div class='commit-subject'>"); 108 html("<div class='commit-subject'>");
105 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
106 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
107 html_txt(info->subject); 111 html_txt(info->subject);
108 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
109 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
110 show_commit_decorations(commit); 114 show_commit_decorations(commit);
111 html("</div>"); 115 html("</div>");
112 html("<div class='commit-msg'>"); 116 html("<div class='commit-msg'>");
113 if (ctx.repo->commit_filter) 117 if (ctx.repo->commit_filter)
114 cgit_open_filter(ctx.repo->commit_filter); 118 cgit_open_filter(ctx.repo->commit_filter);
115 html_txt(info->msg); 119 html_txt(info->msg);
116 if (ctx.repo->commit_filter) 120 if (ctx.repo->commit_filter)
117 cgit_close_filter(ctx.repo->commit_filter); 121 cgit_close_filter(ctx.repo->commit_filter);
118 html("</div>"); 122 html("</div>");
119 if (parents < 3) { 123 if (parents < 3) {
120 if (parents) 124 if (parents)
121 tmp = sha1_to_hex(commit->parents->item->object.sha1); 125 tmp = sha1_to_hex(commit->parents->item->object.sha1);
122 else 126 else
123 tmp = NULL; 127 tmp = NULL;
124 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 128 cgit_print_diff(ctx.qry.sha1, tmp, prefix);
125 } 129 }
126 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
127} 131}
diff --git a/ui-commit.h b/ui-commit.h
index 40bcb31..8198b4b 100644
--- a/ui-commit.h
+++ b/ui-commit.h
@@ -1,6 +1,6 @@
1#ifndef UI_COMMIT_H 1#ifndef UI_COMMIT_H
2#define UI_COMMIT_H 2#define UI_COMMIT_H
3 3
4extern void cgit_print_commit(char *hex); 4extern void cgit_print_commit(char *hex, const char *prefix);
5 5
6#endif /* UI_COMMIT_H */ 6#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
index a92a768..fb836df 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -133,59 +133,61 @@ static void inspect_filepair(struct diff_filepair *pair)
133 slots = 4; 133 slots = 4;
134 else 134 else
135 slots = slots * 2; 135 slots = slots * 2;
136 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
137 } 137 }
138 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
139 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
140 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
141 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
142 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
143 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
144 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
145 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
146 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
147 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
148 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
149 items[files-1].binary = binary; 149 items[files-1].binary = binary;
150 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
151 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
152 total_adds += lines_added; 152 total_adds += lines_added;
153 total_rems += lines_removed; 153 total_rems += lines_removed;
154} 154}
155 155
156void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
157 const unsigned char *new_sha1) 157 const unsigned char *new_sha1, const char *prefix)
158{ 158{
159 int i; 159 int i;
160 160
161 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
163 ctx.qry.sha2, NULL, 0); 163 ctx.qry.sha2, NULL, 0);
164 if (prefix)
165 htmlf(" (limited to '%s')", prefix);
164 html("</div>"); 166 html("</div>");
165 html("<table summary='diffstat' class='diffstat'>"); 167 html("<table summary='diffstat' class='diffstat'>");
166 max_changes = 0; 168 max_changes = 0;
167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 169 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix);
168 for(i = 0; i<files; i++) 170 for(i = 0; i<files; i++)
169 print_fileinfo(&items[i]); 171 print_fileinfo(&items[i]);
170 html("</table>"); 172 html("</table>");
171 html("<div class='diffstat-summary'>"); 173 html("<div class='diffstat-summary'>");
172 htmlf("%d files changed, %d insertions, %d deletions", 174 htmlf("%d files changed, %d insertions, %d deletions",
173 files, total_adds, total_rems); 175 files, total_adds, total_rems);
174 html("</div>"); 176 html("</div>");
175} 177}
176 178
177 179
178/* 180/*
179 * print a single line returned from xdiff 181 * print a single line returned from xdiff
180 */ 182 */
181static void print_line(char *line, int len) 183static void print_line(char *line, int len)
182{ 184{
183 char *class = "ctx"; 185 char *class = "ctx";
184 char c = line[len-1]; 186 char c = line[len-1];
185 187
186 if (line[0] == '+') 188 if (line[0] == '+')
187 class = "add"; 189 class = "add";
188 else if (line[0] == '-') 190 else if (line[0] == '-')
189 class = "del"; 191 class = "del";
190 else if (line[0] == '@') 192 else if (line[0] == '@')
191 class = "hunk"; 193 class = "hunk";
@@ -317,37 +319,37 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi
317 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 319 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
318 320
319 if (old_rev) 321 if (old_rev)
320 get_sha1(old_rev, old_rev_sha1); 322 get_sha1(old_rev, old_rev_sha1);
321 else if (commit->parents && commit->parents->item) 323 else if (commit->parents && commit->parents->item)
322 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 324 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
323 else 325 else
324 hashclr(old_rev_sha1); 326 hashclr(old_rev_sha1);
325 327
326 if (!is_null_sha1(old_rev_sha1)) { 328 if (!is_null_sha1(old_rev_sha1)) {
327 type = sha1_object_info(old_rev_sha1, &size); 329 type = sha1_object_info(old_rev_sha1, &size);
328 if (type == OBJ_BAD) { 330 if (type == OBJ_BAD) {
329 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 331 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
330 return; 332 return;
331 } 333 }
332 commit2 = lookup_commit_reference(old_rev_sha1); 334 commit2 = lookup_commit_reference(old_rev_sha1);
333 if (!commit2 || parse_commit(commit2)) 335 if (!commit2 || parse_commit(commit2))
334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 336 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
335 } 337 }
336 338
337 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 339 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
338 use_ssdiff = 1; 340 use_ssdiff = 1;
339 341
340 print_ssdiff_link(); 342 print_ssdiff_link();
341 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 343 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
342 344
343 if (use_ssdiff) { 345 if (use_ssdiff) {
344 html("<table summary='ssdiff' class='ssdiff'>"); 346 html("<table summary='ssdiff' class='ssdiff'>");
345 } else { 347 } else {
346 html("<table summary='diff' class='diff'>"); 348 html("<table summary='diff' class='diff'>");
347 html("<tr><td>"); 349 html("<tr><td>");
348 } 350 }
349 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 351 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
350 if (!use_ssdiff) 352 if (!use_ssdiff)
351 html("</td></tr>"); 353 html("</td></tr>");
352 html("</table>"); 354 html("</table>");
353} 355}
diff --git a/ui-log.c b/ui-log.c
index 0947604..bfbe436 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -25,92 +25,95 @@ void count_lines(char *line, int size)
25} 25}
26 26
27void inspect_files(struct diff_filepair *pair) 27void inspect_files(struct diff_filepair *pair)
28{ 28{
29 unsigned long old_size = 0; 29 unsigned long old_size = 0;
30 unsigned long new_size = 0; 30 unsigned long new_size = 0;
31 int binary = 0; 31 int binary = 0;
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
40{ 40{
41 struct name_decoration *deco; 41 struct name_decoration *deco;
42 static char buf[1024]; 42 static char buf[1024];
43 43
44 buf[sizeof(buf) - 1] = 0; 44 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 ctx.qry.vpath, 0, NULL, NULL,
51 ctx.qry.showmsg);
51 } 52 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 53 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 54 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 55 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 56 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 57 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 58 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 59 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 60 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 61 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 62 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 63 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 64 sha1_to_hex(commit->object.sha1),
64 0, NULL, NULL, ctx.qry.showmsg); 65 ctx.qry.vpath, 0, NULL, NULL,
66 ctx.qry.showmsg);
65 } 67 }
66 else { 68 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 69 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 70 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1), 0); 71 sha1_to_hex(commit->object.sha1),
72 ctx.qry.vpath, 0);
70 } 73 }
71 deco = deco->next; 74 deco = deco->next;
72 } 75 }
73} 76}
74 77
75void print_commit(struct commit *commit) 78void print_commit(struct commit *commit)
76{ 79{
77 struct commitinfo *info; 80 struct commitinfo *info;
78 char *tmp; 81 char *tmp;
79 int cols = 2; 82 int cols = 2;
80 83
81 info = cgit_parse_commit(commit); 84 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 85 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 86 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 87 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 88 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
86 html_link_open(tmp, NULL, NULL); 89 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 90 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 91 html_link_close();
89 htmlf("</td><td%s>", 92 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 93 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 94 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1), 0); 95 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
93 show_commit_decorations(commit); 96 show_commit_decorations(commit);
94 html("</td><td>"); 97 html("</td><td>");
95 html_txt(info->author); 98 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 99 if (ctx.repo->enable_log_filecount) {
97 files = 0; 100 files = 0;
98 add_lines = 0; 101 add_lines = 0;
99 rem_lines = 0; 102 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 103 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 104 html("</td><td>");
102 htmlf("%d", files); 105 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 106 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 107 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 108 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 109 }
107 } 110 }
108 html("</td></tr>\n"); 111 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 112 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 113 if (ctx.repo->enable_log_filecount) {
111 cols++; 114 cols++;
112 if (ctx.repo->enable_log_linecount) 115 if (ctx.repo->enable_log_linecount)
113 cols++; 116 cols++;
114 } 117 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 118 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 119 cols);
@@ -155,80 +158,80 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
155 argv[argc++] = "--"; 158 argv[argc++] = "--";
156 argv[argc++] = path; 159 argv[argc++] = path;
157 } 160 }
158 init_revisions(&rev, NULL); 161 init_revisions(&rev, NULL);
159 rev.abbrev = DEFAULT_ABBREV; 162 rev.abbrev = DEFAULT_ABBREV;
160 rev.commit_format = CMIT_FMT_DEFAULT; 163 rev.commit_format = CMIT_FMT_DEFAULT;
161 rev.verbose_header = 1; 164 rev.verbose_header = 1;
162 rev.show_root_diff = 0; 165 rev.show_root_diff = 0;
163 setup_revisions(argc, argv, &rev, NULL); 166 setup_revisions(argc, argv, &rev, NULL);
164 load_ref_decorations(DECORATE_FULL_REFS); 167 load_ref_decorations(DECORATE_FULL_REFS);
165 rev.show_decorations = 1; 168 rev.show_decorations = 1;
166 rev.grep_filter.regflags |= REG_ICASE; 169 rev.grep_filter.regflags |= REG_ICASE;
167 compile_grep_patterns(&rev.grep_filter); 170 compile_grep_patterns(&rev.grep_filter);
168 prepare_revision_walk(&rev); 171 prepare_revision_walk(&rev);
169 172
170 if (pager) 173 if (pager)
171 html("<table class='list nowrap'>"); 174 html("<table class='list nowrap'>");
172 175
173 html("<tr class='nohover'><th class='left'>Age</th>" 176 html("<tr class='nohover'><th class='left'>Age</th>"
174 "<th class='left'>Commit message"); 177 "<th class='left'>Commit message");
175 if (pager) { 178 if (pager) {
176 html(" ("); 179 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 180 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 181 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 182 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 183 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 184 html(")");
182 } 185 }
183 html("</th><th class='left'>Author</th>"); 186 html("</th><th class='left'>Author</th>");
184 if (ctx.repo->enable_log_filecount) { 187 if (ctx.repo->enable_log_filecount) {
185 html("<th class='left'>Files</th>"); 188 html("<th class='left'>Files</th>");
186 columns++; 189 columns++;
187 if (ctx.repo->enable_log_linecount) { 190 if (ctx.repo->enable_log_linecount) {
188 html("<th class='left'>Lines</th>"); 191 html("<th class='left'>Lines</th>");
189 columns++; 192 columns++;
190 } 193 }
191 } 194 }
192 html("</tr>\n"); 195 html("</tr>\n");
193 196
194 if (ofs<0) 197 if (ofs<0)
195 ofs = 0; 198 ofs = 0;
196 199
197 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 200 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
198 free(commit->buffer); 201 free(commit->buffer);
199 commit->buffer = NULL; 202 commit->buffer = NULL;
200 free_commit_list(commit->parents); 203 free_commit_list(commit->parents);
201 commit->parents = NULL; 204 commit->parents = NULL;
202 } 205 }
203 206
204 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 207 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
205 print_commit(commit); 208 print_commit(commit);
206 free(commit->buffer); 209 free(commit->buffer);
207 commit->buffer = NULL; 210 commit->buffer = NULL;
208 free_commit_list(commit->parents); 211 free_commit_list(commit->parents);
209 commit->parents = NULL; 212 commit->parents = NULL;
210 } 213 }
211 if (pager) { 214 if (pager) {
212 htmlf("</table><div class='pager'>", 215 htmlf("</table><div class='pager'>",
213 columns); 216 columns);
214 if (ofs > 0) { 217 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 218 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 219 ctx.qry.sha1, ctx.qry.vpath,
217 ofs - cnt, ctx.qry.grep, 220 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 221 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 222 html("&nbsp;");
220 } 223 }
221 if ((commit = get_revision(&rev)) != NULL) { 224 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 225 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 226 ctx.qry.sha1, ctx.qry.vpath,
224 ofs + cnt, ctx.qry.grep, 227 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 228 ctx.qry.search, ctx.qry.showmsg);
226 } 229 }
227 html("</div>"); 230 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 231 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 232 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 233 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
231 NULL, NULL, ctx.qry.showmsg); 234 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 235 html("</td></tr>\n");
233 } 236 }
234} 237}
diff --git a/ui-patch.c b/ui-patch.c
index 2a8f7a5..25dc9fe 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -56,74 +56,76 @@ static void header(unsigned char *sha1, char *path1, int mode1,
56} 56}
57 57
58static void filepair_cb(struct diff_filepair *pair) 58static void filepair_cb(struct diff_filepair *pair)
59{ 59{
60 unsigned long old_size = 0; 60 unsigned long old_size = 0;
61 unsigned long new_size = 0; 61 unsigned long new_size = 0;
62 int binary = 0; 62 int binary = 0;
63 63
64 header(pair->one->sha1, pair->one->path, pair->one->mode, 64 header(pair->one->sha1, pair->one->path, pair->one->mode,
65 pair->two->sha1, pair->two->path, pair->two->mode); 65 pair->two->sha1, pair->two->path, pair->two->mode);
66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
67 if (S_ISGITLINK(pair->one->mode)) 67 if (S_ISGITLINK(pair->one->mode))
68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
69 if (S_ISGITLINK(pair->two->mode)) 69 if (S_ISGITLINK(pair->two->mode))
70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex, const char *prefix)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
84 unsigned char sha1[20], old_sha1[20]; 84 unsigned char sha1[20], old_sha1[20];
85 char *patchname; 85 char *patchname;
86 86
87 if (!hex) 87 if (!hex)
88 hex = ctx.qry.head; 88 hex = ctx.qry.head;
89 89
90 if (get_sha1(hex, sha1)) { 90 if (get_sha1(hex, sha1)) {
91 cgit_print_error(fmt("Bad object id: %s", hex)); 91 cgit_print_error(fmt("Bad object id: %s", hex));
92 return; 92 return;
93 } 93 }
94 commit = lookup_commit_reference(sha1); 94 commit = lookup_commit_reference(sha1);
95 if (!commit) { 95 if (!commit) {
96 cgit_print_error(fmt("Bad commit reference: %s", hex)); 96 cgit_print_error(fmt("Bad commit reference: %s", hex));
97 return; 97 return;
98 } 98 }
99 info = cgit_parse_commit(commit); 99 info = cgit_parse_commit(commit);
100 100
101 if (commit->parents && commit->parents->item) 101 if (commit->parents && commit->parents->item)
102 hashcpy(old_sha1, commit->parents->item->object.sha1); 102 hashcpy(old_sha1, commit->parents->item->object.sha1);
103 else 103 else
104 hashclr(old_sha1); 104 hashclr(old_sha1);
105 105
106 patchname = fmt("%s.patch", sha1_to_hex(sha1)); 106 patchname = fmt("%s.patch", sha1_to_hex(sha1));
107 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
108 ctx.page.filename = patchname; 108 ctx.page.filename = patchname;
109 cgit_print_http_headers(&ctx); 109 cgit_print_http_headers(&ctx);
110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); 110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
111 htmlf("From: %s", info->author); 111 htmlf("From: %s", info->author);
112 if (!ctx.cfg.noplainemail) { 112 if (!ctx.cfg.noplainemail) {
113 htmlf(" %s", info->author_email); 113 htmlf(" %s", info->author_email);
114 } 114 }
115 html("\n"); 115 html("\n");
116 html("Date: "); 116 html("Date: ");
117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); 117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time);
118 htmlf("Subject: %s\n\n", info->subject); 118 htmlf("Subject: %s\n\n", info->subject);
119 if (info->msg && *info->msg) { 119 if (info->msg && *info->msg) {
120 htmlf("%s", info->msg); 120 htmlf("%s", info->msg);
121 if (info->msg[strlen(info->msg) - 1] != '\n') 121 if (info->msg[strlen(info->msg) - 1] != '\n')
122 html("\n"); 122 html("\n");
123 } 123 }
124 html("---\n"); 124 html("---\n");
125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix);
126 html("--\n"); 128 html("--\n");
127 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
128 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
129} 131}
diff --git a/ui-patch.h b/ui-patch.h
index 9f68212..1641cea 100644
--- a/ui-patch.h
+++ b/ui-patch.h
@@ -1,6 +1,6 @@
1#ifndef UI_PATCH_H 1#ifndef UI_PATCH_H
2#define UI_PATCH_H 2#define UI_PATCH_H
3 3
4extern void cgit_print_patch(char *hex); 4extern void cgit_print_patch(char *hex, const char *prefix);
5 5
6#endif /* UI_PATCH_H */ 6#endif /* UI_PATCH_H */
diff --git a/ui-refs.c b/ui-refs.c
index 98738db..94ff6be 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -53,49 +53,49 @@ static int cmp_tag_age(const void *a, const void *b)
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
diff --git a/ui-shared.c b/ui-shared.c
index 8827fff..e991799 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -6,49 +6,49 @@
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_httpscheme() 37char *cgit_httpscheme()
38{ 38{
39 if (ctx.env.https && !strcmp(ctx.env.https, "on")) 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://"; 40 return "https://";
41 else 41 else
42 return "http://"; 42 return "http://";
43} 43}
44 44
45char *cgit_hosturl() 45char *cgit_hosturl()
46{ 46{
47 if (ctx.env.http_host) 47 if (ctx.env.http_host)
48 return ctx.env.http_host; 48 return ctx.env.http_host;
49 if (!ctx.env.server_name) 49 if (!ctx.env.server_name)
50 return NULL; 50 return NULL;
51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
52 return ctx.env.server_name; 52 return ctx.env.server_name;
53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
54} 54}
@@ -112,104 +112,104 @@ const char *cgit_repobasename(const char *reponame)
112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
113 p -= 3; rvbuf[p--] = 0; 113 p -= 3; rvbuf[p--] = 0;
114 } 114 }
115 /* strip more trailing slashes if any */ 115 /* strip more trailing slashes if any */
116 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 116 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
117 /* find last slash in the remaining string */ 117 /* find last slash in the remaining string */
118 rv = strrchr(rvbuf,'/'); 118 rv = strrchr(rvbuf,'/');
119 if(rv) 119 if(rv)
120 return ++rv; 120 return ++rv;
121 return rvbuf; 121 return rvbuf;
122} 122}
123 123
124char *cgit_currurl() 124char *cgit_currurl()
125{ 125{
126 if (!ctx.cfg.virtual_root) 126 if (!ctx.cfg.virtual_root)
127 return ctx.cfg.script_name; 127 return ctx.cfg.script_name;
128 else if (ctx.qry.page) 128 else if (ctx.qry.page)
129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
143 html("/"); 143 html("/");
144 } else 144 } else
145 html(ctx.cfg.script_name); 145 html(ctx.cfg.script_name);
146 146
147 if (page) { 147 if (page) {
148 htmlf("?p=%s", page); 148 htmlf("?p=%s", page);
149 delim = "&"; 149 delim = "&";
150 } 150 }
151 if (search) { 151 if (search) {
152 html(delim); 152 html(delim);
153 html("q="); 153 html("q=");
154 html_attr(search); 154 html_attr(search);
155 delim = "&"; 155 delim = "&";
156 } 156 }
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(char *page, char *name, char *title, char *class, 163static void site_link(const char *page, const char *name, const char *title,
164 char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
171 } 171 }
172 if (class) { 172 if (class) {
173 html(" class='"); 173 html(" class='");
174 html_attr(class); 174 html_attr(class);
175 html("'"); 175 html("'");
176 } 176 }
177 html(" href='"); 177 html(" href='");
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(char *name, char *title, char *class, char *pattern, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(char *title, char *class, char *page, char *head, 190static char *repolink(const char *title, const char *class, const char *page,
191 char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
198 html_attr(title); 198 html_attr(title);
199 html("'"); 199 html("'");
200 } 200 }
201 if (class) { 201 if (class) {
202 html(" class='"); 202 html(" class='");
203 html_attr(class); 203 html_attr(class);
204 html("'"); 204 html("'");
205 } 205 }
206 html(" href='"); 206 html(" href='");
207 if (ctx.cfg.virtual_root) { 207 if (ctx.cfg.virtual_root) {
208 html_url_path(ctx.cfg.virtual_root); 208 html_url_path(ctx.cfg.virtual_root);
209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
210 html("/"); 210 html("/");
211 html_url_path(ctx.repo->url); 211 html_url_path(ctx.repo->url);
212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
213 html("/"); 213 html("/");
214 if (page) { 214 if (page) {
215 html_url_path(page); 215 html_url_path(page);
@@ -219,241 +219,303 @@ static char *repolink(char *title, char *class, char *page, char *head,
219 } 219 }
220 } else { 220 } else {
221 html(ctx.cfg.script_name); 221 html(ctx.cfg.script_name);
222 html("?url="); 222 html("?url=");
223 html_url_arg(ctx.repo->url); 223 html_url_arg(ctx.repo->url);
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(const char *page, const char *name, const char *title,
244 char *head, char *rev, char *path) 244 const char *class, const char *head, const char *rev,
245 const char *path)
245{ 246{
246 char *delim; 247 char *delim;
247 248
248 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
249 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { 250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
250 html(delim); 251 html(delim);
251 html("id="); 252 html("id=");
252 html_url_arg(rev); 253 html_url_arg(rev);
253 } 254 }
254 html("'>"); 255 html("'>");
255 html_txt(name); 256 html_txt(name);
256 html("</a>"); 257 html("</a>");
257} 258}
258 259
259void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head)
260{ 262{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 264}
263 265
264void cgit_tag_link(char *name, char *title, char *class, char *head, 266void cgit_tag_link(const char *name, const char *title, const char *class,
265 char *rev) 267 const char *head, const char *rev)
266{ 268{
267 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
268} 270}
269 271
270void cgit_tree_link(char *name, char *title, char *class, char *head, 272void cgit_tree_link(const char *name, const char *title, const char *class,
271 char *rev, char *path) 273 const char *head, const char *rev, const char *path)
272{ 274{
273 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
274} 276}
275 277
276void cgit_plain_link(char *name, char *title, char *class, char *head, 278void cgit_plain_link(const char *name, const char *title, const char *class,
277 char *rev, char *path) 279 const char *head, const char *rev, const char *path)
278{ 280{
279 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
280} 282}
281 283
282void cgit_log_link(char *name, char *title, char *class, char *head, 284void cgit_log_link(const char *name, const char *title, const char *class,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 285 const char *head, const char *rev, const char *path,
284 int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
285{ 287{
286 char *delim; 288 char *delim;
287 289
288 delim = repolink(title, class, "log", head, path); 290 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 291 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 292 html(delim);
291 html("id="); 293 html("id=");
292 html_url_arg(rev); 294 html_url_arg(rev);
293 delim = "&"; 295 delim = "&";
294 } 296 }
295 if (grep && pattern) { 297 if (grep && pattern) {
296 html(delim); 298 html(delim);
297 html("qt="); 299 html("qt=");
298 html_url_arg(grep); 300 html_url_arg(grep);
299 delim = "&"; 301 delim = "&";
300 html(delim); 302 html(delim);
301 html("q="); 303 html("q=");
302 html_url_arg(pattern); 304 html_url_arg(pattern);
303 } 305 }
304 if (ofs > 0) { 306 if (ofs > 0) {
305 html(delim); 307 html(delim);
306 html("ofs="); 308 html("ofs=");
307 htmlf("%d", ofs); 309 htmlf("%d", ofs);
308 delim = "&"; 310 delim = "&";
309 } 311 }
310 if (showmsg) { 312 if (showmsg) {
311 html(delim); 313 html(delim);
312 html("showmsg=1"); 314 html("showmsg=1");
313 } 315 }
314 html("'>"); 316 html("'>");
315 html_txt(name); 317 html_txt(name);
316 html("</a>"); 318 html("</a>");
317} 319}
318 320
319void cgit_commit_link(char *name, char *title, char *class, char *head, 321void cgit_commit_link(char *name, const char *title, const char *class,
320 char *rev, int toggle_ssdiff) 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff)
321{ 324{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 327 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 330 }
328 331
329 char *delim; 332 char *delim;
330 333
331 delim = repolink(title, class, "commit", head, NULL); 334 delim = repolink(title, class, "commit", head, path);
332 if (rev && strcmp(rev, ctx.qry.head)) { 335 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim); 336 html(delim);
334 html("id="); 337 html("id=");
335 html_url_arg(rev); 338 html_url_arg(rev);
336 delim = "&amp;"; 339 delim = "&amp;";
337 } 340 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim); 342 html(delim);
340 html("ss=1"); 343 html("ss=1");
341 } 344 }
342 html("'>"); 345 html("'>");
343 html_txt(name); 346 html_txt(name);
344 html("</a>"); 347 html("</a>");
345} 348}
346 349
347void cgit_refs_link(char *name, char *title, char *class, char *head, 350void cgit_refs_link(const char *name, const char *title, const char *class,
348 char *rev, char *path) 351 const char *head, const char *rev, const char *path)
349{ 352{
350 reporevlink("refs", name, title, class, head, rev, path); 353 reporevlink("refs", name, title, class, head, rev, path);
351} 354}
352 355
353void cgit_snapshot_link(char *name, char *title, char *class, char *head, 356void cgit_snapshot_link(const char *name, const char *title, const char *class,
354 char *rev, char *archivename) 357 const char *head, const char *rev,
358 const char *archivename)
355{ 359{
356 reporevlink("snapshot", name, title, class, head, rev, archivename); 360 reporevlink("snapshot", name, title, class, head, rev, archivename);
357} 361}
358 362
359void cgit_diff_link(char *name, char *title, char *class, char *head, 363void cgit_diff_link(const char *name, const char *title, const char *class,
360 char *new_rev, char *old_rev, char *path, 364 const char *head, const char *new_rev, const char *old_rev,
361 int toggle_ssdiff) 365 const char *path, int toggle_ssdiff)
362{ 366{
363 char *delim; 367 char *delim;
364 368
365 delim = repolink(title, class, "diff", head, path); 369 delim = repolink(title, class, "diff", head, path);
366 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 370 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
367 html(delim); 371 html(delim);
368 html("id="); 372 html("id=");
369 html_url_arg(new_rev); 373 html_url_arg(new_rev);
370 delim = "&amp;"; 374 delim = "&amp;";
371 } 375 }
372 if (old_rev) { 376 if (old_rev) {
373 html(delim); 377 html(delim);
374 html("id2="); 378 html("id2=");
375 html_url_arg(old_rev); 379 html_url_arg(old_rev);
376 delim = "&amp;"; 380 delim = "&amp;";
377 } 381 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 382 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim); 383 html(delim);
380 html("ss=1"); 384 html("ss=1");
381 } 385 }
382 html("'>"); 386 html("'>");
383 html_txt(name); 387 html_txt(name);
384 html("</a>"); 388 html("</a>");
385} 389}
386 390
387void cgit_patch_link(char *name, char *title, char *class, char *head, 391void cgit_patch_link(const char *name, const char *title, const char *class,
388 char *rev) 392 const char *head, const char *rev, const char *path)
389{ 393{
390 reporevlink("patch", name, title, class, head, rev, NULL); 394 reporevlink("patch", name, title, class, head, rev, path);
391} 395}
392 396
393void cgit_stats_link(char *name, char *title, char *class, char *head, 397void cgit_stats_link(const char *name, const char *title, const char *class,
394 char *path) 398 const char *head, const char *path)
395{ 399{
396 reporevlink("stats", name, title, class, head, NULL, path); 400 reporevlink("stats", name, title, class, head, NULL, path);
397} 401}
398 402
403void cgit_self_link(char *name, const char *title, const char *class,
404 struct cgit_context *ctx)
405{
406 if (!strcmp(ctx->qry.page, "repolist"))
407 return cgit_index_link(name, title, class, ctx->qry.search,
408 ctx->qry.ofs);
409 else if (!strcmp(ctx->qry.page, "summary"))
410 return cgit_summary_link(name, title, class, ctx->qry.head);
411 else if (!strcmp(ctx->qry.page, "tag"))
412 return cgit_tag_link(name, title, class, ctx->qry.head,
413 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
414 else if (!strcmp(ctx->qry.page, "tree"))
415 return cgit_tree_link(name, title, class, ctx->qry.head,
416 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
417 ctx->qry.path);
418 else if (!strcmp(ctx->qry.page, "plain"))
419 return cgit_plain_link(name, title, class, ctx->qry.head,
420 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
421 ctx->qry.path);
422 else if (!strcmp(ctx->qry.page, "log"))
423 return cgit_log_link(name, title, class, ctx->qry.head,
424 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
425 ctx->qry.path, ctx->qry.ofs,
426 ctx->qry.grep, ctx->qry.search,
427 ctx->qry.showmsg);
428 else if (!strcmp(ctx->qry.page, "commit"))
429 return cgit_commit_link(name, title, class, ctx->qry.head,
430 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
431 ctx->qry.path, 0);
432 else if (!strcmp(ctx->qry.page, "patch"))
433 return cgit_patch_link(name, title, class, ctx->qry.head,
434 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
435 ctx->qry.path);
436 else if (!strcmp(ctx->qry.page, "refs"))
437 return cgit_refs_link(name, title, class, ctx->qry.head,
438 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
439 ctx->qry.path);
440 else if (!strcmp(ctx->qry.page, "snapshot"))
441 return cgit_snapshot_link(name, title, class, ctx->qry.head,
442 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
443 ctx->qry.path);
444 else if (!strcmp(ctx->qry.page, "diff"))
445 return cgit_diff_link(name, title, class, ctx->qry.head,
446 ctx->qry.sha1, ctx->qry.sha2,
447 ctx->qry.path, 0);
448 else if (!strcmp(ctx->qry.page, "stats"))
449 return cgit_stats_link(name, title, class, ctx->qry.head,
450 ctx->qry.path);
451
452 /* Don't known how to make link for this page */
453 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
454 html("><!-- cgit_self_link() doesn't know how to make link for page '");
455 html_txt(ctx->qry.page);
456 html("' -->");
457 html_txt(name);
458 html("</a>");
459}
460
399void cgit_object_link(struct object *obj) 461void cgit_object_link(struct object *obj)
400{ 462{
401 char *page, *shortrev, *fullrev, *name; 463 char *page, *shortrev, *fullrev, *name;
402 464
403 fullrev = sha1_to_hex(obj->sha1); 465 fullrev = sha1_to_hex(obj->sha1);
404 shortrev = xstrdup(fullrev); 466 shortrev = xstrdup(fullrev);
405 shortrev[10] = '\0'; 467 shortrev[10] = '\0';
406 if (obj->type == OBJ_COMMIT) { 468 if (obj->type == OBJ_COMMIT) {
407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 469 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
408 ctx.qry.head, fullrev, 0); 470 ctx.qry.head, fullrev, NULL, 0);
409 return; 471 return;
410 } else if (obj->type == OBJ_TREE) 472 } else if (obj->type == OBJ_TREE)
411 page = "tree"; 473 page = "tree";
412 else if (obj->type == OBJ_TAG) 474 else if (obj->type == OBJ_TAG)
413 page = "tag"; 475 page = "tag";
414 else 476 else
415 page = "blob"; 477 page = "blob";
416 name = fmt("%s %s...", typename(obj->type), shortrev); 478 name = fmt("%s %s...", typename(obj->type), shortrev);
417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 479 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
418} 480}
419 481
420void cgit_print_date(time_t secs, char *format, int local_time) 482void cgit_print_date(time_t secs, const char *format, int local_time)
421{ 483{
422 char buf[64]; 484 char buf[64];
423 struct tm *time; 485 struct tm *time;
424 486
425 if (!secs) 487 if (!secs)
426 return; 488 return;
427 if(local_time) 489 if(local_time)
428 time = localtime(&secs); 490 time = localtime(&secs);
429 else 491 else
430 time = gmtime(&secs); 492 time = gmtime(&secs);
431 strftime(buf, sizeof(buf)-1, format, time); 493 strftime(buf, sizeof(buf)-1, format, time);
432 html_txt(buf); 494 html_txt(buf);
433} 495}
434 496
435void cgit_print_age(time_t t, time_t max_relative, char *format) 497void cgit_print_age(time_t t, time_t max_relative, const char *format)
436{ 498{
437 time_t now, secs; 499 time_t now, secs;
438 500
439 if (!t) 501 if (!t)
440 return; 502 return;
441 time(&now); 503 time(&now);
442 secs = now - t; 504 secs = now - t;
443 505
444 if (secs > max_relative && max_relative >= 0) { 506 if (secs > max_relative && max_relative >= 0) {
445 cgit_print_date(t, format, ctx.cfg.local_time); 507 cgit_print_date(t, format, ctx.cfg.local_time);
446 return; 508 return;
447 } 509 }
448 510
449 if (secs < TM_HOUR * 2) { 511 if (secs < TM_HOUR * 2) {
450 htmlf("<span class='age-mins'>%.0f min.</span>", 512 htmlf("<span class='age-mins'>%.0f min.</span>",
451 secs * 1.0 / TM_MIN); 513 secs * 1.0 / TM_MIN);
452 return; 514 return;
453 } 515 }
454 if (secs < TM_DAY * 2) { 516 if (secs < TM_DAY * 2) {
455 htmlf("<span class='age-hours'>%.0f hours</span>", 517 htmlf("<span class='age-hours'>%.0f hours</span>",
456 secs * 1.0 / TM_HOUR); 518 secs * 1.0 / TM_HOUR);
457 return; 519 return;
458 } 520 }
459 if (secs < TM_WEEK * 2) { 521 if (secs < TM_WEEK * 2) {
@@ -510,49 +572,49 @@ void cgit_print_docstart(struct cgit_context *ctx)
510 } 572 }
511 573
512 char *host = cgit_hosturl(); 574 char *host = cgit_hosturl();
513 html(cgit_doctype); 575 html(cgit_doctype);
514 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 576 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
515 html("<head>\n"); 577 html("<head>\n");
516 html("<title>"); 578 html("<title>");
517 html_txt(ctx->page.title); 579 html_txt(ctx->page.title);
518 html("</title>\n"); 580 html("</title>\n");
519 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 581 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
520 if (ctx->cfg.robots && *ctx->cfg.robots) 582 if (ctx->cfg.robots && *ctx->cfg.robots)
521 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 583 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
522 html("<link rel='stylesheet' type='text/css' href='"); 584 html("<link rel='stylesheet' type='text/css' href='");
523 html_attr(ctx->cfg.css); 585 html_attr(ctx->cfg.css);
524 html("'/>\n"); 586 html("'/>\n");
525 if (ctx->cfg.favicon) { 587 if (ctx->cfg.favicon) {
526 html("<link rel='shortcut icon' href='"); 588 html("<link rel='shortcut icon' href='");
527 html_attr(ctx->cfg.favicon); 589 html_attr(ctx->cfg.favicon);
528 html("'/>\n"); 590 html("'/>\n");
529 } 591 }
530 if (host && ctx->repo) { 592 if (host && ctx->repo) {
531 html("<link rel='alternate' title='Atom feed' href='"); 593 html("<link rel='alternate' title='Atom feed' href='");
532 html(cgit_httpscheme()); 594 html(cgit_httpscheme());
533 html_attr(cgit_hosturl()); 595 html_attr(cgit_hosturl());
534 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 596 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
535 fmt("h=%s", ctx->qry.head))); 597 fmt("h=%s", ctx->qry.head)));
536 html("' type='application/atom+xml'/>\n"); 598 html("' type='application/atom+xml'/>\n");
537 } 599 }
538 if (ctx->cfg.head_include) 600 if (ctx->cfg.head_include)
539 html_include(ctx->cfg.head_include); 601 html_include(ctx->cfg.head_include);
540 html("</head>\n"); 602 html("</head>\n");
541 html("<body>\n"); 603 html("<body>\n");
542 if (ctx->cfg.header) 604 if (ctx->cfg.header)
543 html_include(ctx->cfg.header); 605 html_include(ctx->cfg.header);
544} 606}
545 607
546void cgit_print_docend() 608void cgit_print_docend()
547{ 609{
548 html("</div> <!-- class=content -->\n"); 610 html("</div> <!-- class=content -->\n");
549 if (ctx.cfg.embedded) { 611 if (ctx.cfg.embedded) {
550 html("</div> <!-- id=cgit -->\n"); 612 html("</div> <!-- id=cgit -->\n");
551 if (ctx.cfg.footer) 613 if (ctx.cfg.footer)
552 html_include(ctx.cfg.footer); 614 html_include(ctx.cfg.footer);
553 return; 615 return;
554 } 616 }
555 if (ctx.cfg.footer) 617 if (ctx.cfg.footer)
556 html_include(ctx.cfg.footer); 618 html_include(ctx.cfg.footer);
557 else { 619 else {
558 htmlf("<div class='footer'>generated by cgit %s at ", 620 htmlf("<div class='footer'>generated by cgit %s at ",
@@ -590,83 +652,103 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
590 return 1; 652 return 1;
591 if (obj->type == OBJ_TAG) { 653 if (obj->type == OBJ_TAG) {
592 tag = lookup_tag(sha1); 654 tag = lookup_tag(sha1);
593 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 655 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
594 return 0; 656 return 0;
595 hashcpy(fileid, tag->tagged->sha1); 657 hashcpy(fileid, tag->tagged->sha1);
596 } else if (obj->type != OBJ_BLOB) { 658 } else if (obj->type != OBJ_BLOB) {
597 return 0; 659 return 0;
598 } else { 660 } else {
599 hashcpy(fileid, sha1); 661 hashcpy(fileid, sha1);
600 } 662 }
601 if (!*header) { 663 if (!*header) {
602 html("<h1>download</h1>\n"); 664 html("<h1>download</h1>\n");
603 *header = 1; 665 *header = 1;
604 } 666 }
605 url = cgit_pageurl(ctx.qry.repo, "blob", 667 url = cgit_pageurl(ctx.qry.repo, "blob",
606 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 668 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
607 buf)); 669 buf));
608 html_link_open(url, NULL, "menu"); 670 html_link_open(url, NULL, "menu");
609 html_txt(strlpart(buf, 20)); 671 html_txt(strlpart(buf, 20));
610 html_link_close(); 672 html_link_close();
611 return 0; 673 return 0;
612} 674}
613 675
614void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 676void cgit_add_hidden_formfields(int incl_head, int incl_search,
677 const char *page)
615{ 678{
616 char *url; 679 char *url;
617 680
618 if (!ctx.cfg.virtual_root) { 681 if (!ctx.cfg.virtual_root) {
619 url = fmt("%s/%s", ctx.qry.repo, page); 682 url = fmt("%s/%s", ctx.qry.repo, page);
620 if (ctx.qry.path) 683 if (ctx.qry.vpath)
621 url = fmt("%s/%s", url, ctx.qry.path); 684 url = fmt("%s/%s", url, ctx.qry.vpath);
622 html_hidden("url", url); 685 html_hidden("url", url);
623 } 686 }
624 687
625 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 688 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
626 strcmp(ctx.qry.head, ctx.repo->defbranch)) 689 strcmp(ctx.qry.head, ctx.repo->defbranch))
627 html_hidden("h", ctx.qry.head); 690 html_hidden("h", ctx.qry.head);
628 691
629 if (ctx.qry.sha1) 692 if (ctx.qry.sha1)
630 html_hidden("id", ctx.qry.sha1); 693 html_hidden("id", ctx.qry.sha1);
631 if (ctx.qry.sha2) 694 if (ctx.qry.sha2)
632 html_hidden("id2", ctx.qry.sha2); 695 html_hidden("id2", ctx.qry.sha2);
633 if (ctx.qry.showmsg) 696 if (ctx.qry.showmsg)
634 html_hidden("showmsg", "1"); 697 html_hidden("showmsg", "1");
635 698
636 if (incl_search) { 699 if (incl_search) {
637 if (ctx.qry.grep) 700 if (ctx.qry.grep)
638 html_hidden("qt", ctx.qry.grep); 701 html_hidden("qt", ctx.qry.grep);
639 if (ctx.qry.search) 702 if (ctx.qry.search)
640 html_hidden("q", ctx.qry.search); 703 html_hidden("q", ctx.qry.search);
641 } 704 }
642} 705}
643 706
644const char *fallback_cmd = "repolist"; 707static const char *hc(struct cgit_context *ctx, const char *page)
708{
709 return strcmp(ctx->qry.page, page) ? NULL : "active";
710}
645 711
646char *hc(struct cgit_cmd *cmd, const char *page) 712static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
647{ 713{
648 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 714 char *old_path = ctx->qry.path;
715 char *p = path, *q, *end = path + strlen(path);
716
717 ctx->qry.path = NULL;
718 cgit_self_link("root", NULL, NULL, ctx);
719 ctx->qry.path = p = path;
720 while (p < end) {
721 if (!(q = strchr(p, '/')))
722 q = end;
723 *q = '\0';
724 html_txt("/");
725 cgit_self_link(p, NULL, NULL, ctx);
726 if (q < end)
727 *q = '/';
728 p = q + 1;
729 }
730 ctx->qry.path = old_path;
649} 731}
650 732
651static void print_header(struct cgit_context *ctx) 733static void print_header(struct cgit_context *ctx)
652{ 734{
653 html("<table id='header'>\n"); 735 html("<table id='header'>\n");
654 html("<tr>\n"); 736 html("<tr>\n");
655 737
656 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 738 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
657 html("<td class='logo' rowspan='2'><a href='"); 739 html("<td class='logo' rowspan='2'><a href='");
658 if (ctx->cfg.logo_link) 740 if (ctx->cfg.logo_link)
659 html_attr(ctx->cfg.logo_link); 741 html_attr(ctx->cfg.logo_link);
660 else 742 else
661 html_attr(cgit_rooturl()); 743 html_attr(cgit_rooturl());
662 html("'><img src='"); 744 html("'><img src='");
663 html_attr(ctx->cfg.logo); 745 html_attr(ctx->cfg.logo);
664 html("' alt='cgit logo'/></a></td>\n"); 746 html("' alt='cgit logo'/></a></td>\n");
665 } 747 }
666 748
667 html("<td class='main'>"); 749 html("<td class='main'>");
668 if (ctx->repo) { 750 if (ctx->repo) {
669 cgit_index_link("index", NULL, NULL, NULL, 0); 751 cgit_index_link("index", NULL, NULL, NULL, 0);
670 html(" : "); 752 html(" : ");
671 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 753 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
672 html("</td><td class='form'>"); 754 html("</td><td class='form'>");
@@ -676,111 +758,113 @@ static void print_header(struct cgit_context *ctx)
676 for_each_branch_ref(print_branch_option, ctx->qry.head); 758 for_each_branch_ref(print_branch_option, ctx->qry.head);
677 html("</select> "); 759 html("</select> ");
678 html("<input type='submit' name='' value='switch'/>"); 760 html("<input type='submit' name='' value='switch'/>");
679 html("</form>"); 761 html("</form>");
680 } else 762 } else
681 html_txt(ctx->cfg.root_title); 763 html_txt(ctx->cfg.root_title);
682 html("</td></tr>\n"); 764 html("</td></tr>\n");
683 765
684 html("<tr><td class='sub'>"); 766 html("<tr><td class='sub'>");
685 if (ctx->repo) { 767 if (ctx->repo) {
686 html_txt(ctx->repo->desc); 768 html_txt(ctx->repo->desc);
687 html("</td><td class='sub right'>"); 769 html("</td><td class='sub right'>");
688 html_txt(ctx->repo->owner); 770 html_txt(ctx->repo->owner);
689 } else { 771 } else {
690 if (ctx->cfg.root_desc) 772 if (ctx->cfg.root_desc)
691 html_txt(ctx->cfg.root_desc); 773 html_txt(ctx->cfg.root_desc);
692 else if (ctx->cfg.index_info) 774 else if (ctx->cfg.index_info)
693 html_include(ctx->cfg.index_info); 775 html_include(ctx->cfg.index_info);
694 } 776 }
695 html("</td></tr></table>\n"); 777 html("</td></tr></table>\n");
696} 778}
697 779
698void cgit_print_pageheader(struct cgit_context *ctx) 780void cgit_print_pageheader(struct cgit_context *ctx)
699{ 781{
700 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
701
702 if (!cmd && ctx->repo)
703 fallback_cmd = "summary";
704
705 html("<div id='cgit'>"); 782 html("<div id='cgit'>");
706 if (!ctx->cfg.noheader) 783 if (!ctx->cfg.noheader)
707 print_header(ctx); 784 print_header(ctx);
708 785
709 html("<table class='tabs'><tr><td>\n"); 786 html("<table class='tabs'><tr><td>\n");
710 if (ctx->repo) { 787 if (ctx->repo) {
711 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 788 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
712 ctx->qry.head); 789 ctx->qry.head);
713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 790 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
714 ctx->qry.sha1, NULL);
715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
718 ctx->qry.sha1, NULL); 791 ctx->qry.sha1, NULL);
719 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 792 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
720 ctx->qry.head, ctx->qry.sha1, 0); 793 NULL, ctx->qry.vpath, 0, NULL, NULL,
721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 794 ctx->qry.showmsg);
722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0); 795 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
796 ctx->qry.sha1, ctx->qry.vpath);
797 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
798 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
799 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
800 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
723 if (ctx->repo->max_stats) 801 if (ctx->repo->max_stats)
724 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 802 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
725 ctx->qry.head, NULL); 803 ctx->qry.head, ctx->qry.vpath);
726 if (ctx->repo->readme) 804 if (ctx->repo->readme)
727 reporevlink("about", "about", NULL, 805 reporevlink("about", "about", NULL,
728 hc(cmd, "about"), ctx->qry.head, NULL, 806 hc(ctx, "about"), ctx->qry.head, NULL,
729 NULL); 807 NULL);
730 html("</td><td class='form'>"); 808 html("</td><td class='form'>");
731 html("<form class='right' method='get' action='"); 809 html("<form class='right' method='get' action='");
732 if (ctx->cfg.virtual_root) 810 if (ctx->cfg.virtual_root)
733 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 811 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
734 ctx->qry.path, NULL)); 812 ctx->qry.vpath, NULL));
735 html("'>\n"); 813 html("'>\n");
736 cgit_add_hidden_formfields(1, 0, "log"); 814 cgit_add_hidden_formfields(1, 0, "log");
737 html("<select name='qt'>\n"); 815 html("<select name='qt'>\n");
738 html_option("grep", "log msg", ctx->qry.grep); 816 html_option("grep", "log msg", ctx->qry.grep);
739 html_option("author", "author", ctx->qry.grep); 817 html_option("author", "author", ctx->qry.grep);
740 html_option("committer", "committer", ctx->qry.grep); 818 html_option("committer", "committer", ctx->qry.grep);
741 html("</select>\n"); 819 html("</select>\n");
742 html("<input class='txt' type='text' size='10' name='q' value='"); 820 html("<input class='txt' type='text' size='10' name='q' value='");
743 html_attr(ctx->qry.search); 821 html_attr(ctx->qry.search);
744 html("'/>\n"); 822 html("'/>\n");
745 html("<input type='submit' value='search'/>\n"); 823 html("<input type='submit' value='search'/>\n");
746 html("</form>\n"); 824 html("</form>\n");
747 } else { 825 } else {
748 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 826 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
749 if (ctx->cfg.root_readme) 827 if (ctx->cfg.root_readme)
750 site_link("about", "about", NULL, hc(cmd, "about"), 828 site_link("about", "about", NULL, hc(ctx, "about"),
751 NULL, 0); 829 NULL, 0);
752 html("</td><td class='form'>"); 830 html("</td><td class='form'>");
753 html("<form method='get' action='"); 831 html("<form method='get' action='");
754 html_attr(cgit_rooturl()); 832 html_attr(cgit_rooturl());
755 html("'>\n"); 833 html("'>\n");
756 html("<input type='text' name='q' size='10' value='"); 834 html("<input type='text' name='q' size='10' value='");
757 html_attr(ctx->qry.search); 835 html_attr(ctx->qry.search);
758 html("'/>\n"); 836 html("'/>\n");
759 html("<input type='submit' value='search'/>\n"); 837 html("<input type='submit' value='search'/>\n");
760 html("</form>"); 838 html("</form>");
761 } 839 }
762 html("</td></tr></table>\n"); 840 html("</td></tr></table>\n");
841 if (ctx->qry.vpath) {
842 html("<div class='path'>");
843 html("path: ");
844 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
845 html("</div>");
846 }
763 html("<div class='content'>"); 847 html("<div class='content'>");
764} 848}
765 849
766void cgit_print_filemode(unsigned short mode) 850void cgit_print_filemode(unsigned short mode)
767{ 851{
768 if (S_ISDIR(mode)) 852 if (S_ISDIR(mode))
769 html("d"); 853 html("d");
770 else if (S_ISLNK(mode)) 854 else if (S_ISLNK(mode))
771 html("l"); 855 html("l");
772 else if (S_ISGITLINK(mode)) 856 else if (S_ISGITLINK(mode))
773 html("m"); 857 html("m");
774 else 858 else
775 html("-"); 859 html("-");
776 html_fileperm(mode >> 6); 860 html_fileperm(mode >> 6);
777 html_fileperm(mode >> 3); 861 html_fileperm(mode >> 3);
778 html_fileperm(mode); 862 html_fileperm(mode);
779} 863}
780 864
781void cgit_print_snapshot_links(const char *repo, const char *head, 865void cgit_print_snapshot_links(const char *repo, const char *head,
782 const char *hex, int snapshots) 866 const char *hex, int snapshots)
783{ 867{
784 const struct cgit_snapshot_format* f; 868 const struct cgit_snapshot_format* f;
785 char *prefix; 869 char *prefix;
786 char *filename; 870 char *filename;
diff --git a/ui-shared.h b/ui-shared.h
index 9ebc1f9..3cc1258 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,52 +1,66 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_rooturl(); 6extern char *cgit_rooturl();
7extern char *cgit_repourl(const char *reponame); 7extern char *cgit_repourl(const char *reponame);
8extern char *cgit_fileurl(const char *reponame, const char *pagename, 8extern char *cgit_fileurl(const char *reponame, const char *pagename,
9 const char *filename, const char *query); 9 const char *filename, const char *query);
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(const char *name, const char *title,
14 char *pattern, int ofs); 14 const char *class, const char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(const char *name, const char *title,
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16 const char *class, const char *head);
17 char *rev); 17extern void cgit_tag_link(const char *name, const char *title,
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18 const char *class, const char *head,
19 char *rev, char *path); 19 const char *rev);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_tree_link(const char *name, const char *title,
21 char *rev, char *path); 21 const char *class, const char *head,
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22 const char *rev, const char *path);
23 char *rev, char *path, int ofs, char *grep, 23extern void cgit_plain_link(const char *name, const char *title,
24 char *pattern, int showmsg); 24 const char *class, const char *head,
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25 const char *rev, const char *path);
26 char *rev, int toggle_ssdiff); 26extern void cgit_log_link(const char *name, const char *title,
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27 const char *class, const char *head, const char *rev,
28 char *rev); 28 const char *path, int ofs, const char *grep,
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29 const char *pattern, int showmsg);
30 char *rev, char *path); 30extern void cgit_commit_link(char *name, const char *title,
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31 const char *class, const char *head,
32 char *head, char *rev, char *archivename); 32 const char *rev, const char *path,
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33 int toggle_ssdiff);
34 char *new_rev, char *old_rev, char *path, 34extern void cgit_patch_link(const char *name, const char *title,
35 int toggle_ssdiff); 35 const char *class, const char *head,
36extern void cgit_stats_link(char *name, char *title, char *class, char *head, 36 const char *rev, const char *path);
37 char *path); 37extern void cgit_refs_link(const char *name, const char *title,
38 const char *class, const char *head,
39 const char *rev, const char *path);
40extern void cgit_snapshot_link(const char *name, const char *title,
41 const char *class, const char *head,
42 const char *rev, const char *archivename);
43extern void cgit_diff_link(const char *name, const char *title,
44 const char *class, const char *head,
45 const char *new_rev, const char *old_rev,
46 const char *path, int toggle_ssdiff);
47extern void cgit_stats_link(const char *name, const char *title,
48 const char *class, const char *head,
49 const char *path);
50extern void cgit_self_link(char *name, const char *title,
51 const char *class, struct cgit_context *ctx);
38extern void cgit_object_link(struct object *obj); 52extern void cgit_object_link(struct object *obj);
39 53
40extern void cgit_print_error(char *msg); 54extern void cgit_print_error(const char *msg);
41extern void cgit_print_date(time_t secs, char *format, int local_time); 55extern void cgit_print_date(time_t secs, const char *format, int local_time);
42extern void cgit_print_age(time_t t, time_t max_relative, char *format); 56extern void cgit_print_age(time_t t, time_t max_relative, const char *format);
43extern void cgit_print_http_headers(struct cgit_context *ctx); 57extern void cgit_print_http_headers(struct cgit_context *ctx);
44extern void cgit_print_docstart(struct cgit_context *ctx); 58extern void cgit_print_docstart(struct cgit_context *ctx);
45extern void cgit_print_docend(); 59extern void cgit_print_docend();
46extern void cgit_print_pageheader(struct cgit_context *ctx); 60extern void cgit_print_pageheader(struct cgit_context *ctx);
47extern void cgit_print_filemode(unsigned short mode); 61extern void cgit_print_filemode(unsigned short mode);
48extern void cgit_print_snapshot_links(const char *repo, const char *head, 62extern void cgit_print_snapshot_links(const char *repo, const char *head,
49 const char *hex, int snapshots); 63 const char *hex, int snapshots);
50extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 64extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
51 char *page); 65 const char *page);
52#endif /* UI_SHARED_H */ 66#endif /* UI_SHARED_H */
diff --git a/ui-tree.c b/ui-tree.c
index 0ee38f2..75ec9cb 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -81,52 +81,52 @@ static void print_binary_buffer(char *buf, unsigned long size)
81 } 81 }
82 html("</table>\n"); 82 html("</table>\n");
83} 83}
84 84
85static void print_object(const unsigned char *sha1, char *path, const char *basename) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
86{ 86{
87 enum object_type type; 87 enum object_type type;
88 char *buf; 88 char *buf;
89 unsigned long size; 89 unsigned long size;
90 90
91 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
92 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
93 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
98 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
99 if (!buf) { 99 if (!buf) {
100 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
101 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
102 return; 102 return;
103 } 103 }
104 104
105 html(" ("); 105 htmlf("blob: %s (", sha1_to_hex(sha1));
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 html(")\n");
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { 110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>", 111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size); 112 size / 1024, ctx.cfg.max_blob_size);
113 return; 113 return;
114 } 114 }
115 115
116 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
117 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
118 else 118 else
119 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
120} 120}
121 121
122 122
123static int ls_item(const unsigned char *sha1, const char *base, int baselen, 123static int ls_item(const unsigned char *sha1, const char *base, int baselen,
124 const char *pathname, unsigned int mode, int stage, 124 const char *pathname, unsigned int mode, int stage,
125 void *cbdata) 125 void *cbdata)
126{ 126{
127 char *name; 127 char *name;
128 char *fullpath; 128 char *fullpath;
129 char *class; 129 char *class;
130 enum object_type type; 130 enum object_type type;
131 unsigned long size = 0; 131 unsigned long size = 0;
132 132
@@ -204,90 +204,79 @@ static void ls_tail()
204 204
205static void ls_tree(const unsigned char *sha1, char *path) 205static void ls_tree(const unsigned char *sha1, char *path)
206{ 206{
207 struct tree *tree; 207 struct tree *tree;
208 208
209 tree = parse_tree_indirect(sha1); 209 tree = parse_tree_indirect(sha1);
210 if (!tree) { 210 if (!tree) {
211 cgit_print_error(fmt("Not a tree object: %s", 211 cgit_print_error(fmt("Not a tree object: %s",
212 sha1_to_hex(sha1))); 212 sha1_to_hex(sha1)));
213 return; 213 return;
214 } 214 }
215 215
216 ls_head(); 216 ls_head();
217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
218 ls_tail(); 218 ls_tail();
219} 219}
220 220
221 221
222static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 222static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
223 const char *pathname, unsigned mode, int stage, 223 const char *pathname, unsigned mode, int stage,
224 void *cbdata) 224 void *cbdata)
225{ 225{
226 static int state; 226 static int state;
227 static char buffer[PATH_MAX]; 227 static char buffer[PATH_MAX];
228 char *url;
229 228
230 if (state == 0) { 229 if (state == 0) {
231 memcpy(buffer, base, baselen); 230 memcpy(buffer, base, baselen);
232 strcpy(buffer+baselen, pathname); 231 strcpy(buffer+baselen, pathname);
233 url = cgit_pageurl(ctx.qry.repo, "tree",
234 fmt("h=%s&amp;path=%s", curr_rev, buffer));
235 html("/");
236 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
237 curr_rev, buffer);
238
239 if (strcmp(match_path, buffer)) 232 if (strcmp(match_path, buffer))
240 return READ_TREE_RECURSIVE; 233 return READ_TREE_RECURSIVE;
241 234
242 if (S_ISDIR(mode)) { 235 if (S_ISDIR(mode)) {
243 state = 1; 236 state = 1;
244 ls_head(); 237 ls_head();
245 return READ_TREE_RECURSIVE; 238 return READ_TREE_RECURSIVE;
246 } else { 239 } else {
247 print_object(sha1, buffer, pathname); 240 print_object(sha1, buffer, pathname);
248 return 0; 241 return 0;
249 } 242 }
250 } 243 }
251 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 244 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
252 return 0; 245 return 0;
253} 246}
254 247
255 248
256/* 249/*
257 * Show a tree or a blob 250 * Show a tree or a blob
258 * rev: the commit pointing at the root tree object 251 * rev: the commit pointing at the root tree object
259 * path: path to tree or blob 252 * path: path to tree or blob
260 */ 253 */
261void cgit_print_tree(const char *rev, char *path) 254void cgit_print_tree(const char *rev, char *path)
262{ 255{
263 unsigned char sha1[20]; 256 unsigned char sha1[20];
264 struct commit *commit; 257 struct commit *commit;
265 const char *paths[] = {path, NULL}; 258 const char *paths[] = {path, NULL};
266 259
267 if (!rev) 260 if (!rev)
268 rev = ctx.qry.head; 261 rev = ctx.qry.head;
269 262
270 curr_rev = xstrdup(rev); 263 curr_rev = xstrdup(rev);
271 if (get_sha1(rev, sha1)) { 264 if (get_sha1(rev, sha1)) {
272 cgit_print_error(fmt("Invalid revision name: %s", rev)); 265 cgit_print_error(fmt("Invalid revision name: %s", rev));
273 return; 266 return;
274 } 267 }
275 commit = lookup_commit_reference(sha1); 268 commit = lookup_commit_reference(sha1);
276 if (!commit || parse_commit(commit)) { 269 if (!commit || parse_commit(commit)) {
277 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 270 cgit_print_error(fmt("Invalid commit reference: %s", rev));
278 return; 271 return;
279 } 272 }
280 273
281 html("path: <a href='");
282 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
283 html("'>root</a>");
284
285 if (path == NULL) { 274 if (path == NULL) {
286 ls_tree(commit->tree->object.sha1, NULL); 275 ls_tree(commit->tree->object.sha1, NULL);
287 return; 276 return;
288 } 277 }
289 278
290 match_path = path; 279 match_path = path;
291 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 280 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
292 ls_tail(); 281 ls_tail();
293} 282}