summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c6
-rw-r--r--cgit.css1
-rw-r--r--cgit.h11
-rw-r--r--cgitrc5
-rw-r--r--shared.c11
-rw-r--r--ui-commit.c9
-rw-r--r--ui-shared.c41
-rw-r--r--ui-snapshot.c150
-rw-r--r--ui-tree.c4
9 files changed, 194 insertions, 44 deletions
diff --git a/cgit.c b/cgit.c
index 4b91829..6597529 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,254 +1,256 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11static int cgit_prepare_cache(struct cacheitem *item) 11static int cgit_prepare_cache(struct cacheitem *item)
12{ 12{
13 if (!cgit_repo && cgit_query_repo) { 13 if (!cgit_repo && cgit_query_repo) {
14 char *title = fmt("%s - %s", cgit_root_title, "Bad request"); 14 char *title = fmt("%s - %s", cgit_root_title, "Bad request");
15 cgit_print_docstart(title, item); 15 cgit_print_docstart(title, item);
16 cgit_print_pageheader(title, 0); 16 cgit_print_pageheader(title, 0);
17 cgit_print_error(fmt("Unknown repo: %s", cgit_query_repo)); 17 cgit_print_error(fmt("Unknown repo: %s", cgit_query_repo));
18 cgit_print_docend(); 18 cgit_print_docend();
19 return 0; 19 return 0;
20 } 20 }
21 21
22 if (!cgit_repo) { 22 if (!cgit_repo) {
23 item->name = xstrdup(fmt("%s/index.html", cgit_cache_root)); 23 item->name = xstrdup(fmt("%s/index.html", cgit_cache_root));
24 item->ttl = cgit_cache_root_ttl; 24 item->ttl = cgit_cache_root_ttl;
25 return 1; 25 return 1;
26 } 26 }
27 27
28 if (!cgit_cmd) { 28 if (!cgit_cmd) {
29 item->name = xstrdup(fmt("%s/%s/index.%s.html", cgit_cache_root, 29 item->name = xstrdup(fmt("%s/%s/index.%s.html", cgit_cache_root,
30 cache_safe_filename(cgit_repo->url), 30 cache_safe_filename(cgit_repo->url),
31 cache_safe_filename(cgit_querystring))); 31 cache_safe_filename(cgit_querystring)));
32 item->ttl = cgit_cache_repo_ttl; 32 item->ttl = cgit_cache_repo_ttl;
33 } else { 33 } else {
34 item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root, 34 item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root,
35 cache_safe_filename(cgit_repo->url), 35 cache_safe_filename(cgit_repo->url),
36 cgit_query_page, 36 cgit_query_page,
37 cache_safe_filename(cgit_querystring))); 37 cache_safe_filename(cgit_querystring)));
38 if (cgit_query_has_symref) 38 if (cgit_query_has_symref)
39 item->ttl = cgit_cache_dynamic_ttl; 39 item->ttl = cgit_cache_dynamic_ttl;
40 else if (cgit_query_has_sha1) 40 else if (cgit_query_has_sha1)
41 item->ttl = cgit_cache_static_ttl; 41 item->ttl = cgit_cache_static_ttl;
42 else 42 else
43 item->ttl = cgit_cache_repo_ttl; 43 item->ttl = cgit_cache_repo_ttl;
44 } 44 }
45 return 1; 45 return 1;
46} 46}
47 47
48static void cgit_print_repo_page(struct cacheitem *item) 48static void cgit_print_repo_page(struct cacheitem *item)
49{ 49{
50 char *title; 50 char *title;
51 int show_search; 51 int show_search;
52 52
53 if (!cgit_query_head) 53 if (!cgit_query_head)
54 cgit_query_head = cgit_repo->defbranch; 54 cgit_query_head = cgit_repo->defbranch;
55 55
56 if (chdir(cgit_repo->path)) { 56 if (chdir(cgit_repo->path)) {
57 title = fmt("%s - %s", cgit_root_title, "Bad request"); 57 title = fmt("%s - %s", cgit_root_title, "Bad request");
58 cgit_print_docstart(title, item); 58 cgit_print_docstart(title, item);
59 cgit_print_pageheader(title, 0); 59 cgit_print_pageheader(title, 0);
60 cgit_print_error(fmt("Unable to scan repository: %s", 60 cgit_print_error(fmt("Unable to scan repository: %s",
61 strerror(errno))); 61 strerror(errno)));
62 cgit_print_docend(); 62 cgit_print_docend();
63 return; 63 return;
64 } 64 }
65 65
66 title = fmt("%s - %s", cgit_repo->name, cgit_repo->desc); 66 title = fmt("%s - %s", cgit_repo->name, cgit_repo->desc);
67 show_search = 0; 67 show_search = 0;
68 setenv("GIT_DIR", cgit_repo->path, 1); 68 setenv("GIT_DIR", cgit_repo->path, 1);
69 69
70 if ((cgit_cmd == CMD_SNAPSHOT) && cgit_repo->snapshots) { 70 if ((cgit_cmd == CMD_SNAPSHOT) && cgit_repo->snapshots) {
71 cgit_print_snapshot(item, cgit_query_sha1, "zip", 71 cgit_print_snapshot(item, cgit_query_sha1,
72 cgit_repo->url, cgit_query_name); 72 cgit_repobasename(cgit_repo->url),
73 cgit_query_name,
74 cgit_repo->snapshots );
73 return; 75 return;
74 } 76 }
75 77
76 if (cgit_cmd == CMD_BLOB) { 78 if (cgit_cmd == CMD_BLOB) {
77 cgit_print_blob(item, cgit_query_sha1, cgit_query_path); 79 cgit_print_blob(item, cgit_query_sha1, cgit_query_path);
78 return; 80 return;
79 } 81 }
80 82
81 show_search = (cgit_cmd == CMD_LOG); 83 show_search = (cgit_cmd == CMD_LOG);
82 cgit_print_docstart(title, item); 84 cgit_print_docstart(title, item);
83 if (!cgit_cmd) { 85 if (!cgit_cmd) {
84 cgit_print_pageheader("summary", show_search); 86 cgit_print_pageheader("summary", show_search);
85 cgit_print_summary(); 87 cgit_print_summary();
86 cgit_print_docend(); 88 cgit_print_docend();
87 return; 89 return;
88 } 90 }
89 91
90 cgit_print_pageheader(cgit_query_page, show_search); 92 cgit_print_pageheader(cgit_query_page, show_search);
91 93
92 switch(cgit_cmd) { 94 switch(cgit_cmd) {
93 case CMD_LOG: 95 case CMD_LOG:
94 cgit_print_log(cgit_query_sha1, cgit_query_ofs, 96 cgit_print_log(cgit_query_sha1, cgit_query_ofs,
95 cgit_max_commit_count, cgit_query_search, 97 cgit_max_commit_count, cgit_query_search,
96 cgit_query_path, 1); 98 cgit_query_path, 1);
97 break; 99 break;
98 case CMD_TREE: 100 case CMD_TREE:
99 cgit_print_tree(cgit_query_sha1, cgit_query_path); 101 cgit_print_tree(cgit_query_sha1, cgit_query_path);
100 break; 102 break;
101 case CMD_COMMIT: 103 case CMD_COMMIT:
102 cgit_print_commit(cgit_query_sha1); 104 cgit_print_commit(cgit_query_sha1);
103 break; 105 break;
104 case CMD_TAG: 106 case CMD_TAG:
105 cgit_print_tag(cgit_query_sha1); 107 cgit_print_tag(cgit_query_sha1);
106 break; 108 break;
107 case CMD_DIFF: 109 case CMD_DIFF:
108 cgit_print_diff(cgit_query_sha1, cgit_query_sha2); 110 cgit_print_diff(cgit_query_sha1, cgit_query_sha2);
109 break; 111 break;
110 default: 112 default:
111 cgit_print_error("Invalid request"); 113 cgit_print_error("Invalid request");
112 } 114 }
113 cgit_print_docend(); 115 cgit_print_docend();
114} 116}
115 117
116static void cgit_fill_cache(struct cacheitem *item, int use_cache) 118static void cgit_fill_cache(struct cacheitem *item, int use_cache)
117{ 119{
118 static char buf[PATH_MAX]; 120 static char buf[PATH_MAX];
119 int stdout2; 121 int stdout2;
120 122
121 getcwd(buf, sizeof(buf)); 123 getcwd(buf, sizeof(buf));
122 item->st.st_mtime = time(NULL); 124 item->st.st_mtime = time(NULL);
123 125
124 if (use_cache) { 126 if (use_cache) {
125 stdout2 = chk_positive(dup(STDOUT_FILENO), 127 stdout2 = chk_positive(dup(STDOUT_FILENO),
126 "Preserving STDOUT"); 128 "Preserving STDOUT");
127 chk_zero(close(STDOUT_FILENO), "Closing STDOUT"); 129 chk_zero(close(STDOUT_FILENO), "Closing STDOUT");
128 chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)"); 130 chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)");
129 } 131 }
130 132
131 if (cgit_repo) 133 if (cgit_repo)
132 cgit_print_repo_page(item); 134 cgit_print_repo_page(item);
133 else 135 else
134 cgit_print_repolist(item); 136 cgit_print_repolist(item);
135 137
136 if (use_cache) { 138 if (use_cache) {
137 chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT"); 139 chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT");
138 chk_positive(dup2(stdout2, STDOUT_FILENO), 140 chk_positive(dup2(stdout2, STDOUT_FILENO),
139 "Restoring original STDOUT"); 141 "Restoring original STDOUT");
140 chk_zero(close(stdout2), "Closing temporary STDOUT"); 142 chk_zero(close(stdout2), "Closing temporary STDOUT");
141 } 143 }
142 144
143 chdir(buf); 145 chdir(buf);
144} 146}
145 147
146static void cgit_check_cache(struct cacheitem *item) 148static void cgit_check_cache(struct cacheitem *item)
147{ 149{
148 int i = 0; 150 int i = 0;
149 151
150 top: 152 top:
151 if (++i > cgit_max_lock_attempts) { 153 if (++i > cgit_max_lock_attempts) {
152 die("cgit_refresh_cache: unable to lock %s: %s", 154 die("cgit_refresh_cache: unable to lock %s: %s",
153 item->name, strerror(errno)); 155 item->name, strerror(errno));
154 } 156 }
155 if (!cache_exist(item)) { 157 if (!cache_exist(item)) {
156 if (!cache_lock(item)) { 158 if (!cache_lock(item)) {
157 sleep(1); 159 sleep(1);
158 goto top; 160 goto top;
159 } 161 }
160 if (!cache_exist(item)) { 162 if (!cache_exist(item)) {
161 cgit_fill_cache(item, 1); 163 cgit_fill_cache(item, 1);
162 cache_unlock(item); 164 cache_unlock(item);
163 } else { 165 } else {
164 cache_cancel_lock(item); 166 cache_cancel_lock(item);
165 } 167 }
166 } else if (cache_expired(item) && cache_lock(item)) { 168 } else if (cache_expired(item) && cache_lock(item)) {
167 if (cache_expired(item)) { 169 if (cache_expired(item)) {
168 cgit_fill_cache(item, 1); 170 cgit_fill_cache(item, 1);
169 cache_unlock(item); 171 cache_unlock(item);
170 } else { 172 } else {
171 cache_cancel_lock(item); 173 cache_cancel_lock(item);
172 } 174 }
173 } 175 }
174} 176}
175 177
176static void cgit_print_cache(struct cacheitem *item) 178static void cgit_print_cache(struct cacheitem *item)
177{ 179{
178 static char buf[4096]; 180 static char buf[4096];
179 ssize_t i; 181 ssize_t i;
180 182
181 int fd = open(item->name, O_RDONLY); 183 int fd = open(item->name, O_RDONLY);
182 if (fd<0) 184 if (fd<0)
183 die("Unable to open cached file %s", item->name); 185 die("Unable to open cached file %s", item->name);
184 186
185 while((i=read(fd, buf, sizeof(buf))) > 0) 187 while((i=read(fd, buf, sizeof(buf))) > 0)
186 write(STDOUT_FILENO, buf, i); 188 write(STDOUT_FILENO, buf, i);
187 189
188 close(fd); 190 close(fd);
189} 191}
190 192
191static void cgit_parse_args(int argc, const char **argv) 193static void cgit_parse_args(int argc, const char **argv)
192{ 194{
193 int i; 195 int i;
194 196
195 for (i = 1; i < argc; i++) { 197 for (i = 1; i < argc; i++) {
196 if (!strncmp(argv[i], "--cache=", 8)) { 198 if (!strncmp(argv[i], "--cache=", 8)) {
197 cgit_cache_root = xstrdup(argv[i]+8); 199 cgit_cache_root = xstrdup(argv[i]+8);
198 } 200 }
199 if (!strcmp(argv[i], "--nocache")) { 201 if (!strcmp(argv[i], "--nocache")) {
200 cgit_nocache = 1; 202 cgit_nocache = 1;
201 } 203 }
202 if (!strncmp(argv[i], "--query=", 8)) { 204 if (!strncmp(argv[i], "--query=", 8)) {
203 cgit_querystring = xstrdup(argv[i]+8); 205 cgit_querystring = xstrdup(argv[i]+8);
204 } 206 }
205 if (!strncmp(argv[i], "--repo=", 7)) { 207 if (!strncmp(argv[i], "--repo=", 7)) {
206 cgit_query_repo = xstrdup(argv[i]+7); 208 cgit_query_repo = xstrdup(argv[i]+7);
207 } 209 }
208 if (!strncmp(argv[i], "--page=", 7)) { 210 if (!strncmp(argv[i], "--page=", 7)) {
209 cgit_query_page = xstrdup(argv[i]+7); 211 cgit_query_page = xstrdup(argv[i]+7);
210 } 212 }
211 if (!strncmp(argv[i], "--head=", 7)) { 213 if (!strncmp(argv[i], "--head=", 7)) {
212 cgit_query_head = xstrdup(argv[i]+7); 214 cgit_query_head = xstrdup(argv[i]+7);
213 cgit_query_has_symref = 1; 215 cgit_query_has_symref = 1;
214 } 216 }
215 if (!strncmp(argv[i], "--sha1=", 7)) { 217 if (!strncmp(argv[i], "--sha1=", 7)) {
216 cgit_query_sha1 = xstrdup(argv[i]+7); 218 cgit_query_sha1 = xstrdup(argv[i]+7);
217 cgit_query_has_sha1 = 1; 219 cgit_query_has_sha1 = 1;
218 } 220 }
219 if (!strncmp(argv[i], "--ofs=", 6)) { 221 if (!strncmp(argv[i], "--ofs=", 6)) {
220 cgit_query_ofs = atoi(argv[i]+6); 222 cgit_query_ofs = atoi(argv[i]+6);
221 } 223 }
222 } 224 }
223} 225}
224 226
225int main(int argc, const char **argv) 227int main(int argc, const char **argv)
226{ 228{
227 struct cacheitem item; 229 struct cacheitem item;
228 const char *cgit_config_env = getenv("CGIT_CONFIG"); 230 const char *cgit_config_env = getenv("CGIT_CONFIG");
229 231
230 htmlfd = STDOUT_FILENO; 232 htmlfd = STDOUT_FILENO;
231 item.st.st_mtime = time(NULL); 233 item.st.st_mtime = time(NULL);
232 cgit_repolist.length = 0; 234 cgit_repolist.length = 0;
233 cgit_repolist.count = 0; 235 cgit_repolist.count = 0;
234 cgit_repolist.repos = NULL; 236 cgit_repolist.repos = NULL;
235 237
236 cgit_read_config(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 238 cgit_read_config(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
237 cgit_global_config_cb); 239 cgit_global_config_cb);
238 cgit_repo = NULL; 240 cgit_repo = NULL;
239 if (getenv("SCRIPT_NAME")) 241 if (getenv("SCRIPT_NAME"))
240 cgit_script_name = xstrdup(getenv("SCRIPT_NAME")); 242 cgit_script_name = xstrdup(getenv("SCRIPT_NAME"));
241 if (getenv("QUERY_STRING")) 243 if (getenv("QUERY_STRING"))
242 cgit_querystring = xstrdup(getenv("QUERY_STRING")); 244 cgit_querystring = xstrdup(getenv("QUERY_STRING"));
243 cgit_parse_args(argc, argv); 245 cgit_parse_args(argc, argv);
244 cgit_parse_query(cgit_querystring, cgit_querystring_cb); 246 cgit_parse_query(cgit_querystring, cgit_querystring_cb);
245 if (!cgit_prepare_cache(&item)) 247 if (!cgit_prepare_cache(&item))
246 return 0; 248 return 0;
247 if (cgit_nocache) { 249 if (cgit_nocache) {
248 cgit_fill_cache(&item, 0); 250 cgit_fill_cache(&item, 0);
249 } else { 251 } else {
250 cgit_check_cache(&item); 252 cgit_check_cache(&item);
251 cgit_print_cache(&item); 253 cgit_print_cache(&item);
252 } 254 }
253 return 0; 255 return 0;
254} 256}
diff --git a/cgit.css b/cgit.css
index 112dac1..43a40a3 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,426 +1,427 @@
1body { 1body {
2 font-family: arial, sans-serif; 2 font-family: arial, sans-serif;
3 font-size: 11pt; 3 font-size: 11pt;
4 color: black; 4 color: black;
5 background: white; 5 background: white;
6} 6}
7 7
8body, table { 8body, table {
9 padding: 0em; 9 padding: 0em;
10 margin: 0em; 10 margin: 0em;
11} 11}
12 12
13table { 13table {
14 border-collapse: collapse; 14 border-collapse: collapse;
15} 15}
16 16
17h2 { 17h2 {
18 font-size: 120%; 18 font-size: 120%;
19 font-weight: bold; 19 font-weight: bold;
20 margin-top: 0em; 20 margin-top: 0em;
21 margin-bottom: 0.25em; 21 margin-bottom: 0.25em;
22} 22}
23 23
24h3 { 24h3 {
25 margin-top: 0em; 25 margin-top: 0em;
26 font-size: 100%; 26 font-size: 100%;
27 font-weight: normal; 27 font-weight: normal;
28} 28}
29 29
30h4 { 30h4 {
31 margin-top: 1.5em; 31 margin-top: 1.5em;
32 margin-bottom: 0.1em; 32 margin-bottom: 0.1em;
33 font-size: 100%; 33 font-size: 100%;
34 font-weight: bold; 34 font-weight: bold;
35} 35}
36 36
37a { 37a {
38 color: blue; 38 color: blue;
39 text-decoration: none; 39 text-decoration: none;
40} 40}
41 41
42a:hover { 42a:hover {
43 text-decoration: underline; 43 text-decoration: underline;
44} 44}
45 45
46table.list { 46table.list {
47 border: none; 47 border: none;
48 border-collapse: collapse; 48 border-collapse: collapse;
49} 49}
50 50
51table.list tr { 51table.list tr {
52 background: white; 52 background: white;
53} 53}
54 54
55table.list tr:hover { 55table.list tr:hover {
56 background: #eee; 56 background: #eee;
57} 57}
58 58
59table.list tr.nohover:hover { 59table.list tr.nohover:hover {
60 background: white; 60 background: white;
61} 61}
62 62
63table.list th { 63table.list th {
64 font-weight: bold; 64 font-weight: bold;
65 border-bottom: solid 1px #777; 65 border-bottom: solid 1px #777;
66 padding: 0.1em 0.5em 0.1em 0.5em; 66 padding: 0.1em 0.5em 0.1em 0.5em;
67 vertical-align: baseline; 67 vertical-align: baseline;
68} 68}
69 69
70table.list td { 70table.list td {
71 border: none; 71 border: none;
72 padding: 0.1em 0.5em 0.1em 0.5em; 72 padding: 0.1em 0.5em 0.1em 0.5em;
73} 73}
74 74
75img { 75img {
76 border: none; 76 border: none;
77} 77}
78 78
79table#layout { 79table#layout {
80 width: 100%; 80 width: 100%;
81 border-collapse: collapse; 81 border-collapse: collapse;
82 margin: 0px; 82 margin: 0px;
83} 83}
84 84
85td#header, td#logo { 85td#header, td#logo {
86 color: #666; 86 color: #666;
87 background-color: #ddd; 87 background-color: #ddd;
88 border-bottom: solid 1px #000; 88 border-bottom: solid 1px #000;
89} 89}
90 90
91td#header { 91td#header {
92 font-size: 150%; 92 font-size: 150%;
93 font-weight: bold; 93 font-weight: bold;
94 padding: 0.2em 0.5em; 94 padding: 0.2em 0.5em;
95 vertical-align: text-bottom; 95 vertical-align: text-bottom;
96} 96}
97 97
98td#header a { 98td#header a {
99 color: #666; 99 color: #666;
100} 100}
101 101
102td#header a:hoved { 102td#header a:hoved {
103 text-decoration: underline; 103 text-decoration: underline;
104} 104}
105 105
106td#logo { 106td#logo {
107 text-align: right; 107 text-align: right;
108 vertical-align: middle; 108 vertical-align: middle;
109 padding-right: 0.5em; 109 padding-right: 0.5em;
110} 110}
111 111
112td#crumb, td#search { 112td#crumb, td#search {
113 color: #ccc; 113 color: #ccc;
114 border-top: solid 3px #555; 114 border-top: solid 3px #555;
115 background-color: #666; 115 background-color: #666;
116 border-bottom: solid 1px #333; 116 border-bottom: solid 1px #333;
117 padding: 2px 1em; 117 padding: 2px 1em;
118} 118}
119 119
120td#crumb { 120td#crumb {
121 font-weight: bold; 121 font-weight: bold;
122} 122}
123 123
124td#crumb a { 124td#crumb a {
125 color: #ccc; 125 color: #ccc;
126 background-color: #666; 126 background-color: #666;
127 padding: 0em 0.5em 0em 0.5em; 127 padding: 0em 0.5em 0em 0.5em;
128} 128}
129 129
130td#crumb a:hover { 130td#crumb a:hover {
131 color: #666; 131 color: #666;
132 background-color: #ccc; 132 background-color: #ccc;
133 text-decoration: none; 133 text-decoration: none;
134} 134}
135 135
136td#search { 136td#search {
137 text-align: right; 137 text-align: right;
138 vertical-align: middle; 138 vertical-align: middle;
139 padding-right: 0.5em; 139 padding-right: 0.5em;
140} 140}
141 141
142td#search form { 142td#search form {
143 margin: 0px; 143 margin: 0px;
144 padding: 0px; 144 padding: 0px;
145} 145}
146 146
147td#search input { 147td#search input {
148 font-size: 9pt; 148 font-size: 9pt;
149 padding: 0px; 149 padding: 0px;
150 width: 10em; 150 width: 10em;
151 border: solid 1px #333; 151 border: solid 1px #333;
152 color: #333; 152 color: #333;
153 background-color: #fff; 153 background-color: #fff;
154} 154}
155 155
156div#summary { 156div#summary {
157 vertical-align: top; 157 vertical-align: top;
158 margin-bottom: 1em; 158 margin-bottom: 1em;
159} 159}
160 160
161table#downloads { 161table#downloads {
162 float: right; 162 float: right;
163 border-collapse: collapse; 163 border-collapse: collapse;
164 border: solid 1px #777; 164 border: solid 1px #777;
165 margin-left: 0.5em; 165 margin-left: 0.5em;
166 margin-bottom: 0.5em; 166 margin-bottom: 0.5em;
167} 167}
168 168
169table#downloads th { 169table#downloads th {
170 background-color: #ccc; 170 background-color: #ccc;
171} 171}
172 172
173td#content { 173td#content {
174 padding: 1em 0.5em; 174 padding: 1em 0.5em;
175} 175}
176 176
177div#blob { 177div#blob {
178 border: solid 1px black; 178 border: solid 1px black;
179} 179}
180 180
181div.error { 181div.error {
182 color: red; 182 color: red;
183 font-weight: bold; 183 font-weight: bold;
184 margin: 1em 2em; 184 margin: 1em 2em;
185} 185}
186 186
187a.ls-blob, a.ls-dir, a.ls-mod { 187a.ls-blob, a.ls-dir, a.ls-mod {
188 font-family: monospace; 188 font-family: monospace;
189} 189}
190 190
191td.ls-size { 191td.ls-size {
192 text-align: right; 192 text-align: right;
193} 193}
194 194
195td.ls-size { 195td.ls-size {
196 font-family: monospace; 196 font-family: monospace;
197} 197}
198 198
199td.ls-mode { 199td.ls-mode {
200 font-family: monospace; 200 font-family: monospace;
201} 201}
202 202
203table.blob { 203table.blob {
204 margin-top: 0.5em; 204 margin-top: 0.5em;
205 border-top: solid 1px black; 205 border-top: solid 1px black;
206} 206}
207 207
208table.blob td.no { 208table.blob td.no {
209 border-right: solid 1px black; 209 border-right: solid 1px black;
210 color: black; 210 color: black;
211 background-color: #eee; 211 background-color: #eee;
212 text-align: right; 212 text-align: right;
213} 213}
214 214
215table.blob td.txt { 215table.blob td.txt {
216 white-space: pre; 216 white-space: pre;
217 font-family: monospace; 217 font-family: monospace;
218 padding-left: 0.5em; 218 padding-left: 0.5em;
219} 219}
220 220
221table.nowrap td { 221table.nowrap td {
222 white-space: nowrap; 222 white-space: nowrap;
223} 223}
224 224
225table.commit-info { 225table.commit-info {
226 border-collapse: collapse; 226 border-collapse: collapse;
227 margin-top: 1.5em; 227 margin-top: 1.5em;
228} 228}
229 229
230table.commit-info th { 230table.commit-info th {
231 text-align: left; 231 text-align: left;
232 font-weight: normal; 232 font-weight: normal;
233 padding: 0.1em 1em 0.1em 0.1em; 233 padding: 0.1em 1em 0.1em 0.1em;
234 vertical-align: top;
234} 235}
235 236
236table.commit-info td { 237table.commit-info td {
237 font-weight: normal; 238 font-weight: normal;
238 padding: 0.1em 1em 0.1em 0.1em; 239 padding: 0.1em 1em 0.1em 0.1em;
239} 240}
240 241
241div.commit-subject { 242div.commit-subject {
242 font-weight: bold; 243 font-weight: bold;
243 font-size: 125%; 244 font-size: 125%;
244 margin: 1.5em 0em 0.5em 0em; 245 margin: 1.5em 0em 0.5em 0em;
245 padding: 0em; 246 padding: 0em;
246} 247}
247 248
248div.commit-msg { 249div.commit-msg {
249 white-space: pre; 250 white-space: pre;
250 font-family: monospace; 251 font-family: monospace;
251} 252}
252 253
253div.diffstat-header { 254div.diffstat-header {
254 font-weight: bold; 255 font-weight: bold;
255 padding-top: 1.5em; 256 padding-top: 1.5em;
256} 257}
257 258
258table.diffstat { 259table.diffstat {
259 border-collapse: collapse; 260 border-collapse: collapse;
260 width: 100%; 261 width: 100%;
261 border: solid 1px #aaa; 262 border: solid 1px #aaa;
262 background-color: #eee; 263 background-color: #eee;
263} 264}
264 265
265table.diffstat tr:hover { 266table.diffstat tr:hover {
266 background-color: #ccc; 267 background-color: #ccc;
267} 268}
268 269
269table.diffstat th { 270table.diffstat th {
270 font-weight: normal; 271 font-weight: normal;
271 text-align: left; 272 text-align: left;
272 text-decoration: underline; 273 text-decoration: underline;
273 padding: 0.1em 1em 0.1em 0.1em; 274 padding: 0.1em 1em 0.1em 0.1em;
274 font-size: 100%; 275 font-size: 100%;
275} 276}
276 277
277table.diffstat td { 278table.diffstat td {
278 padding: 0.2em 0.2em 0.1em 0.1em; 279 padding: 0.2em 0.2em 0.1em 0.1em;
279 font-size: 100%; 280 font-size: 100%;
280 border: none; 281 border: none;
281} 282}
282 283
283table.diffstat td.mode { 284table.diffstat td.mode {
284 white-space: nowrap; 285 white-space: nowrap;
285} 286}
286 287
287table.diffstat td span.modechange { 288table.diffstat td span.modechange {
288 padding-left: 1em; 289 padding-left: 1em;
289 color: red; 290 color: red;
290} 291}
291 292
292table.diffstat td.add a { 293table.diffstat td.add a {
293 color: green; 294 color: green;
294} 295}
295 296
296table.diffstat td.del a { 297table.diffstat td.del a {
297 color: red; 298 color: red;
298} 299}
299 300
300table.diffstat td.upd a { 301table.diffstat td.upd a {
301 color: blue; 302 color: blue;
302} 303}
303 304
304table.diffstat td.graph { 305table.diffstat td.graph {
305 width: 75%; 306 width: 75%;
306 vertical-align: middle; 307 vertical-align: middle;
307} 308}
308 309
309table.diffstat td.graph table { 310table.diffstat td.graph table {
310 border: none; 311 border: none;
311} 312}
312 313
313table.diffstat td.graph td { 314table.diffstat td.graph td {
314 padding: 0px; 315 padding: 0px;
315 border: 0px; 316 border: 0px;
316 height: 7pt; 317 height: 7pt;
317} 318}
318 319
319table.diffstat td.graph td.add { 320table.diffstat td.graph td.add {
320 background-color: #5c5; 321 background-color: #5c5;
321} 322}
322 323
323table.diffstat td.graph td.rem { 324table.diffstat td.graph td.rem {
324 background-color: #c55; 325 background-color: #c55;
325} 326}
326 327
327div.diffstat-summary { 328div.diffstat-summary {
328 color: #888; 329 color: #888;
329 padding-top: 0.5em; 330 padding-top: 0.5em;
330} 331}
331 332
332table.diff td { 333table.diff td {
333 font-family: monospace; 334 font-family: monospace;
334 white-space: pre; 335 white-space: pre;
335} 336}
336 337
337table.diff td div.head { 338table.diff td div.head {
338 font-weight: bold; 339 font-weight: bold;
339 padding-top: 1em; 340 padding-top: 1em;
340} 341}
341 342
342table.diff td div.hunk { 343table.diff td div.hunk {
343 color: #009; 344 color: #009;
344} 345}
345 346
346table.diff td div.add { 347table.diff td div.add {
347 color: green; 348 color: green;
348} 349}
349 350
350table.diff td div.del { 351table.diff td div.del {
351 color: red; 352 color: red;
352} 353}
353 354
354.sha1 { 355.sha1 {
355 font-family: monospace; 356 font-family: monospace;
356 font-size: 90%; 357 font-size: 90%;
357} 358}
358 359
359.left { 360.left {
360 text-align: left; 361 text-align: left;
361} 362}
362 363
363.right { 364.right {
364 text-align: right; 365 text-align: right;
365} 366}
366 367
367table.list td.repogroup { 368table.list td.repogroup {
368 font-style: italic; 369 font-style: italic;
369 color: #888; 370 color: #888;
370} 371}
371 372
372a.button { 373a.button {
373 font-size: 80%; 374 font-size: 80%;
374 color: #aaa; 375 color: #aaa;
375 background-color: #eee; 376 background-color: #eee;
376 border: solid 1px #aaa; 377 border: solid 1px #aaa;
377 padding: 0em 0.5em; 378 padding: 0em 0.5em;
378 margin: 0.1em 0.25em; 379 margin: 0.1em 0.25em;
379} 380}
380 381
381a.button:hover { 382a.button:hover {
382 text-decoration: none; 383 text-decoration: none;
383 color: #333; 384 color: #333;
384 background-color: #ccc; 385 background-color: #ccc;
385} 386}
386 387
387a.primary { 388a.primary {
388 font-size: 100%; 389 font-size: 100%;
389} 390}
390 391
391a.secondary { 392a.secondary {
392 font-size: 90%; 393 font-size: 90%;
393} 394}
394 395
395td.toplevel-repo { 396td.toplevel-repo {
396 397
397} 398}
398 399
399table.list td.sublevel-repo { 400table.list td.sublevel-repo {
400 padding-left: 1.5em; 401 padding-left: 1.5em;
401} 402}
402 403
403span.age-mins { 404span.age-mins {
404 font-weight: bold; 405 font-weight: bold;
405 color: #080; 406 color: #080;
406} 407}
407 408
408span.age-hours { 409span.age-hours {
409 color: #080; 410 color: #080;
410} 411}
411 412
412span.age-days { 413span.age-days {
413 color: #040; 414 color: #040;
414} 415}
415 416
416span.age-weeks { 417span.age-weeks {
417 color: #444; 418 color: #444;
418} 419}
419 420
420span.age-months { 421span.age-months {
421 color: #888; 422 color: #888;
422} 423}
423 424
424span.age-years { 425span.age-years {
425 color: #bbb; 426 color: #bbb;
426} 427}
diff --git a/cgit.h b/cgit.h
index 610a16d..eddcaa3 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,239 +1,246 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <xdiff/xdiff.h> 18#include <xdiff/xdiff.h>
19 19
20 20
21/* 21/*
22 * The valid cgit repo-commands 22 * The valid cgit repo-commands
23 */ 23 */
24#define CMD_LOG 1 24#define CMD_LOG 1
25#define CMD_COMMIT 2 25#define CMD_COMMIT 2
26#define CMD_DIFF 3 26#define CMD_DIFF 3
27#define CMD_TREE 4 27#define CMD_TREE 4
28#define CMD_BLOB 5 28#define CMD_BLOB 5
29#define CMD_SNAPSHOT 6 29#define CMD_SNAPSHOT 6
30#define CMD_TAG 7 30#define CMD_TAG 7
31 31
32/* 32/*
33 * Dateformats used on misc. pages 33 * Dateformats used on misc. pages
34 */ 34 */
35#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S" 35#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S"
36#define FMT_SHORTDATE "%Y-%m-%d" 36#define FMT_SHORTDATE "%Y-%m-%d"
37 37
38 38
39/* 39/*
40 * Limits used for relative dates 40 * Limits used for relative dates
41 */ 41 */
42#define TM_MIN 60 42#define TM_MIN 60
43#define TM_HOUR (TM_MIN * 60) 43#define TM_HOUR (TM_MIN * 60)
44#define TM_DAY (TM_HOUR * 24) 44#define TM_DAY (TM_HOUR * 24)
45#define TM_WEEK (TM_DAY * 7) 45#define TM_WEEK (TM_DAY * 7)
46#define TM_YEAR (TM_DAY * 365) 46#define TM_YEAR (TM_DAY * 365)
47#define TM_MONTH (TM_YEAR / 12.0) 47#define TM_MONTH (TM_YEAR / 12.0)
48 48
49 49
50typedef void (*configfn)(const char *name, const char *value); 50typedef void (*configfn)(const char *name, const char *value);
51typedef void (*filepair_fn)(struct diff_filepair *pair); 51typedef void (*filepair_fn)(struct diff_filepair *pair);
52typedef void (*linediff_fn)(char *line, int len); 52typedef void (*linediff_fn)(char *line, int len);
53 53
54struct cacheitem { 54struct cacheitem {
55 char *name; 55 char *name;
56 struct stat st; 56 struct stat st;
57 int ttl; 57 int ttl;
58 int fd; 58 int fd;
59}; 59};
60 60
61struct repoinfo { 61struct repoinfo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *group; 68 char *group;
69 char *module_link; 69 char *module_link;
70 char *readme; 70 char *readme;
71 int snapshots; 71 int snapshots;
72 int enable_log_filecount; 72 int enable_log_filecount;
73 int enable_log_linecount; 73 int enable_log_linecount;
74}; 74};
75 75
76struct repolist { 76struct repolist {
77 int length; 77 int length;
78 int count; 78 int count;
79 struct repoinfo *repos; 79 struct repoinfo *repos;
80}; 80};
81 81
82struct commitinfo { 82struct commitinfo {
83 struct commit *commit; 83 struct commit *commit;
84 char *author; 84 char *author;
85 char *author_email; 85 char *author_email;
86 unsigned long author_date; 86 unsigned long author_date;
87 char *committer; 87 char *committer;
88 char *committer_email; 88 char *committer_email;
89 unsigned long committer_date; 89 unsigned long committer_date;
90 char *subject; 90 char *subject;
91 char *msg; 91 char *msg;
92}; 92};
93 93
94struct taginfo { 94struct taginfo {
95 char *tagger; 95 char *tagger;
96 char *tagger_email; 96 char *tagger_email;
97 int tagger_date; 97 int tagger_date;
98 char *msg; 98 char *msg;
99}; 99};
100 100
101extern const char *cgit_version; 101extern const char *cgit_version;
102 102
103extern struct repolist cgit_repolist; 103extern struct repolist cgit_repolist;
104extern struct repoinfo *cgit_repo; 104extern struct repoinfo *cgit_repo;
105extern int cgit_cmd; 105extern int cgit_cmd;
106 106
107extern char *cgit_root_title; 107extern char *cgit_root_title;
108extern char *cgit_css; 108extern char *cgit_css;
109extern char *cgit_logo; 109extern char *cgit_logo;
110extern char *cgit_index_header; 110extern char *cgit_index_header;
111extern char *cgit_logo_link; 111extern char *cgit_logo_link;
112extern char *cgit_module_link; 112extern char *cgit_module_link;
113extern char *cgit_agefile; 113extern char *cgit_agefile;
114extern char *cgit_virtual_root; 114extern char *cgit_virtual_root;
115extern char *cgit_script_name; 115extern char *cgit_script_name;
116extern char *cgit_cache_root; 116extern char *cgit_cache_root;
117extern char *cgit_repo_group; 117extern char *cgit_repo_group;
118 118
119extern int cgit_nocache; 119extern int cgit_nocache;
120extern int cgit_snapshots; 120extern int cgit_snapshots;
121extern int cgit_enable_index_links; 121extern int cgit_enable_index_links;
122extern int cgit_enable_log_filecount; 122extern int cgit_enable_log_filecount;
123extern int cgit_enable_log_linecount; 123extern int cgit_enable_log_linecount;
124extern int cgit_max_lock_attempts; 124extern int cgit_max_lock_attempts;
125extern int cgit_cache_root_ttl; 125extern int cgit_cache_root_ttl;
126extern int cgit_cache_repo_ttl; 126extern int cgit_cache_repo_ttl;
127extern int cgit_cache_dynamic_ttl; 127extern int cgit_cache_dynamic_ttl;
128extern int cgit_cache_static_ttl; 128extern int cgit_cache_static_ttl;
129extern int cgit_cache_max_create_time; 129extern int cgit_cache_max_create_time;
130extern int cgit_summary_log; 130extern int cgit_summary_log;
131 131
132extern int cgit_max_msg_len; 132extern int cgit_max_msg_len;
133extern int cgit_max_repodesc_len; 133extern int cgit_max_repodesc_len;
134extern int cgit_max_commit_count; 134extern int cgit_max_commit_count;
135 135
136extern int cgit_query_has_symref; 136extern int cgit_query_has_symref;
137extern int cgit_query_has_sha1; 137extern int cgit_query_has_sha1;
138 138
139extern char *cgit_querystring; 139extern char *cgit_querystring;
140extern char *cgit_query_repo; 140extern char *cgit_query_repo;
141extern char *cgit_query_page; 141extern char *cgit_query_page;
142extern char *cgit_query_search; 142extern char *cgit_query_search;
143extern char *cgit_query_head; 143extern char *cgit_query_head;
144extern char *cgit_query_sha1; 144extern char *cgit_query_sha1;
145extern char *cgit_query_sha2; 145extern char *cgit_query_sha2;
146extern char *cgit_query_path; 146extern char *cgit_query_path;
147extern char *cgit_query_name; 147extern char *cgit_query_name;
148extern int cgit_query_ofs; 148extern int cgit_query_ofs;
149 149
150extern int htmlfd; 150extern int htmlfd;
151 151
152extern int cgit_get_cmd_index(const char *cmd); 152extern int cgit_get_cmd_index(const char *cmd);
153extern struct repoinfo *cgit_get_repoinfo(const char *url); 153extern struct repoinfo *cgit_get_repoinfo(const char *url);
154extern void cgit_global_config_cb(const char *name, const char *value); 154extern void cgit_global_config_cb(const char *name, const char *value);
155extern void cgit_repo_config_cb(const char *name, const char *value); 155extern void cgit_repo_config_cb(const char *name, const char *value);
156extern void cgit_querystring_cb(const char *name, const char *value); 156extern void cgit_querystring_cb(const char *name, const char *value);
157 157
158extern int chk_zero(int result, char *msg); 158extern int chk_zero(int result, char *msg);
159extern int chk_positive(int result, char *msg); 159extern int chk_positive(int result, char *msg);
160extern int chk_non_negative(int result, char *msg);
160 161
161extern int hextoint(char c); 162extern int hextoint(char c);
162extern char *trim_end(const char *str, char c); 163extern char *trim_end(const char *str, char c);
163 164
164extern void *cgit_free_commitinfo(struct commitinfo *info); 165extern void *cgit_free_commitinfo(struct commitinfo *info);
165 166
166extern int cgit_diff_files(const unsigned char *old_sha1, 167extern int cgit_diff_files(const unsigned char *old_sha1,
167 const unsigned char *new_sha1, 168 const unsigned char *new_sha1,
168 linediff_fn fn); 169 linediff_fn fn);
169 170
170extern void cgit_diff_tree(const unsigned char *old_sha1, 171extern void cgit_diff_tree(const unsigned char *old_sha1,
171 const unsigned char *new_sha1, 172 const unsigned char *new_sha1,
172 filepair_fn fn); 173 filepair_fn fn);
173 174
174extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 175extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
175 176
176extern char *fmt(const char *format,...); 177extern char *fmt(const char *format,...);
177 178
178extern void html(const char *txt); 179extern void html(const char *txt);
179extern void htmlf(const char *format,...); 180extern void htmlf(const char *format,...);
180extern void html_txt(char *txt); 181extern void html_txt(char *txt);
181extern void html_ntxt(int len, char *txt); 182extern void html_ntxt(int len, char *txt);
182extern void html_attr(char *txt); 183extern void html_attr(char *txt);
183extern void html_hidden(char *name, char *value); 184extern void html_hidden(char *name, char *value);
184extern void html_link_open(char *url, char *title, char *class); 185extern void html_link_open(char *url, char *title, char *class);
185extern void html_link_close(void); 186extern void html_link_close(void);
186extern void html_filemode(unsigned short mode); 187extern void html_filemode(unsigned short mode);
187extern int html_include(const char *filename); 188extern int html_include(const char *filename);
188 189
189extern int cgit_read_config(const char *filename, configfn fn); 190extern int cgit_read_config(const char *filename, configfn fn);
190extern int cgit_parse_query(char *txt, configfn fn); 191extern int cgit_parse_query(char *txt, configfn fn);
191extern struct commitinfo *cgit_parse_commit(struct commit *commit); 192extern struct commitinfo *cgit_parse_commit(struct commit *commit);
192extern struct taginfo *cgit_parse_tag(struct tag *tag); 193extern struct taginfo *cgit_parse_tag(struct tag *tag);
193extern void cgit_parse_url(const char *url); 194extern void cgit_parse_url(const char *url);
194 195
195extern char *cache_safe_filename(const char *unsafe); 196extern char *cache_safe_filename(const char *unsafe);
196extern int cache_lock(struct cacheitem *item); 197extern int cache_lock(struct cacheitem *item);
197extern int cache_unlock(struct cacheitem *item); 198extern int cache_unlock(struct cacheitem *item);
198extern int cache_cancel_lock(struct cacheitem *item); 199extern int cache_cancel_lock(struct cacheitem *item);
199extern int cache_exist(struct cacheitem *item); 200extern int cache_exist(struct cacheitem *item);
200extern int cache_expired(struct cacheitem *item); 201extern int cache_expired(struct cacheitem *item);
201 202
202extern char *cgit_repourl(const char *reponame); 203extern char *cgit_repourl(const char *reponame);
204extern char *cgit_fileurl(const char *reponame, const char *pagename,
205 const char *filename, const char *query);
203extern char *cgit_pageurl(const char *reponame, const char *pagename, 206extern char *cgit_pageurl(const char *reponame, const char *pagename,
204 const char *query); 207 const char *query);
205 208
209extern const char *cgit_repobasename(const char *reponame);
210
206extern void cgit_tree_link(char *name, char *title, char *class, char *head, 211extern void cgit_tree_link(char *name, char *title, char *class, char *head,
207 char *rev, char *path); 212 char *rev, char *path);
208extern void cgit_log_link(char *name, char *title, char *class, char *head, 213extern void cgit_log_link(char *name, char *title, char *class, char *head,
209 char *rev, char *path, int ofs); 214 char *rev, char *path, int ofs);
210extern void cgit_commit_link(char *name, char *title, char *class, char *head, 215extern void cgit_commit_link(char *name, char *title, char *class, char *head,
211 char *rev); 216 char *rev);
212extern void cgit_diff_link(char *name, char *title, char *class, char *head, 217extern void cgit_diff_link(char *name, char *title, char *class, char *head,
213 char *new_rev, char *old_rev, char *path); 218 char *new_rev, char *old_rev, char *path);
214 219
215extern void cgit_object_link(struct object *obj); 220extern void cgit_object_link(struct object *obj);
216 221
217extern void cgit_print_error(char *msg); 222extern void cgit_print_error(char *msg);
218extern void cgit_print_date(time_t secs, char *format); 223extern void cgit_print_date(time_t secs, char *format);
219extern void cgit_print_age(time_t t, time_t max_relative, char *format); 224extern void cgit_print_age(time_t t, time_t max_relative, char *format);
220extern void cgit_print_docstart(char *title, struct cacheitem *item); 225extern void cgit_print_docstart(char *title, struct cacheitem *item);
221extern void cgit_print_docend(); 226extern void cgit_print_docend();
222extern void cgit_print_pageheader(char *title, int show_search); 227extern void cgit_print_pageheader(char *title, int show_search);
223extern void cgit_print_snapshot_start(const char *mimetype, 228extern void cgit_print_snapshot_start(const char *mimetype,
224 const char *filename, 229 const char *filename,
225 struct cacheitem *item); 230 struct cacheitem *item);
226 231
227extern void cgit_print_repolist(struct cacheitem *item); 232extern void cgit_print_repolist(struct cacheitem *item);
228extern void cgit_print_summary(); 233extern void cgit_print_summary();
229extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *path, int pager); 234extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *path, int pager);
230extern void cgit_print_blob(struct cacheitem *item, const char *hex, char *path); 235extern void cgit_print_blob(struct cacheitem *item, const char *hex, char *path);
231extern void cgit_print_tree(const char *rev, char *path); 236extern void cgit_print_tree(const char *rev, char *path);
232extern void cgit_print_commit(char *hex); 237extern void cgit_print_commit(char *hex);
233extern void cgit_print_tag(char *revname); 238extern void cgit_print_tag(char *revname);
234extern void cgit_print_diff(const char *new_hex, const char *old_hex); 239extern void cgit_print_diff(const char *new_hex, const char *old_hex);
235extern void cgit_print_snapshot(struct cacheitem *item, const char *hex, 240extern void cgit_print_snapshot(struct cacheitem *item, const char *hex,
236 const char *format, const char *prefix, 241 const char *prefix, const char *filename,
237 const char *filename); 242 int snapshot);
243extern void cgit_print_snapshot_links(const char *repo, const char *hex,int snapshots);
244extern int cgit_parse_snapshots_mask(const char *str);
238 245
239#endif /* CGIT_H */ 246#endif /* CGIT_H */
diff --git a/cgitrc b/cgitrc
index 40877f8..1040997 100644
--- a/cgitrc
+++ b/cgitrc
@@ -1,120 +1,121 @@
1## 1##
2## cgitrc: template for /etc/cgitrc 2## cgitrc: template for /etc/cgitrc
3## 3##
4 4
5 5
6## Uncomment and set to 1 to deactivate caching of generated pages. Mostly 6## Uncomment and set to 1 to deactivate caching of generated pages. Mostly
7## usefull for testing. 7## usefull for testing.
8#nocache=0 8#nocache=0
9 9
10 10
11## Enable/disable snapshots by default. This can be overridden per repo 11## Set allowed snapshot types by default. Can be overridden per repo
12# can be any combination of zip/tar.gz/tar.bz2/tar
12#snapshots=0 13#snapshots=0
13 14
14 15
15## Enable/disable extra links to summary/log/tree per repo on index page 16## Enable/disable extra links to summary/log/tree per repo on index page
16#enable-index-links=0 17#enable-index-links=0
17 18
18 19
19## Enable/disable display of 'number of files changed' in log view 20## Enable/disable display of 'number of files changed' in log view
20#enable-log-filecount=0 21#enable-log-filecount=0
21 22
22 23
23## Enable/disable display of 'number of lines changed' in log view 24## Enable/disable display of 'number of lines changed' in log view
24#enable-log-linecount=0 25#enable-log-linecount=0
25 26
26 27
27## Enable/disable display of HEAD shortlog in summary view. Set it to maximum 28## Enable/disable display of HEAD shortlog in summary view. Set it to maximum
28## number of commits that should be displayed 29## number of commits that should be displayed
29#summary-log=0 30#summary-log=0
30 31
31 32
32## Specify a root for virtual urls. This makes cgit generate urls like 33## Specify a root for virtual urls. This makes cgit generate urls like
33## 34##
34## http://localhost/git/repo/log/?id=master 35## http://localhost/git/repo/log/?id=master
35## 36##
36## instead of 37## instead of
37## 38##
38## http://localhost/cgit/cgit.cgi?r=repo&p=log&id=master 39## http://localhost/cgit/cgit.cgi?r=repo&p=log&id=master
39## 40##
40## For this to work with apache, rewrite rules must be added to httpd.conf, 41## For this to work with apache, rewrite rules must be added to httpd.conf,
41## possibly looking something like this: 42## possibly looking something like this:
42## 43##
43## RewriteRule ^/git/$ /cgit/cgit.cgi [L,QSA] 44## RewriteRule ^/git/$ /cgit/cgit.cgi [L,QSA]
44## RewriteRule ^/git/([^/]+)/$ /cgit/cgit.cgi?r=$1 [L,QSA] 45## RewriteRule ^/git/([^/]+)/$ /cgit/cgit.cgi?r=$1 [L,QSA]
45## RewriteRule ^/git/([^/]+)/([^/]+)/$ /cgit/cgit.cgi?r=$1&p=$2 [L,QSA] 46## RewriteRule ^/git/([^/]+)/([^/]+)/$ /cgit/cgit.cgi?r=$1&p=$2 [L,QSA]
46## 47##
47## This setting is disabled by default. 48## This setting is disabled by default.
48#virtual-root=/git 49#virtual-root=/git
49 50
50 51
51## Set the title printed on the root page 52## Set the title printed on the root page
52#root-title=Git repository browser 53#root-title=Git repository browser
53 54
54 55
55## Link to css file 56## Link to css file
56#css=/cgit/cgit.css 57#css=/cgit/cgit.css
57 58
58 59
59## Link to logo file 60## Link to logo file
60#logo=/cgit/git-logo.png 61#logo=/cgit/git-logo.png
61 62
62 63
63## Url loaded when clicking the logo 64## Url loaded when clicking the logo
64#logo-link=http://www.kernel.org/pub/software/scm/git/docs/ 65#logo-link=http://www.kernel.org/pub/software/scm/git/docs/
65 66
66 67
67## Url loaded when clicking a submodule link 68## Url loaded when clicking a submodule link
68#module-link=./?repo=%s&page=commit&id=%s 69#module-link=./?repo=%s&page=commit&id=%s
69 70
70 71
71## Number of chars shown of repo description (in repolist view) 72## Number of chars shown of repo description (in repolist view)
72#max-repodesc-length=60 73#max-repodesc-length=60
73 74
74 75
75## Number of chars shown of commit subject message (in log view) 76## Number of chars shown of commit subject message (in log view)
76#max-message-length=60 77#max-message-length=60
77 78
78 79
79## Number of commits per page in log view 80## Number of commits per page in log view
80#max-commit-count=50 81#max-commit-count=50
81 82
82 83
83## Root of cached output 84## Root of cached output
84#cache-root=/var/cache/cgit 85#cache-root=/var/cache/cgit
85 86
86 87
87## Include another config-file 88## Include another config-file
88#include=/var/cgit/repolist 89#include=/var/cgit/repolist
89 90
90## 91##
91## Time-To-Live settings: specifies how long (in minutes) different pages 92## Time-To-Live settings: specifies how long (in minutes) different pages
92## should be cached (0 for instant expiration, -1 for immortal pages) 93## should be cached (0 for instant expiration, -1 for immortal pages)
93## 94##
94 95
95## ttl for root page 96## ttl for root page
96#cache-root-ttl=5 97#cache-root-ttl=5
97 98
98## ttl for repo summary page 99## ttl for repo summary page
99#cache-repo-ttl=5 100#cache-repo-ttl=5
100 101
101## ttl for other dynamic pages 102## ttl for other dynamic pages
102#cache-dynamic-ttl=5 103#cache-dynamic-ttl=5
103 104
104## ttl for static pages (addressed by SHA-1) 105## ttl for static pages (addressed by SHA-1)
105#cache-static-ttl=-1 106#cache-static-ttl=-1
106 107
107 108
108 109
109## Example repository entry. Required values are repo.url and repo.path (each 110## Example repository entry. Required values are repo.url and repo.path (each
110## repository section must start with repo.url). 111## repository section must start with repo.url).
111#repo.url=cgit 112#repo.url=cgit
112#repo.name=cgit 113#repo.name=cgit
113#repo.desc=the caching cgi for git 114#repo.desc=the caching cgi for git
114#repo.path=/pub/git/cgit 115#repo.path=/pub/git/cgit
115#repo.owner=Lars Hjemli 116#repo.owner=Lars Hjemli
116 #repo.snapshots=1 # override a sitewide snapshot-setting 117 #repo.snapshots=tar.bz2 # override a sitewide snapshot-setting
117 #repo.enable-log-filecount=0 # override the default filecount setting 118 #repo.enable-log-filecount=0 # override the default filecount setting
118 #repo.enable-log-linecount=0 # override the default linecount setting 119 #repo.enable-log-linecount=0 # override the default linecount setting
119 #repo.module-link=/git/%s/commit/?id=%s # override the standard module-link 120 #repo.module-link=/git/%s/commit/?id=%s # override the standard module-link
120 #repo.readme=info/web/readme # specify a file to include on summary page 121 #repo.readme=info/web/readme # specify a file to include on summary page
diff --git a/shared.c b/shared.c
index 06693b0..077934f 100644
--- a/shared.c
+++ b/shared.c
@@ -1,405 +1,412 @@
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 repolist cgit_repolist; 11struct repolist cgit_repolist;
12struct repoinfo *cgit_repo; 12struct repoinfo *cgit_repo;
13int cgit_cmd; 13int cgit_cmd;
14 14
15const char *cgit_version = CGIT_VERSION; 15const char *cgit_version = CGIT_VERSION;
16 16
17char *cgit_root_title = "Git repository browser"; 17char *cgit_root_title = "Git repository browser";
18char *cgit_css = "/cgit.css"; 18char *cgit_css = "/cgit.css";
19char *cgit_logo = "/git-logo.png"; 19char *cgit_logo = "/git-logo.png";
20char *cgit_index_header = NULL; 20char *cgit_index_header = NULL;
21char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; 21char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/";
22char *cgit_module_link = "./?repo=%s&page=commit&id=%s"; 22char *cgit_module_link = "./?repo=%s&page=commit&id=%s";
23char *cgit_agefile = "info/web/last-modified"; 23char *cgit_agefile = "info/web/last-modified";
24char *cgit_virtual_root = NULL; 24char *cgit_virtual_root = NULL;
25char *cgit_script_name = CGIT_SCRIPT_NAME; 25char *cgit_script_name = CGIT_SCRIPT_NAME;
26char *cgit_cache_root = "/var/cache/cgit"; 26char *cgit_cache_root = "/var/cache/cgit";
27char *cgit_repo_group = NULL; 27char *cgit_repo_group = NULL;
28 28
29int cgit_nocache = 0; 29int cgit_nocache = 0;
30int cgit_snapshots = 0; 30int cgit_snapshots = 0;
31int cgit_enable_index_links = 0; 31int cgit_enable_index_links = 0;
32int cgit_enable_log_filecount = 0; 32int cgit_enable_log_filecount = 0;
33int cgit_enable_log_linecount = 0; 33int cgit_enable_log_linecount = 0;
34int cgit_max_lock_attempts = 5; 34int cgit_max_lock_attempts = 5;
35int cgit_cache_root_ttl = 5; 35int cgit_cache_root_ttl = 5;
36int cgit_cache_repo_ttl = 5; 36int cgit_cache_repo_ttl = 5;
37int cgit_cache_dynamic_ttl = 5; 37int cgit_cache_dynamic_ttl = 5;
38int cgit_cache_static_ttl = -1; 38int cgit_cache_static_ttl = -1;
39int cgit_cache_max_create_time = 5; 39int cgit_cache_max_create_time = 5;
40int cgit_summary_log = 0; 40int cgit_summary_log = 0;
41 41
42int cgit_max_msg_len = 60; 42int cgit_max_msg_len = 60;
43int cgit_max_repodesc_len = 60; 43int cgit_max_repodesc_len = 60;
44int cgit_max_commit_count = 50; 44int cgit_max_commit_count = 50;
45 45
46int cgit_query_has_symref = 0; 46int cgit_query_has_symref = 0;
47int cgit_query_has_sha1 = 0; 47int cgit_query_has_sha1 = 0;
48 48
49char *cgit_querystring = NULL; 49char *cgit_querystring = NULL;
50char *cgit_query_repo = NULL; 50char *cgit_query_repo = NULL;
51char *cgit_query_page = NULL; 51char *cgit_query_page = NULL;
52char *cgit_query_head = NULL; 52char *cgit_query_head = NULL;
53char *cgit_query_search = NULL; 53char *cgit_query_search = NULL;
54char *cgit_query_sha1 = NULL; 54char *cgit_query_sha1 = NULL;
55char *cgit_query_sha2 = NULL; 55char *cgit_query_sha2 = NULL;
56char *cgit_query_path = NULL; 56char *cgit_query_path = NULL;
57char *cgit_query_name = NULL; 57char *cgit_query_name = NULL;
58int cgit_query_ofs = 0; 58int cgit_query_ofs = 0;
59 59
60int htmlfd = 0; 60int htmlfd = 0;
61 61
62 62
63int cgit_get_cmd_index(const char *cmd) 63int cgit_get_cmd_index(const char *cmd)
64{ 64{
65 static char *cmds[] = {"log", "commit", "diff", "tree", "blob", 65 static char *cmds[] = {"log", "commit", "diff", "tree", "blob",
66 "snapshot", "tag", NULL}; 66 "snapshot", "tag", NULL};
67 int i; 67 int i;
68 68
69 for(i = 0; cmds[i]; i++) 69 for(i = 0; cmds[i]; i++)
70 if (!strcmp(cmd, cmds[i])) 70 if (!strcmp(cmd, cmds[i]))
71 return i + 1; 71 return i + 1;
72 return 0; 72 return 0;
73} 73}
74 74
75int chk_zero(int result, char *msg) 75int chk_zero(int result, char *msg)
76{ 76{
77 if (result != 0) 77 if (result != 0)
78 die("%s: %s", msg, strerror(errno)); 78 die("%s: %s", msg, strerror(errno));
79 return result; 79 return result;
80} 80}
81 81
82int chk_positive(int result, char *msg) 82int chk_positive(int result, char *msg)
83{ 83{
84 if (result <= 0) 84 if (result <= 0)
85 die("%s: %s", msg, strerror(errno)); 85 die("%s: %s", msg, strerror(errno));
86 return result; 86 return result;
87} 87}
88 88
89int chk_non_negative(int result, char *msg)
90{
91 if (result < 0)
92 die("%s: %s",msg, strerror(errno));
93 return result;
94}
95
89struct repoinfo *add_repo(const char *url) 96struct repoinfo *add_repo(const char *url)
90{ 97{
91 struct repoinfo *ret; 98 struct repoinfo *ret;
92 99
93 if (++cgit_repolist.count > cgit_repolist.length) { 100 if (++cgit_repolist.count > cgit_repolist.length) {
94 if (cgit_repolist.length == 0) 101 if (cgit_repolist.length == 0)
95 cgit_repolist.length = 8; 102 cgit_repolist.length = 8;
96 else 103 else
97 cgit_repolist.length *= 2; 104 cgit_repolist.length *= 2;
98 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 105 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
99 cgit_repolist.length * 106 cgit_repolist.length *
100 sizeof(struct repoinfo)); 107 sizeof(struct repoinfo));
101 } 108 }
102 109
103 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 110 ret = &cgit_repolist.repos[cgit_repolist.count-1];
104 ret->url = xstrdup(url); 111 ret->url = xstrdup(url);
105 ret->name = ret->url; 112 ret->name = ret->url;
106 ret->path = NULL; 113 ret->path = NULL;
107 ret->desc = NULL; 114 ret->desc = NULL;
108 ret->owner = NULL; 115 ret->owner = NULL;
109 ret->group = cgit_repo_group; 116 ret->group = cgit_repo_group;
110 ret->defbranch = "master"; 117 ret->defbranch = "master";
111 ret->snapshots = cgit_snapshots; 118 ret->snapshots = cgit_snapshots;
112 ret->enable_log_filecount = cgit_enable_log_filecount; 119 ret->enable_log_filecount = cgit_enable_log_filecount;
113 ret->enable_log_linecount = cgit_enable_log_linecount; 120 ret->enable_log_linecount = cgit_enable_log_linecount;
114 ret->module_link = cgit_module_link; 121 ret->module_link = cgit_module_link;
115 ret->readme = NULL; 122 ret->readme = NULL;
116 return ret; 123 return ret;
117} 124}
118 125
119struct repoinfo *cgit_get_repoinfo(const char *url) 126struct repoinfo *cgit_get_repoinfo(const char *url)
120{ 127{
121 int i; 128 int i;
122 struct repoinfo *repo; 129 struct repoinfo *repo;
123 130
124 for (i=0; i<cgit_repolist.count; i++) { 131 for (i=0; i<cgit_repolist.count; i++) {
125 repo = &cgit_repolist.repos[i]; 132 repo = &cgit_repolist.repos[i];
126 if (!strcmp(repo->url, url)) 133 if (!strcmp(repo->url, url))
127 return repo; 134 return repo;
128 } 135 }
129 return NULL; 136 return NULL;
130} 137}
131 138
132void cgit_global_config_cb(const char *name, const char *value) 139void cgit_global_config_cb(const char *name, const char *value)
133{ 140{
134 if (!strcmp(name, "root-title")) 141 if (!strcmp(name, "root-title"))
135 cgit_root_title = xstrdup(value); 142 cgit_root_title = xstrdup(value);
136 else if (!strcmp(name, "css")) 143 else if (!strcmp(name, "css"))
137 cgit_css = xstrdup(value); 144 cgit_css = xstrdup(value);
138 else if (!strcmp(name, "logo")) 145 else if (!strcmp(name, "logo"))
139 cgit_logo = xstrdup(value); 146 cgit_logo = xstrdup(value);
140 else if (!strcmp(name, "index-header")) 147 else if (!strcmp(name, "index-header"))
141 cgit_index_header = xstrdup(value); 148 cgit_index_header = xstrdup(value);
142 else if (!strcmp(name, "logo-link")) 149 else if (!strcmp(name, "logo-link"))
143 cgit_logo_link = xstrdup(value); 150 cgit_logo_link = xstrdup(value);
144 else if (!strcmp(name, "module-link")) 151 else if (!strcmp(name, "module-link"))
145 cgit_module_link = xstrdup(value); 152 cgit_module_link = xstrdup(value);
146 else if (!strcmp(name, "virtual-root")) 153 else if (!strcmp(name, "virtual-root"))
147 cgit_virtual_root = xstrdup(value); 154 cgit_virtual_root = xstrdup(value);
148 else if (!strcmp(name, "nocache")) 155 else if (!strcmp(name, "nocache"))
149 cgit_nocache = atoi(value); 156 cgit_nocache = atoi(value);
150 else if (!strcmp(name, "snapshots")) 157 else if (!strcmp(name, "snapshots"))
151 cgit_snapshots = atoi(value); 158 cgit_snapshots = cgit_parse_snapshots_mask(value);
152 else if (!strcmp(name, "enable-index-links")) 159 else if (!strcmp(name, "enable-index-links"))
153 cgit_enable_index_links = atoi(value); 160 cgit_enable_index_links = atoi(value);
154 else if (!strcmp(name, "enable-log-filecount")) 161 else if (!strcmp(name, "enable-log-filecount"))
155 cgit_enable_log_filecount = atoi(value); 162 cgit_enable_log_filecount = atoi(value);
156 else if (!strcmp(name, "enable-log-linecount")) 163 else if (!strcmp(name, "enable-log-linecount"))
157 cgit_enable_log_linecount = atoi(value); 164 cgit_enable_log_linecount = atoi(value);
158 else if (!strcmp(name, "cache-root")) 165 else if (!strcmp(name, "cache-root"))
159 cgit_cache_root = xstrdup(value); 166 cgit_cache_root = xstrdup(value);
160 else if (!strcmp(name, "cache-root-ttl")) 167 else if (!strcmp(name, "cache-root-ttl"))
161 cgit_cache_root_ttl = atoi(value); 168 cgit_cache_root_ttl = atoi(value);
162 else if (!strcmp(name, "cache-repo-ttl")) 169 else if (!strcmp(name, "cache-repo-ttl"))
163 cgit_cache_repo_ttl = atoi(value); 170 cgit_cache_repo_ttl = atoi(value);
164 else if (!strcmp(name, "cache-static-ttl")) 171 else if (!strcmp(name, "cache-static-ttl"))
165 cgit_cache_static_ttl = atoi(value); 172 cgit_cache_static_ttl = atoi(value);
166 else if (!strcmp(name, "cache-dynamic-ttl")) 173 else if (!strcmp(name, "cache-dynamic-ttl"))
167 cgit_cache_dynamic_ttl = atoi(value); 174 cgit_cache_dynamic_ttl = atoi(value);
168 else if (!strcmp(name, "max-message-length")) 175 else if (!strcmp(name, "max-message-length"))
169 cgit_max_msg_len = atoi(value); 176 cgit_max_msg_len = atoi(value);
170 else if (!strcmp(name, "max-repodesc-length")) 177 else if (!strcmp(name, "max-repodesc-length"))
171 cgit_max_repodesc_len = atoi(value); 178 cgit_max_repodesc_len = atoi(value);
172 else if (!strcmp(name, "max-commit-count")) 179 else if (!strcmp(name, "max-commit-count"))
173 cgit_max_commit_count = atoi(value); 180 cgit_max_commit_count = atoi(value);
174 else if (!strcmp(name, "summary-log")) 181 else if (!strcmp(name, "summary-log"))
175 cgit_summary_log = atoi(value); 182 cgit_summary_log = atoi(value);
176 else if (!strcmp(name, "agefile")) 183 else if (!strcmp(name, "agefile"))
177 cgit_agefile = xstrdup(value); 184 cgit_agefile = xstrdup(value);
178 else if (!strcmp(name, "repo.group")) 185 else if (!strcmp(name, "repo.group"))
179 cgit_repo_group = xstrdup(value); 186 cgit_repo_group = xstrdup(value);
180 else if (!strcmp(name, "repo.url")) 187 else if (!strcmp(name, "repo.url"))
181 cgit_repo = add_repo(value); 188 cgit_repo = add_repo(value);
182 else if (!strcmp(name, "repo.name")) 189 else if (!strcmp(name, "repo.name"))
183 cgit_repo->name = xstrdup(value); 190 cgit_repo->name = xstrdup(value);
184 else if (cgit_repo && !strcmp(name, "repo.path")) 191 else if (cgit_repo && !strcmp(name, "repo.path"))
185 cgit_repo->path = xstrdup(value); 192 cgit_repo->path = xstrdup(value);
186 else if (cgit_repo && !strcmp(name, "repo.desc")) 193 else if (cgit_repo && !strcmp(name, "repo.desc"))
187 cgit_repo->desc = xstrdup(value); 194 cgit_repo->desc = xstrdup(value);
188 else if (cgit_repo && !strcmp(name, "repo.owner")) 195 else if (cgit_repo && !strcmp(name, "repo.owner"))
189 cgit_repo->owner = xstrdup(value); 196 cgit_repo->owner = xstrdup(value);
190 else if (cgit_repo && !strcmp(name, "repo.defbranch")) 197 else if (cgit_repo && !strcmp(name, "repo.defbranch"))
191 cgit_repo->defbranch = xstrdup(value); 198 cgit_repo->defbranch = xstrdup(value);
192 else if (cgit_repo && !strcmp(name, "repo.snapshots")) 199 else if (cgit_repo && !strcmp(name, "repo.snapshots"))
193 cgit_repo->snapshots = cgit_snapshots * atoi(value); 200 cgit_repo->snapshots = cgit_snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
194 else if (cgit_repo && !strcmp(name, "repo.enable-log-filecount")) 201 else if (cgit_repo && !strcmp(name, "repo.enable-log-filecount"))
195 cgit_repo->enable_log_filecount = cgit_enable_log_filecount * atoi(value); 202 cgit_repo->enable_log_filecount = cgit_enable_log_filecount * atoi(value);
196 else if (cgit_repo && !strcmp(name, "repo.enable-log-linecount")) 203 else if (cgit_repo && !strcmp(name, "repo.enable-log-linecount"))
197 cgit_repo->enable_log_linecount = cgit_enable_log_linecount * atoi(value); 204 cgit_repo->enable_log_linecount = cgit_enable_log_linecount * atoi(value);
198 else if (cgit_repo && !strcmp(name, "repo.module-link")) 205 else if (cgit_repo && !strcmp(name, "repo.module-link"))
199 cgit_repo->module_link= xstrdup(value); 206 cgit_repo->module_link= xstrdup(value);
200 else if (cgit_repo && !strcmp(name, "repo.readme") && value != NULL) { 207 else if (cgit_repo && !strcmp(name, "repo.readme") && value != NULL) {
201 if (*value == '/') 208 if (*value == '/')
202 cgit_repo->readme = xstrdup(value); 209 cgit_repo->readme = xstrdup(value);
203 else 210 else
204 cgit_repo->readme = xstrdup(fmt("%s/%s", cgit_repo->path, value)); 211 cgit_repo->readme = xstrdup(fmt("%s/%s", cgit_repo->path, value));
205 } else if (!strcmp(name, "include")) 212 } else if (!strcmp(name, "include"))
206 cgit_read_config(value, cgit_global_config_cb); 213 cgit_read_config(value, cgit_global_config_cb);
207} 214}
208 215
209void cgit_querystring_cb(const char *name, const char *value) 216void cgit_querystring_cb(const char *name, const char *value)
210{ 217{
211 if (!strcmp(name,"r")) { 218 if (!strcmp(name,"r")) {
212 cgit_query_repo = xstrdup(value); 219 cgit_query_repo = xstrdup(value);
213 cgit_repo = cgit_get_repoinfo(value); 220 cgit_repo = cgit_get_repoinfo(value);
214 } else if (!strcmp(name, "p")) { 221 } else if (!strcmp(name, "p")) {
215 cgit_query_page = xstrdup(value); 222 cgit_query_page = xstrdup(value);
216 cgit_cmd = cgit_get_cmd_index(value); 223 cgit_cmd = cgit_get_cmd_index(value);
217 } else if (!strcmp(name, "url")) { 224 } else if (!strcmp(name, "url")) {
218 cgit_parse_url(value); 225 cgit_parse_url(value);
219 } else if (!strcmp(name, "q")) { 226 } else if (!strcmp(name, "q")) {
220 cgit_query_search = xstrdup(value); 227 cgit_query_search = xstrdup(value);
221 } else if (!strcmp(name, "h")) { 228 } else if (!strcmp(name, "h")) {
222 cgit_query_head = xstrdup(value); 229 cgit_query_head = xstrdup(value);
223 cgit_query_has_symref = 1; 230 cgit_query_has_symref = 1;
224 } else if (!strcmp(name, "id")) { 231 } else if (!strcmp(name, "id")) {
225 cgit_query_sha1 = xstrdup(value); 232 cgit_query_sha1 = xstrdup(value);
226 cgit_query_has_sha1 = 1; 233 cgit_query_has_sha1 = 1;
227 } else if (!strcmp(name, "id2")) { 234 } else if (!strcmp(name, "id2")) {
228 cgit_query_sha2 = xstrdup(value); 235 cgit_query_sha2 = xstrdup(value);
229 cgit_query_has_sha1 = 1; 236 cgit_query_has_sha1 = 1;
230 } else if (!strcmp(name, "ofs")) { 237 } else if (!strcmp(name, "ofs")) {
231 cgit_query_ofs = atoi(value); 238 cgit_query_ofs = atoi(value);
232 } else if (!strcmp(name, "path")) { 239 } else if (!strcmp(name, "path")) {
233 cgit_query_path = trim_end(value, '/'); 240 cgit_query_path = trim_end(value, '/');
234 } else if (!strcmp(name, "name")) { 241 } else if (!strcmp(name, "name")) {
235 cgit_query_name = xstrdup(value); 242 cgit_query_name = xstrdup(value);
236 } 243 }
237} 244}
238 245
239void *cgit_free_commitinfo(struct commitinfo *info) 246void *cgit_free_commitinfo(struct commitinfo *info)
240{ 247{
241 free(info->author); 248 free(info->author);
242 free(info->author_email); 249 free(info->author_email);
243 free(info->committer); 250 free(info->committer);
244 free(info->committer_email); 251 free(info->committer_email);
245 free(info->subject); 252 free(info->subject);
246 free(info); 253 free(info);
247 return NULL; 254 return NULL;
248} 255}
249 256
250int hextoint(char c) 257int hextoint(char c)
251{ 258{
252 if (c >= 'a' && c <= 'f') 259 if (c >= 'a' && c <= 'f')
253 return 10 + c - 'a'; 260 return 10 + c - 'a';
254 else if (c >= 'A' && c <= 'F') 261 else if (c >= 'A' && c <= 'F')
255 return 10 + c - 'A'; 262 return 10 + c - 'A';
256 else if (c >= '0' && c <= '9') 263 else if (c >= '0' && c <= '9')
257 return c - '0'; 264 return c - '0';
258 else 265 else
259 return -1; 266 return -1;
260} 267}
261 268
262char *trim_end(const char *str, char c) 269char *trim_end(const char *str, char c)
263{ 270{
264 int len; 271 int len;
265 char *s, *t; 272 char *s, *t;
266 273
267 if (str == NULL) 274 if (str == NULL)
268 return NULL; 275 return NULL;
269 t = (char *)str; 276 t = (char *)str;
270 len = strlen(t); 277 len = strlen(t);
271 while(len > 0 && t[len - 1] == c) 278 while(len > 0 && t[len - 1] == c)
272 len--; 279 len--;
273 280
274 if (len == 0) 281 if (len == 0)
275 return NULL; 282 return NULL;
276 283
277 c = t[len]; 284 c = t[len];
278 t[len] = '\0'; 285 t[len] = '\0';
279 s = xstrdup(t); 286 s = xstrdup(t);
280 t[len] = c; 287 t[len] = c;
281 return s; 288 return s;
282} 289}
283 290
284void cgit_diff_tree_cb(struct diff_queue_struct *q, 291void cgit_diff_tree_cb(struct diff_queue_struct *q,
285 struct diff_options *options, void *data) 292 struct diff_options *options, void *data)
286{ 293{
287 int i; 294 int i;
288 295
289 for (i = 0; i < q->nr; i++) { 296 for (i = 0; i < q->nr; i++) {
290 if (q->queue[i]->status == 'U') 297 if (q->queue[i]->status == 'U')
291 continue; 298 continue;
292 ((filepair_fn)data)(q->queue[i]); 299 ((filepair_fn)data)(q->queue[i]);
293 } 300 }
294} 301}
295 302
296static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 303static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
297{ 304{
298 enum object_type type; 305 enum object_type type;
299 306
300 if (is_null_sha1(sha1)) { 307 if (is_null_sha1(sha1)) {
301 file->ptr = (char *)""; 308 file->ptr = (char *)"";
302 file->size = 0; 309 file->size = 0;
303 } else { 310 } else {
304 file->ptr = read_sha1_file(sha1, &type, &file->size); 311 file->ptr = read_sha1_file(sha1, &type, &file->size);
305 } 312 }
306 return 1; 313 return 1;
307} 314}
308 315
309/* 316/*
310 * Receive diff-buffers from xdiff and concatenate them as 317 * Receive diff-buffers from xdiff and concatenate them as
311 * needed across multiple callbacks. 318 * needed across multiple callbacks.
312 * 319 *
313 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 320 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
314 * ripped from git and modified to use globals instead of 321 * ripped from git and modified to use globals instead of
315 * a special callback-struct. 322 * a special callback-struct.
316 */ 323 */
317char *diffbuf = NULL; 324char *diffbuf = NULL;
318int buflen = 0; 325int buflen = 0;
319 326
320int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 327int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
321{ 328{
322 int i; 329 int i;
323 330
324 for (i = 0; i < nbuf; i++) { 331 for (i = 0; i < nbuf; i++) {
325 if (mb[i].ptr[mb[i].size-1] != '\n') { 332 if (mb[i].ptr[mb[i].size-1] != '\n') {
326 /* Incomplete line */ 333 /* Incomplete line */
327 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 334 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
328 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 335 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
329 buflen += mb[i].size; 336 buflen += mb[i].size;
330 continue; 337 continue;
331 } 338 }
332 339
333 /* we have a complete line */ 340 /* we have a complete line */
334 if (!diffbuf) { 341 if (!diffbuf) {
335 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 342 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
336 continue; 343 continue;
337 } 344 }
338 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 345 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
339 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 346 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
340 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 347 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
341 free(diffbuf); 348 free(diffbuf);
342 diffbuf = NULL; 349 diffbuf = NULL;
343 buflen = 0; 350 buflen = 0;
344 } 351 }
345 if (diffbuf) { 352 if (diffbuf) {
346 ((linediff_fn)priv)(diffbuf, buflen); 353 ((linediff_fn)priv)(diffbuf, buflen);
347 free(diffbuf); 354 free(diffbuf);
348 diffbuf = NULL; 355 diffbuf = NULL;
349 buflen = 0; 356 buflen = 0;
350 } 357 }
351 return 0; 358 return 0;
352} 359}
353 360
354int cgit_diff_files(const unsigned char *old_sha1, 361int cgit_diff_files(const unsigned char *old_sha1,
355 const unsigned char *new_sha1, 362 const unsigned char *new_sha1,
356 linediff_fn fn) 363 linediff_fn fn)
357{ 364{
358 mmfile_t file1, file2; 365 mmfile_t file1, file2;
359 xpparam_t diff_params; 366 xpparam_t diff_params;
360 xdemitconf_t emit_params; 367 xdemitconf_t emit_params;
361 xdemitcb_t emit_cb; 368 xdemitcb_t emit_cb;
362 369
363 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 370 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
364 return 1; 371 return 1;
365 372
366 diff_params.flags = XDF_NEED_MINIMAL; 373 diff_params.flags = XDF_NEED_MINIMAL;
367 emit_params.ctxlen = 3; 374 emit_params.ctxlen = 3;
368 emit_params.flags = XDL_EMIT_FUNCNAMES; 375 emit_params.flags = XDL_EMIT_FUNCNAMES;
369 emit_cb.outf = filediff_cb; 376 emit_cb.outf = filediff_cb;
370 emit_cb.priv = fn; 377 emit_cb.priv = fn;
371 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 378 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
372 return 0; 379 return 0;
373} 380}
374 381
375void cgit_diff_tree(const unsigned char *old_sha1, 382void cgit_diff_tree(const unsigned char *old_sha1,
376 const unsigned char *new_sha1, 383 const unsigned char *new_sha1,
377 filepair_fn fn) 384 filepair_fn fn)
378{ 385{
379 struct diff_options opt; 386 struct diff_options opt;
380 int ret; 387 int ret;
381 388
382 diff_setup(&opt); 389 diff_setup(&opt);
383 opt.output_format = DIFF_FORMAT_CALLBACK; 390 opt.output_format = DIFF_FORMAT_CALLBACK;
384 opt.detect_rename = 1; 391 opt.detect_rename = 1;
385 opt.recursive = 1; 392 opt.recursive = 1;
386 opt.format_callback = cgit_diff_tree_cb; 393 opt.format_callback = cgit_diff_tree_cb;
387 opt.format_callback_data = fn; 394 opt.format_callback_data = fn;
388 diff_setup_done(&opt); 395 diff_setup_done(&opt);
389 396
390 if (old_sha1 && !is_null_sha1(old_sha1)) 397 if (old_sha1 && !is_null_sha1(old_sha1))
391 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 398 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
392 else 399 else
393 ret = diff_root_tree_sha1(new_sha1, "", &opt); 400 ret = diff_root_tree_sha1(new_sha1, "", &opt);
394 diffcore_std(&opt); 401 diffcore_std(&opt);
395 diff_flush(&opt); 402 diff_flush(&opt);
396} 403}
397 404
398void cgit_diff_commit(struct commit *commit, filepair_fn fn) 405void cgit_diff_commit(struct commit *commit, filepair_fn fn)
399{ 406{
400 unsigned char *old_sha1 = NULL; 407 unsigned char *old_sha1 = NULL;
401 408
402 if (commit->parents) 409 if (commit->parents)
403 old_sha1 = commit->parents->item->object.sha1; 410 old_sha1 = commit->parents->item->object.sha1;
404 cgit_diff_tree(old_sha1, commit->object.sha1, fn); 411 cgit_diff_tree(old_sha1, commit->object.sha1, fn);
405} 412}
diff --git a/ui-commit.c b/ui-commit.c
index 2679b59..50e9e11 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,228 +1,225 @@
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 10
11static int files, slots; 11static int files, slots;
12static int total_adds, total_rems, max_changes; 12static int total_adds, total_rems, max_changes;
13static int lines_added, lines_removed; 13static int lines_added, lines_removed;
14static char *curr_rev; 14static char *curr_rev;
15 15
16static struct fileinfo { 16static struct fileinfo {
17 char status; 17 char status;
18 unsigned char old_sha1[20]; 18 unsigned char old_sha1[20];
19 unsigned char new_sha1[20]; 19 unsigned char new_sha1[20];
20 unsigned short old_mode; 20 unsigned short old_mode;
21 unsigned short new_mode; 21 unsigned short new_mode;
22 char *old_path; 22 char *old_path;
23 char *new_path; 23 char *new_path;
24 unsigned int added; 24 unsigned int added;
25 unsigned int removed; 25 unsigned int removed;
26} *items; 26} *items;
27 27
28 28
29void print_fileinfo(struct fileinfo *info) 29void print_fileinfo(struct fileinfo *info)
30{ 30{
31 char *class; 31 char *class;
32 32
33 switch (info->status) { 33 switch (info->status) {
34 case DIFF_STATUS_ADDED: 34 case DIFF_STATUS_ADDED:
35 class = "add"; 35 class = "add";
36 break; 36 break;
37 case DIFF_STATUS_COPIED: 37 case DIFF_STATUS_COPIED:
38 class = "cpy"; 38 class = "cpy";
39 break; 39 break;
40 case DIFF_STATUS_DELETED: 40 case DIFF_STATUS_DELETED:
41 class = "del"; 41 class = "del";
42 break; 42 break;
43 case DIFF_STATUS_MODIFIED: 43 case DIFF_STATUS_MODIFIED:
44 class = "upd"; 44 class = "upd";
45 break; 45 break;
46 case DIFF_STATUS_RENAMED: 46 case DIFF_STATUS_RENAMED:
47 class = "mov"; 47 class = "mov";
48 break; 48 break;
49 case DIFF_STATUS_TYPE_CHANGED: 49 case DIFF_STATUS_TYPE_CHANGED:
50 class = "typ"; 50 class = "typ";
51 break; 51 break;
52 case DIFF_STATUS_UNKNOWN: 52 case DIFF_STATUS_UNKNOWN:
53 class = "unk"; 53 class = "unk";
54 break; 54 break;
55 case DIFF_STATUS_UNMERGED: 55 case DIFF_STATUS_UNMERGED:
56 class = "stg"; 56 class = "stg";
57 break; 57 break;
58 default: 58 default:
59 die("bug: unhandled diff status %c", info->status); 59 die("bug: unhandled diff status %c", info->status);
60 } 60 }
61 61
62 html("<tr>"); 62 html("<tr>");
63 htmlf("<td class='mode'>"); 63 htmlf("<td class='mode'>");
64 if (is_null_sha1(info->new_sha1)) { 64 if (is_null_sha1(info->new_sha1)) {
65 html_filemode(info->old_mode); 65 html_filemode(info->old_mode);
66 } else { 66 } else {
67 html_filemode(info->new_mode); 67 html_filemode(info->new_mode);
68 } 68 }
69 69
70 if (info->old_mode != info->new_mode && 70 if (info->old_mode != info->new_mode &&
71 !is_null_sha1(info->old_sha1) && 71 !is_null_sha1(info->old_sha1) &&
72 !is_null_sha1(info->new_sha1)) { 72 !is_null_sha1(info->new_sha1)) {
73 html("<span class='modechange'>["); 73 html("<span class='modechange'>[");
74 html_filemode(info->old_mode); 74 html_filemode(info->old_mode);
75 html("]</span>"); 75 html("]</span>");
76 } 76 }
77 htmlf("</td><td class='%s'>", class); 77 htmlf("</td><td class='%s'>", class);
78 cgit_tree_link(info->new_path, NULL, NULL, cgit_query_head, curr_rev, 78 cgit_tree_link(info->new_path, NULL, NULL, cgit_query_head, curr_rev,
79 info->new_path); 79 info->new_path);
80 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 80 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
81 htmlf(" (%s from %s)", 81 htmlf(" (%s from %s)",
82 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 82 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
83 info->old_path); 83 info->old_path);
84 html("</td><td class='right'>"); 84 html("</td><td class='right'>");
85 htmlf("%d", info->added + info->removed); 85 htmlf("%d", info->added + info->removed);
86 html("</td><td class='graph'>"); 86 html("</td><td class='graph'>");
87 htmlf("<table width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 87 htmlf("<table width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
88 htmlf("<td class='add' style='width: %.1f%%;'/>", 88 htmlf("<td class='add' style='width: %.1f%%;'/>",
89 info->added * 100.0 / max_changes); 89 info->added * 100.0 / max_changes);
90 htmlf("<td class='rem' style='width: %.1f%%;'/>", 90 htmlf("<td class='rem' style='width: %.1f%%;'/>",
91 info->removed * 100.0 / max_changes); 91 info->removed * 100.0 / max_changes);
92 htmlf("<td class='none' style='width: %.1f%%;'/>", 92 htmlf("<td class='none' style='width: %.1f%%;'/>",
93 (max_changes - info->removed - info->added) * 100.0 / max_changes); 93 (max_changes - info->removed - info->added) * 100.0 / max_changes);
94 html("</tr></table></td></tr>\n"); 94 html("</tr></table></td></tr>\n");
95} 95}
96 96
97void cgit_count_diff_lines(char *line, int len) 97void cgit_count_diff_lines(char *line, int len)
98{ 98{
99 if (line && (len > 0)) { 99 if (line && (len > 0)) {
100 if (line[0] == '+') 100 if (line[0] == '+')
101 lines_added++; 101 lines_added++;
102 else if (line[0] == '-') 102 else if (line[0] == '-')
103 lines_removed++; 103 lines_removed++;
104 } 104 }
105} 105}
106 106
107void inspect_filepair(struct diff_filepair *pair) 107void inspect_filepair(struct diff_filepair *pair)
108{ 108{
109 files++; 109 files++;
110 lines_added = 0; 110 lines_added = 0;
111 lines_removed = 0; 111 lines_removed = 0;
112 cgit_diff_files(pair->one->sha1, pair->two->sha1, cgit_count_diff_lines); 112 cgit_diff_files(pair->one->sha1, pair->two->sha1, cgit_count_diff_lines);
113 if (files >= slots) { 113 if (files >= slots) {
114 if (slots == 0) 114 if (slots == 0)
115 slots = 4; 115 slots = 4;
116 else 116 else
117 slots = slots * 2; 117 slots = slots * 2;
118 items = xrealloc(items, slots * sizeof(struct fileinfo)); 118 items = xrealloc(items, slots * sizeof(struct fileinfo));
119 } 119 }
120 items[files-1].status = pair->status; 120 items[files-1].status = pair->status;
121 hashcpy(items[files-1].old_sha1, pair->one->sha1); 121 hashcpy(items[files-1].old_sha1, pair->one->sha1);
122 hashcpy(items[files-1].new_sha1, pair->two->sha1); 122 hashcpy(items[files-1].new_sha1, pair->two->sha1);
123 items[files-1].old_mode = pair->one->mode; 123 items[files-1].old_mode = pair->one->mode;
124 items[files-1].new_mode = pair->two->mode; 124 items[files-1].new_mode = pair->two->mode;
125 items[files-1].old_path = xstrdup(pair->one->path); 125 items[files-1].old_path = xstrdup(pair->one->path);
126 items[files-1].new_path = xstrdup(pair->two->path); 126 items[files-1].new_path = xstrdup(pair->two->path);
127 items[files-1].added = lines_added; 127 items[files-1].added = lines_added;
128 items[files-1].removed = lines_removed; 128 items[files-1].removed = lines_removed;
129 if (lines_added + lines_removed > max_changes) 129 if (lines_added + lines_removed > max_changes)
130 max_changes = lines_added + lines_removed; 130 max_changes = lines_added + lines_removed;
131 total_adds += lines_added; 131 total_adds += lines_added;
132 total_rems += lines_removed; 132 total_rems += lines_removed;
133} 133}
134 134
135 135
136void cgit_print_commit(char *hex) 136void cgit_print_commit(char *hex)
137{ 137{
138 struct commit *commit, *parent; 138 struct commit *commit, *parent;
139 struct commitinfo *info; 139 struct commitinfo *info;
140 struct commit_list *p; 140 struct commit_list *p;
141 unsigned char sha1[20]; 141 unsigned char sha1[20];
142 char *filename;
143 char *tmp; 142 char *tmp;
144 int i; 143 int i;
145 144
146 if (!hex) 145 if (!hex)
147 hex = cgit_query_head; 146 hex = cgit_query_head;
148 curr_rev = hex; 147 curr_rev = hex;
149 148
150 if (get_sha1(hex, sha1)) { 149 if (get_sha1(hex, sha1)) {
151 cgit_print_error(fmt("Bad object id: %s", hex)); 150 cgit_print_error(fmt("Bad object id: %s", hex));
152 return; 151 return;
153 } 152 }
154 commit = lookup_commit_reference(sha1); 153 commit = lookup_commit_reference(sha1);
155 if (!commit) { 154 if (!commit) {
156 cgit_print_error(fmt("Bad commit reference: %s", hex)); 155 cgit_print_error(fmt("Bad commit reference: %s", hex));
157 return; 156 return;
158 } 157 }
159 info = cgit_parse_commit(commit); 158 info = cgit_parse_commit(commit);
160 159
161 html("<table class='commit-info'>\n"); 160 html("<table class='commit-info'>\n");
162 html("<tr><th>author</th><td>"); 161 html("<tr><th>author</th><td>");
163 html_txt(info->author); 162 html_txt(info->author);
164 html(" "); 163 html(" ");
165 html_txt(info->author_email); 164 html_txt(info->author_email);
166 html("</td><td class='right'>"); 165 html("</td><td class='right'>");
167 cgit_print_date(info->author_date, FMT_LONGDATE); 166 cgit_print_date(info->author_date, FMT_LONGDATE);
168 html("</td></tr>\n"); 167 html("</td></tr>\n");
169 html("<tr><th>committer</th><td>"); 168 html("<tr><th>committer</th><td>");
170 html_txt(info->committer); 169 html_txt(info->committer);
171 html(" "); 170 html(" ");
172 html_txt(info->committer_email); 171 html_txt(info->committer_email);
173 html("</td><td class='right'>"); 172 html("</td><td class='right'>");
174 cgit_print_date(info->committer_date, FMT_LONGDATE); 173 cgit_print_date(info->committer_date, FMT_LONGDATE);
175 html("</td></tr>\n"); 174 html("</td></tr>\n");
176 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 175 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
177 tmp = xstrdup(hex); 176 tmp = xstrdup(hex);
178 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 177 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
179 cgit_query_head, tmp, NULL); 178 cgit_query_head, tmp, NULL);
180 html("</td></tr>\n"); 179 html("</td></tr>\n");
181 for (p = commit->parents; p ; p = p->next) { 180 for (p = commit->parents; p ; p = p->next) {
182 parent = lookup_commit_reference(p->item->object.sha1); 181 parent = lookup_commit_reference(p->item->object.sha1);
183 if (!parent) { 182 if (!parent) {
184 html("<tr><td colspan='3'>"); 183 html("<tr><td colspan='3'>");
185 cgit_print_error("Error reading parent commit"); 184 cgit_print_error("Error reading parent commit");
186 html("</td></tr>"); 185 html("</td></tr>");
187 continue; 186 continue;
188 } 187 }
189 html("<tr><th>parent</th>" 188 html("<tr><th>parent</th>"
190 "<td colspan='2' class='sha1'>"); 189 "<td colspan='2' class='sha1'>");
191 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 190 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
192 cgit_query_head, sha1_to_hex(p->item->object.sha1)); 191 cgit_query_head, sha1_to_hex(p->item->object.sha1));
193 html(" ("); 192 html(" (");
194 cgit_diff_link("diff", NULL, NULL, cgit_query_head, hex, 193 cgit_diff_link("diff", NULL, NULL, cgit_query_head, hex,
195 sha1_to_hex(p->item->object.sha1), NULL); 194 sha1_to_hex(p->item->object.sha1), NULL);
196 html(")</td></tr>"); 195 html(")</td></tr>");
197 } 196 }
198 if (cgit_repo->snapshots) { 197 if (cgit_repo->snapshots) {
199 htmlf("<tr><th>download</th><td colspan='2' class='sha1'><a href='"); 198 html("<tr><th>download</th><td colspan='2' class='sha1'>");
200 filename = fmt("%s-%s.zip", cgit_query_repo, hex); 199 cgit_print_snapshot_links(cgit_query_repo,hex,cgit_repo->snapshots);
201 html_attr(cgit_pageurl(cgit_query_repo, "snapshot", 200 html("</td></tr>");
202 fmt("id=%s&amp;name=%s", hex, filename)));
203 htmlf("'>%s</a></td></tr>", filename);
204 } 201 }
205 html("</table>\n"); 202 html("</table>\n");
206 html("<div class='commit-subject'>"); 203 html("<div class='commit-subject'>");
207 html_txt(info->subject); 204 html_txt(info->subject);
208 html("</div>"); 205 html("</div>");
209 html("<div class='commit-msg'>"); 206 html("<div class='commit-msg'>");
210 html_txt(info->msg); 207 html_txt(info->msg);
211 html("</div>"); 208 html("</div>");
212 if (!(commit->parents && commit->parents->next && commit->parents->next->next)) { 209 if (!(commit->parents && commit->parents->next && commit->parents->next->next)) {
213 html("<div class='diffstat-header'>Diffstat</div>"); 210 html("<div class='diffstat-header'>Diffstat</div>");
214 html("<table class='diffstat'>"); 211 html("<table class='diffstat'>");
215 max_changes = 0; 212 max_changes = 0;
216 cgit_diff_commit(commit, inspect_filepair); 213 cgit_diff_commit(commit, inspect_filepair);
217 for(i = 0; i<files; i++) 214 for(i = 0; i<files; i++)
218 print_fileinfo(&items[i]); 215 print_fileinfo(&items[i]);
219 html("</table>"); 216 html("</table>");
220 html("<div class='diffstat-summary'>"); 217 html("<div class='diffstat-summary'>");
221 htmlf("%d files changed, %d insertions, %d deletions (", 218 htmlf("%d files changed, %d insertions, %d deletions (",
222 files, total_adds, total_rems); 219 files, total_adds, total_rems);
223 cgit_diff_link("show diff", NULL, NULL, cgit_query_head, hex, 220 cgit_diff_link("show diff", NULL, NULL, cgit_query_head, hex,
224 NULL, NULL); 221 NULL, NULL);
225 html(")</div>"); 222 html(")</div>");
226 } 223 }
227 cgit_free_commitinfo(info); 224 cgit_free_commitinfo(info);
228} 225}
diff --git a/ui-shared.c b/ui-shared.c
index fd71c12..ca2ee82 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,394 +1,427 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11const char cgit_doctype[] = 11const char cgit_doctype[] =
12"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 12"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
13" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 13" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
14 14
15static char *http_date(time_t t) 15static char *http_date(time_t t)
16{ 16{
17 static char day[][4] = 17 static char day[][4] =
18 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 18 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
19 static char month[][4] = 19 static char month[][4] =
20 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 20 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
21 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 21 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
22 struct tm *tm = gmtime(&t); 22 struct tm *tm = gmtime(&t);
23 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 23 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
24 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 24 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
25 tm->tm_hour, tm->tm_min, tm->tm_sec); 25 tm->tm_hour, tm->tm_min, tm->tm_sec);
26} 26}
27 27
28static long ttl_seconds(long ttl) 28static long ttl_seconds(long ttl)
29{ 29{
30 if (ttl<0) 30 if (ttl<0)
31 return 60 * 60 * 24 * 365; 31 return 60 * 60 * 24 * 365;
32 else 32 else
33 return ttl * 60; 33 return ttl * 60;
34} 34}
35 35
36void cgit_print_error(char *msg) 36void cgit_print_error(char *msg)
37{ 37{
38 html("<div class='error'>"); 38 html("<div class='error'>");
39 html_txt(msg); 39 html_txt(msg);
40 html("</div>\n"); 40 html("</div>\n");
41} 41}
42 42
43char *cgit_rooturl() 43char *cgit_rooturl()
44{ 44{
45 if (cgit_virtual_root) 45 if (cgit_virtual_root)
46 return fmt("%s/", cgit_virtual_root); 46 return fmt("%s/", cgit_virtual_root);
47 else 47 else
48 return cgit_script_name; 48 return cgit_script_name;
49} 49}
50 50
51char *cgit_repourl(const char *reponame) 51char *cgit_repourl(const char *reponame)
52{ 52{
53 if (cgit_virtual_root) { 53 if (cgit_virtual_root) {
54 return fmt("%s/%s/", cgit_virtual_root, reponame); 54 return fmt("%s/%s/", cgit_virtual_root, reponame);
55 } else { 55 } else {
56 return fmt("?r=%s", reponame); 56 return fmt("?r=%s", reponame);
57 } 57 }
58} 58}
59 59
60char *cgit_pageurl(const char *reponame, const char *pagename, 60char *cgit_fileurl(const char *reponame, const char *pagename,
61 const char *query) 61 const char *filename, const char *query)
62{ 62{
63 if (cgit_virtual_root) { 63 if (cgit_virtual_root) {
64 if (query) 64 if (query)
65 return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, 65 return fmt("%s/%s/%s/%s?%s", cgit_virtual_root, reponame,
66 pagename, query); 66 pagename, filename?filename:"", query);
67 else 67 else
68 return fmt("%s/%s/%s/", cgit_virtual_root, reponame, 68 return fmt("%s/%s/%s/", cgit_virtual_root, reponame,
69 pagename); 69 pagename);
70 } else { 70 } else {
71 if (query) 71 if (query)
72 return fmt("?r=%s&amp;p=%s&amp;%s", reponame, pagename, query); 72 return fmt("?r=%s&amp;p=%s&amp;%s", reponame, pagename, query);
73 else 73 else
74 return fmt("?r=%s&amp;p=%s", reponame, pagename); 74 return fmt("?r=%s&amp;p=%s", reponame, pagename);
75 } 75 }
76} 76}
77 77
78char *cgit_pageurl(const char *reponame, const char *pagename,
79 const char *query)
80{
81 return cgit_fileurl(reponame,pagename,0,query);
82}
83
84const char *cgit_repobasename(const char *reponame)
85{
86 /* I assume we don't need to store more than one repo basename */
87 static char rvbuf[1024];
88 int p;
89 const char *rv;
90 strncpy(rvbuf,reponame,sizeof(rvbuf));
91 if(rvbuf[sizeof(rvbuf)-1])
92 die("cgit_repobasename: truncated repository name '%s'", reponame);
93 p = strlen(rvbuf)-1;
94 /* strip trailing slashes */
95 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
96 /* strip trailing .git */
97 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
98 p -= 3; rvbuf[p--] = 0;
99 }
100 /* strip more trailing slashes if any */
101 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
102 /* find last slash in the remaining string */
103 rv = strrchr(rvbuf,'/');
104 if(rv)
105 return ++rv;
106 return rvbuf;
107}
108
78char *cgit_currurl() 109char *cgit_currurl()
79{ 110{
80 if (!cgit_virtual_root) 111 if (!cgit_virtual_root)
81 return cgit_script_name; 112 return cgit_script_name;
82 else if (cgit_query_page) 113 else if (cgit_query_page)
83 return fmt("%s/%s/%s/", cgit_virtual_root, cgit_query_repo, cgit_query_page); 114 return fmt("%s/%s/%s/", cgit_virtual_root, cgit_query_repo, cgit_query_page);
84 else if (cgit_query_repo) 115 else if (cgit_query_repo)
85 return fmt("%s/%s/", cgit_virtual_root, cgit_query_repo); 116 return fmt("%s/%s/", cgit_virtual_root, cgit_query_repo);
86 else 117 else
87 return fmt("%s/", cgit_virtual_root); 118 return fmt("%s/", cgit_virtual_root);
88} 119}
89 120
90static char *repolink(char *title, char *class, char *page, char *head, 121static char *repolink(char *title, char *class, char *page, char *head,
91 char *path) 122 char *path)
92{ 123{
93 char *delim = "?"; 124 char *delim = "?";
94 125
95 html("<a"); 126 html("<a");
96 if (title) { 127 if (title) {
97 html(" title='"); 128 html(" title='");
98 html_attr(title); 129 html_attr(title);
99 html("'"); 130 html("'");
100 } 131 }
101 if (class) { 132 if (class) {
102 html(" class='"); 133 html(" class='");
103 html_attr(class); 134 html_attr(class);
104 html("'"); 135 html("'");
105 } 136 }
106 html(" href='"); 137 html(" href='");
107 if (cgit_virtual_root) { 138 if (cgit_virtual_root) {
108 html_attr(cgit_virtual_root); 139 html_attr(cgit_virtual_root);
109 if (cgit_virtual_root[strlen(cgit_virtual_root) - 1] != '/') 140 if (cgit_virtual_root[strlen(cgit_virtual_root) - 1] != '/')
110 html("/"); 141 html("/");
111 html_attr(cgit_repo->url); 142 html_attr(cgit_repo->url);
112 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/') 143 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/')
113 html("/"); 144 html("/");
114 if (page) { 145 if (page) {
115 html(page); 146 html(page);
116 html("/"); 147 html("/");
117 if (path) 148 if (path)
118 html_attr(path); 149 html_attr(path);
119 } 150 }
120 } else { 151 } else {
121 html(cgit_script_name); 152 html(cgit_script_name);
122 html("?url="); 153 html("?url=");
123 html_attr(cgit_repo->url); 154 html_attr(cgit_repo->url);
124 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/') 155 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/')
125 html("/"); 156 html("/");
126 if (page) { 157 if (page) {
127 html(page); 158 html(page);
128 html("/"); 159 html("/");
129 if (path) 160 if (path)
130 html_attr(path); 161 html_attr(path);
131 } 162 }
132 delim = "&amp;"; 163 delim = "&amp;";
133 } 164 }
134 if (head && strcmp(head, cgit_repo->defbranch)) { 165 if (head && strcmp(head, cgit_repo->defbranch)) {
135 html(delim); 166 html(delim);
136 html("h="); 167 html("h=");
137 html_attr(head); 168 html_attr(head);
138 delim = "&amp;"; 169 delim = "&amp;";
139 } 170 }
140 return fmt("%s", delim); 171 return fmt("%s", delim);
141} 172}
142 173
143static void reporevlink(char *page, char *name, char *title, char *class, 174static void reporevlink(char *page, char *name, char *title, char *class,
144 char *head, char *rev, char *path) 175 char *head, char *rev, char *path)
145{ 176{
146 char *delim; 177 char *delim;
147 178
148 delim = repolink(title, class, page, head, path); 179 delim = repolink(title, class, page, head, path);
149 if (rev && strcmp(rev, cgit_query_head)) { 180 if (rev && strcmp(rev, cgit_query_head)) {
150 html(delim); 181 html(delim);
151 html("id="); 182 html("id=");
152 html_attr(rev); 183 html_attr(rev);
153 } 184 }
154 html("'>"); 185 html("'>");
155 html_txt(name); 186 html_txt(name);
156 html("</a>"); 187 html("</a>");
157} 188}
158 189
159void cgit_tree_link(char *name, char *title, char *class, char *head, 190void cgit_tree_link(char *name, char *title, char *class, char *head,
160 char *rev, char *path) 191 char *rev, char *path)
161{ 192{
162 reporevlink("tree", name, title, class, head, rev, path); 193 reporevlink("tree", name, title, class, head, rev, path);
163} 194}
164 195
165void cgit_log_link(char *name, char *title, char *class, char *head, 196void cgit_log_link(char *name, char *title, char *class, char *head,
166 char *rev, char *path, int ofs) 197 char *rev, char *path, int ofs)
167{ 198{
168 char *delim; 199 char *delim;
169 200
170 delim = repolink(title, class, "log", head, path); 201 delim = repolink(title, class, "log", head, path);
171 if (rev && strcmp(rev, cgit_query_head)) { 202 if (rev && strcmp(rev, cgit_query_head)) {
172 html(delim); 203 html(delim);
173 html("id="); 204 html("id=");
174 html_attr(rev); 205 html_attr(rev);
175 delim = "&"; 206 delim = "&";
176 } 207 }
177 if (ofs > 0) { 208 if (ofs > 0) {
178 html(delim); 209 html(delim);
179 html("ofs="); 210 html("ofs=");
180 htmlf("%d", ofs); 211 htmlf("%d", ofs);
181 } 212 }
182 html("'>"); 213 html("'>");
183 html_txt(name); 214 html_txt(name);
184 html("</a>"); 215 html("</a>");
185} 216}
186 217
187void cgit_commit_link(char *name, char *title, char *class, char *head, 218void cgit_commit_link(char *name, char *title, char *class, char *head,
188 char *rev) 219 char *rev)
189{ 220{
190 if (strlen(name) > cgit_max_msg_len && cgit_max_msg_len >= 15) { 221 if (strlen(name) > cgit_max_msg_len && cgit_max_msg_len >= 15) {
191 name[cgit_max_msg_len] = '\0'; 222 name[cgit_max_msg_len] = '\0';
192 name[cgit_max_msg_len - 1] = '.'; 223 name[cgit_max_msg_len - 1] = '.';
193 name[cgit_max_msg_len - 2] = '.'; 224 name[cgit_max_msg_len - 2] = '.';
194 name[cgit_max_msg_len - 3] = '.'; 225 name[cgit_max_msg_len - 3] = '.';
195 } 226 }
196 reporevlink("commit", name, title, class, head, rev, NULL); 227 reporevlink("commit", name, title, class, head, rev, NULL);
197} 228}
198 229
199void cgit_diff_link(char *name, char *title, char *class, char *head, 230void cgit_diff_link(char *name, char *title, char *class, char *head,
200 char *new_rev, char *old_rev, char *path) 231 char *new_rev, char *old_rev, char *path)
201{ 232{
202 char *delim; 233 char *delim;
203 234
204 delim = repolink(title, class, "diff", head, path); 235 delim = repolink(title, class, "diff", head, path);
205 if (new_rev && strcmp(new_rev, cgit_query_head)) { 236 if (new_rev && strcmp(new_rev, cgit_query_head)) {
206 html(delim); 237 html(delim);
207 html("id="); 238 html("id=");
208 html_attr(new_rev); 239 html_attr(new_rev);
209 delim = "&amp;"; 240 delim = "&amp;";
210 } 241 }
211 if (old_rev) { 242 if (old_rev) {
212 html(delim); 243 html(delim);
213 html("id2="); 244 html("id2=");
214 html_attr(old_rev); 245 html_attr(old_rev);
215 } 246 }
216 html("'>"); 247 html("'>");
217 html_txt(name); 248 html_txt(name);
218 html("</a>"); 249 html("</a>");
219} 250}
220 251
221void cgit_object_link(struct object *obj) 252void cgit_object_link(struct object *obj)
222{ 253{
223 char *page, *arg, *url; 254 char *page, *arg, *url;
224 255
225 if (obj->type == OBJ_COMMIT) { 256 if (obj->type == OBJ_COMMIT) {
226 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, 257 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
227 cgit_query_head, sha1_to_hex(obj->sha1)); 258 cgit_query_head, sha1_to_hex(obj->sha1));
228 return; 259 return;
229 } else if (obj->type == OBJ_TREE) { 260 } else if (obj->type == OBJ_TREE) {
230 page = "tree"; 261 page = "tree";
231 arg = "id"; 262 arg = "id";
232 } else { 263 } else {
233 page = "blob"; 264 page = "blob";
234 arg = "id"; 265 arg = "id";
235 } 266 }
236 267
237 url = cgit_pageurl(cgit_query_repo, page, 268 url = cgit_pageurl(cgit_query_repo, page,
238 fmt("%s=%s", arg, sha1_to_hex(obj->sha1))); 269 fmt("%s=%s", arg, sha1_to_hex(obj->sha1)));
239 html_link_open(url, NULL, NULL); 270 html_link_open(url, NULL, NULL);
240 htmlf("%s %s", typename(obj->type), 271 htmlf("%s %s", typename(obj->type),
241 sha1_to_hex(obj->sha1)); 272 sha1_to_hex(obj->sha1));
242 html_link_close(); 273 html_link_close();
243} 274}
244 275
245void cgit_print_date(time_t secs, char *format) 276void cgit_print_date(time_t secs, char *format)
246{ 277{
247 char buf[64]; 278 char buf[64];
248 struct tm *time; 279 struct tm *time;
249 280
250 time = gmtime(&secs); 281 time = gmtime(&secs);
251 strftime(buf, sizeof(buf)-1, format, time); 282 strftime(buf, sizeof(buf)-1, format, time);
252 html_txt(buf); 283 html_txt(buf);
253} 284}
254 285
255void cgit_print_age(time_t t, time_t max_relative, char *format) 286void cgit_print_age(time_t t, time_t max_relative, char *format)
256{ 287{
257 time_t now, secs; 288 time_t now, secs;
258 289
259 time(&now); 290 time(&now);
260 secs = now - t; 291 secs = now - t;
261 292
262 if (secs > max_relative && max_relative >= 0) { 293 if (secs > max_relative && max_relative >= 0) {
263 cgit_print_date(t, format); 294 cgit_print_date(t, format);
264 return; 295 return;
265 } 296 }
266 297
267 if (secs < TM_HOUR * 2) { 298 if (secs < TM_HOUR * 2) {
268 htmlf("<span class='age-mins'>%.0f min.</span>", 299 htmlf("<span class='age-mins'>%.0f min.</span>",
269 secs * 1.0 / TM_MIN); 300 secs * 1.0 / TM_MIN);
270 return; 301 return;
271 } 302 }
272 if (secs < TM_DAY * 2) { 303 if (secs < TM_DAY * 2) {
273 htmlf("<span class='age-hours'>%.0f hours</span>", 304 htmlf("<span class='age-hours'>%.0f hours</span>",
274 secs * 1.0 / TM_HOUR); 305 secs * 1.0 / TM_HOUR);
275 return; 306 return;
276 } 307 }
277 if (secs < TM_WEEK * 2) { 308 if (secs < TM_WEEK * 2) {
278 htmlf("<span class='age-days'>%.0f days</span>", 309 htmlf("<span class='age-days'>%.0f days</span>",
279 secs * 1.0 / TM_DAY); 310 secs * 1.0 / TM_DAY);
280 return; 311 return;
281 } 312 }
282 if (secs < TM_MONTH * 2) { 313 if (secs < TM_MONTH * 2) {
283 htmlf("<span class='age-weeks'>%.0f weeks</span>", 314 htmlf("<span class='age-weeks'>%.0f weeks</span>",
284 secs * 1.0 / TM_WEEK); 315 secs * 1.0 / TM_WEEK);
285 return; 316 return;
286 } 317 }
287 if (secs < TM_YEAR * 2) { 318 if (secs < TM_YEAR * 2) {
288 htmlf("<span class='age-months'>%.0f months</span>", 319 htmlf("<span class='age-months'>%.0f months</span>",
289 secs * 1.0 / TM_MONTH); 320 secs * 1.0 / TM_MONTH);
290 return; 321 return;
291 } 322 }
292 htmlf("<span class='age-years'>%.0f years</span>", 323 htmlf("<span class='age-years'>%.0f years</span>",
293 secs * 1.0 / TM_YEAR); 324 secs * 1.0 / TM_YEAR);
294} 325}
295 326
296void cgit_print_docstart(char *title, struct cacheitem *item) 327void cgit_print_docstart(char *title, struct cacheitem *item)
297{ 328{
298 html("Content-Type: text/html; charset=utf-8\n"); 329 html("Content-Type: text/html; charset=utf-8\n");
299 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); 330 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime));
300 htmlf("Expires: %s\n", http_date(item->st.st_mtime + 331 htmlf("Expires: %s\n", http_date(item->st.st_mtime +
301 ttl_seconds(item->ttl))); 332 ttl_seconds(item->ttl)));
302 html("\n"); 333 html("\n");
303 html(cgit_doctype); 334 html(cgit_doctype);
304 html("<html>\n"); 335 html("<html>\n");
305 html("<head>\n"); 336 html("<head>\n");
306 html("<title>"); 337 html("<title>");
307 html_txt(title); 338 html_txt(title);
308 html("</title>\n"); 339 html("</title>\n");
309 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 340 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
310 html("<link rel='stylesheet' type='text/css' href='"); 341 html("<link rel='stylesheet' type='text/css' href='");
311 html_attr(cgit_css); 342 html_attr(cgit_css);
312 html("'/>\n"); 343 html("'/>\n");
313 html("</head>\n"); 344 html("</head>\n");
314 html("<body>\n"); 345 html("<body>\n");
315} 346}
316 347
317void cgit_print_docend() 348void cgit_print_docend()
318{ 349{
319 html("</td></tr></table>"); 350 html("</td></tr></table>");
320 html("</body>\n</html>\n"); 351 html("</body>\n</html>\n");
321} 352}
322 353
323void cgit_print_pageheader(char *title, int show_search) 354void cgit_print_pageheader(char *title, int show_search)
324{ 355{
325 html("<table id='layout'>"); 356 html("<table id='layout'>");
326 html("<tr><td id='header'><a href='"); 357 html("<tr><td id='header'><a href='");
327 html_attr(cgit_rooturl()); 358 html_attr(cgit_rooturl());
328 html("'>"); 359 html("'>");
329 html_txt(cgit_root_title); 360 html_txt(cgit_root_title);
330 html("</a></td><td id='logo'>"); 361 html("</a></td><td id='logo'>");
331 html("<a href='"); 362 html("<a href='");
332 html_attr(cgit_logo_link); 363 html_attr(cgit_logo_link);
333 htmlf("'><img src='%s' alt='logo'/></a>", cgit_logo); 364 htmlf("'><img src='%s' alt='logo'/></a>", cgit_logo);
334 html("</td></tr>"); 365 html("</td></tr>");
335 html("<tr><td id='crumb'>"); 366 html("<tr><td id='crumb'>");
336 if (cgit_query_repo) { 367 if (cgit_query_repo) {
337 html_txt(cgit_repo->name); 368 html_txt(cgit_repo->name);
338 html(" ("); 369 html(" (");
339 html_txt(cgit_query_head); 370 html_txt(cgit_query_head);
340 html(") : &nbsp;"); 371 html(") : &nbsp;");
341 reporevlink(NULL, "summary", NULL, NULL, cgit_query_head, 372 reporevlink(NULL, "summary", NULL, NULL, cgit_query_head,
342 NULL, NULL); 373 NULL, NULL);
343 html(" "); 374 html(" ");
344 cgit_log_link("log", NULL, NULL, cgit_query_head, 375 cgit_log_link("log", NULL, NULL, cgit_query_head,
345 cgit_query_sha1, cgit_query_path, 0); 376 cgit_query_sha1, cgit_query_path, 0);
346 html(" "); 377 html(" ");
347 cgit_tree_link("tree", NULL, NULL, cgit_query_head, 378 cgit_tree_link("tree", NULL, NULL, cgit_query_head,
348 cgit_query_sha1, NULL); 379 cgit_query_sha1, NULL);
349 html(" "); 380 html(" ");
350 cgit_commit_link("commit", NULL, NULL, cgit_query_head, 381 cgit_commit_link("commit", NULL, NULL, cgit_query_head,
351 cgit_query_sha1); 382 cgit_query_sha1);
352 html(" "); 383 html(" ");
353 cgit_diff_link("diff", NULL, NULL, cgit_query_head, 384 cgit_diff_link("diff", NULL, NULL, cgit_query_head,
354 cgit_query_sha1, cgit_query_sha2, 385 cgit_query_sha1, cgit_query_sha2,
355 cgit_query_path); 386 cgit_query_path);
356 } else { 387 } else {
357 html_txt("Index of repositories"); 388 html_txt("Index of repositories");
358 } 389 }
359 html("</td>"); 390 html("</td>");
360 html("<td id='search'>"); 391 html("<td id='search'>");
361 if (show_search) { 392 if (show_search) {
362 html("<form method='get' action='"); 393 html("<form method='get' action='");
363 html_attr(cgit_currurl()); 394 html_attr(cgit_currurl());
364 html("'>"); 395 html("'>");
365 if (!cgit_virtual_root) { 396 if (!cgit_virtual_root) {
366 if (cgit_query_repo) 397 if (cgit_query_repo)
367 html_hidden("r", cgit_query_repo); 398 html_hidden("r", cgit_query_repo);
368 if (cgit_query_page) 399 if (cgit_query_page)
369 html_hidden("p", cgit_query_page); 400 html_hidden("p", cgit_query_page);
370 } 401 }
371 if (cgit_query_head) 402 if (cgit_query_head)
372 html_hidden("h", cgit_query_head); 403 html_hidden("h", cgit_query_head);
373 if (cgit_query_sha1) 404 if (cgit_query_sha1)
374 html_hidden("id", cgit_query_sha1); 405 html_hidden("id", cgit_query_sha1);
375 if (cgit_query_sha2) 406 if (cgit_query_sha2)
376 html_hidden("id2", cgit_query_sha2); 407 html_hidden("id2", cgit_query_sha2);
377 html("<input type='text' name='q' value='"); 408 html("<input type='text' name='q' value='");
378 html_attr(cgit_query_search); 409 html_attr(cgit_query_search);
379 html("'/></form>"); 410 html("'/></form>");
380 } 411 }
381 html("</td></tr>"); 412 html("</td></tr>");
382 html("<tr><td id='content' colspan='2'>"); 413 html("<tr><td id='content' colspan='2'>");
383} 414}
384 415
385void cgit_print_snapshot_start(const char *mimetype, const char *filename, 416void cgit_print_snapshot_start(const char *mimetype, const char *filename,
386 struct cacheitem *item) 417 struct cacheitem *item)
387{ 418{
388 htmlf("Content-Type: %s\n", mimetype); 419 htmlf("Content-Type: %s\n", mimetype);
389 htmlf("Content-Disposition: inline; filename=\"%s\"\n", filename); 420 htmlf("Content-Disposition: inline; filename=\"%s\"\n", filename);
390 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); 421 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime));
391 htmlf("Expires: %s\n", http_date(item->st.st_mtime + 422 htmlf("Expires: %s\n", http_date(item->st.st_mtime +
392 ttl_seconds(item->ttl))); 423 ttl_seconds(item->ttl)));
393 html("\n"); 424 html("\n");
394} 425}
426
427/* vim:set sw=8: */
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 2257d6b..d6be55b 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -1,47 +1,145 @@
1/* ui-snapshot.c: generate snapshot of a commit 1/* ui-snapshot.c: generate snapshot of a commit
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
11static void cgit_print_zip(struct cacheitem *item, const char *hex, 11static int write_compressed_tar_archive(struct archiver_args *args,const char *filter)
12 const char *prefix, const char *filename)
13{ 12{
14 struct archiver_args args; 13 int rw[2];
15 struct commit *commit; 14 pid_t gzpid;
16 unsigned char sha1[20]; 15 int stdout2;
16 int status;
17 int rv;
17 18
18 if (get_sha1(hex, sha1)) { 19 stdout2 = chk_non_negative(dup(STDIN_FILENO), "Preserving STDOUT before compressing");
19 cgit_print_error(fmt("Bad object id: %s", hex)); 20 chk_zero(pipe(rw), "Opening pipe from compressor subprocess");
20 return; 21 gzpid = chk_non_negative(fork(), "Forking compressor subprocess");
22 if(gzpid==0) {
23 /* child */
24 chk_zero(close(rw[1]), "Closing write end of pipe in child");
25 chk_zero(close(STDIN_FILENO), "Closing STDIN");
26 chk_non_negative(dup2(rw[0],STDIN_FILENO), "Redirecting compressor input to stdin");
27 execlp(filter,filter,NULL);
28 _exit(-1);
21 } 29 }
22 commit = lookup_commit_reference(sha1); 30 /* parent */
31 chk_zero(close(rw[0]), "Closing read end of pipe");
32 chk_non_negative(dup2(rw[1],STDOUT_FILENO), "Redirecting output to compressor");
33
34 rv = write_tar_archive(args);
23 35
24 if (!commit) { 36 chk_zero(close(STDOUT_FILENO), "Closing STDOUT redirected to compressor");
25 cgit_print_error(fmt("Not a commit reference: %s", hex)); 37 chk_non_negative(dup2(stdout2,STDOUT_FILENO), "Restoring uncompressed STDOUT");
26 return; 38 chk_zero(close(stdout2), "Closing uncompressed STDOUT");
27 } 39 chk_zero(close(rw[1]), "Closing write end of pipe in parent");
40 chk_positive(waitpid(gzpid,&status,0), "Waiting on compressor process");
41 if(! ( WIFEXITED(status) && WEXITSTATUS(status)==0 ) )
42 cgit_print_error("Failed to compress archive");
28 43
29 memset(&args, 0, sizeof(args)); 44 return rv;
30 args.base = fmt("%s/", prefix);
31 args.tree = commit->tree;
32
33 cgit_print_snapshot_start("application/x-zip", filename, item);
34 write_zip_archive(&args);
35} 45}
36 46
47static int write_tar_gzip_archive(struct archiver_args *args)
48{
49 return write_compressed_tar_archive(args,"gzip");
50}
51static int write_tar_bzip2_archive(struct archiver_args *args)
52{
53 return write_compressed_tar_archive(args,"bzip2");
54}
55
56static const struct snapshot_archive_t {
57 const char *suffix;
58 const char *mimetype;
59 write_archive_fn_t write_func;
60 int bit;
61 }snapshot_archives[] = {
62 { ".zip", "application/x-zip", write_zip_archive, 0x1 },
63 { ".tar.gz", "application/x-tar", write_tar_gzip_archive, 0x2 },
64 { ".tar.bz2", "application/x-tar", write_tar_bzip2_archive, 0x4 },
65 { ".tar", "application/x-tar", write_tar_archive, 0x8 }
66};
37 67
38void cgit_print_snapshot(struct cacheitem *item, const char *hex, 68void cgit_print_snapshot(struct cacheitem *item, const char *hex,
39 const char *format, const char *prefix, 69 const char *prefix, const char *filename,
40 const char *filename) 70 int snapshots)
71{
72 int fnl = strlen(filename);
73 int f;
74 for(f=0;f<(sizeof(snapshot_archives)/sizeof(*snapshot_archives));++f) {
75 const struct snapshot_archive_t* sat = &snapshot_archives[f];
76 int sl;
77 if(!(snapshots&sat->bit)) continue;
78 sl = strlen(sat->suffix);
79 if(fnl<sl || strcmp(&filename[fnl-sl],sat->suffix))
80 continue;
81
82 struct archiver_args args;
83 struct commit *commit;
84 unsigned char sha1[20];
85
86 if(get_sha1(hex, sha1)) {
87 cgit_print_error(fmt("Bad object id: %s", hex));
88 return;
89 }
90 commit = lookup_commit_reference(sha1);
91
92 if(!commit) {
93 cgit_print_error(fmt("Not a commit reference: %s", hex));
94 return;;
95 }
96
97 memset(&args,0,sizeof(args));
98 args.base = fmt("%s/", prefix);
99 args.tree = commit->tree;
100
101 cgit_print_snapshot_start(sat->mimetype, filename, item);
102 (*sat->write_func)(&args);
103 return;
104 }
105 cgit_print_error(fmt("Unsupported snapshot format: %s", filename));
106}
107
108void cgit_print_snapshot_links(const char *repo,const char *hex,int snapshots)
41{ 109{
42 if (!strcmp(format, "zip")) 110 char *filename;
43 cgit_print_zip(item, hex, prefix, filename); 111 int f;
44 else 112 for(f=0;f<(sizeof(snapshot_archives)/sizeof(*snapshot_archives));++f) {
45 cgit_print_error(fmt("Unsupported snapshot format: %s", 113 const struct snapshot_archive_t* sat = &snapshot_archives[f];
46 format)); 114 if(!(snapshots&sat->bit)) continue;
115 filename = fmt("%s-%s%s",cgit_repobasename(repo),hex,sat->suffix);
116 htmlf("<a href='%s'>%s</a><br/>",
117 cgit_fileurl(repo,"snapshot",filename,
118 fmt("id=%s&amp;name=%s",hex,filename)), filename);
119 }
120}
121
122int cgit_parse_snapshots_mask(const char *str)
123{
124 static const char *delim = " \t,:/|;";
125 int f, tl, rv = 0;
126 /* favor legacy setting */
127 if(atoi(str)) return 1;
128 for(;;) {
129 str += strspn(str,delim);
130 tl = strcspn(str,delim);
131 if(!tl)
132 break;
133 for(f=0;f<(sizeof(snapshot_archives)/sizeof(*snapshot_archives));++f) {
134 const struct snapshot_archive_t* sat = &snapshot_archives[f];
135 if(! ( strncmp(sat->suffix,str,tl) && strncmp(sat->suffix+1,str,tl-1) ) ) {
136 rv |= sat->bit;
137 break;
138 }
139 }
140 str += tl;
141 }
142 return rv;
47} 143}
144
145/* vim:set sw=8: */
diff --git a/ui-tree.c b/ui-tree.c
index c5d64ff..75ce449 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,209 +1,213 @@
1/* ui-tree.c: functions for tree output 1/* ui-tree.c: functions for tree output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11char *curr_rev; 11char *curr_rev;
12char *match_path; 12char *match_path;
13int header = 0; 13int header = 0;
14 14
15static void print_object(const unsigned char *sha1, char *path) 15static void print_object(const unsigned char *sha1, char *path)
16{ 16{
17 enum object_type type; 17 enum object_type type;
18 unsigned char *buf; 18 unsigned char *buf;
19 unsigned long size, lineno, start, idx; 19 unsigned long size, lineno, start, idx;
20 20
21 type = sha1_object_info(sha1, &size); 21 type = sha1_object_info(sha1, &size);
22 if (type == OBJ_BAD) { 22 if (type == OBJ_BAD) {
23 cgit_print_error(fmt("Bad object name: %s", 23 cgit_print_error(fmt("Bad object name: %s",
24 sha1_to_hex(sha1))); 24 sha1_to_hex(sha1)));
25 return; 25 return;
26 } 26 }
27 27
28 buf = read_sha1_file(sha1, &type, &size); 28 buf = read_sha1_file(sha1, &type, &size);
29 if (!buf) { 29 if (!buf) {
30 cgit_print_error(fmt("Error reading object %s", 30 cgit_print_error(fmt("Error reading object %s",
31 sha1_to_hex(sha1))); 31 sha1_to_hex(sha1)));
32 return; 32 return;
33 } 33 }
34 34
35 html(" blob: <a href='");
36 html_attr(cgit_pageurl(cgit_query_repo, "blob", fmt("id=%s", sha1_to_hex(sha1))));
37 htmlf("'>%s</a>",sha1_to_hex(sha1));
38
35 html("<table class='blob'>\n"); 39 html("<table class='blob'>\n");
36 idx = 0; 40 idx = 0;
37 start = 0; 41 start = 0;
38 lineno = 0; 42 lineno = 0;
39 while(idx < size) { 43 while(idx < size) {
40 if (buf[idx] == '\n') { 44 if (buf[idx] == '\n') {
41 buf[idx] = '\0'; 45 buf[idx] = '\0';
42 htmlf("<tr><td class='no'>%d</td><td class='txt'>", 46 htmlf("<tr><td class='no'>%d</td><td class='txt'>",
43 ++lineno); 47 ++lineno);
44 html_txt(buf + start); 48 html_txt(buf + start);
45 html("</td></tr>\n"); 49 html("</td></tr>\n");
46 start = idx + 1; 50 start = idx + 1;
47 } 51 }
48 idx++; 52 idx++;
49 } 53 }
50 html("</table>\n"); 54 html("</table>\n");
51} 55}
52 56
53 57
54static int ls_item(const unsigned char *sha1, const char *base, int baselen, 58static int ls_item(const unsigned char *sha1, const char *base, int baselen,
55 const char *pathname, unsigned int mode, int stage) 59 const char *pathname, unsigned int mode, int stage)
56{ 60{
57 char *name; 61 char *name;
58 char *fullpath; 62 char *fullpath;
59 enum object_type type; 63 enum object_type type;
60 unsigned long size = 0; 64 unsigned long size = 0;
61 65
62 name = xstrdup(pathname); 66 name = xstrdup(pathname);
63 fullpath = fmt("%s%s%s", cgit_query_path ? cgit_query_path : "", 67 fullpath = fmt("%s%s%s", cgit_query_path ? cgit_query_path : "",
64 cgit_query_path ? "/" : "", name); 68 cgit_query_path ? "/" : "", name);
65 69
66 type = sha1_object_info(sha1, &size); 70 type = sha1_object_info(sha1, &size);
67 if (type == OBJ_BAD && !S_ISDIRLNK(mode)) { 71 if (type == OBJ_BAD && !S_ISDIRLNK(mode)) {
68 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 72 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
69 name, 73 name,
70 sha1_to_hex(sha1)); 74 sha1_to_hex(sha1));
71 return 0; 75 return 0;
72 } 76 }
73 77
74 html("<tr><td class='ls-mode'>"); 78 html("<tr><td class='ls-mode'>");
75 html_filemode(mode); 79 html_filemode(mode);
76 html("</td><td>"); 80 html("</td><td>");
77 if (S_ISDIRLNK(mode)) { 81 if (S_ISDIRLNK(mode)) {
78 htmlf("<a class='ls-mod' href='"); 82 htmlf("<a class='ls-mod' href='");
79 html_attr(fmt(cgit_repo->module_link, 83 html_attr(fmt(cgit_repo->module_link,
80 name, 84 name,
81 sha1_to_hex(sha1))); 85 sha1_to_hex(sha1)));
82 html("'>"); 86 html("'>");
83 html_txt(name); 87 html_txt(name);
84 html("</a>"); 88 html("</a>");
85 } else if (S_ISDIR(mode)) { 89 } else if (S_ISDIR(mode)) {
86 cgit_tree_link(name, NULL, "ls-dir", cgit_query_head, 90 cgit_tree_link(name, NULL, "ls-dir", cgit_query_head,
87 curr_rev, fullpath); 91 curr_rev, fullpath);
88 } else { 92 } else {
89 cgit_tree_link(name, NULL, "ls-blob", cgit_query_head, 93 cgit_tree_link(name, NULL, "ls-blob", cgit_query_head,
90 curr_rev, fullpath); 94 curr_rev, fullpath);
91 } 95 }
92 htmlf("</td><td class='ls-size'>%li</td>", size); 96 htmlf("</td><td class='ls-size'>%li</td>", size);
93 97
94 html("<td>"); 98 html("<td>");
95 cgit_log_link("log", NULL, "button", cgit_query_head, curr_rev, 99 cgit_log_link("log", NULL, "button", cgit_query_head, curr_rev,
96 fullpath, 0); 100 fullpath, 0);
97 html("</td></tr>\n"); 101 html("</td></tr>\n");
98 free(name); 102 free(name);
99 return 0; 103 return 0;
100} 104}
101 105
102static void ls_head() 106static void ls_head()
103{ 107{
104 html("<table class='list'>\n"); 108 html("<table class='list'>\n");
105 html("<tr class='nohover'>"); 109 html("<tr class='nohover'>");
106 html("<th class='left'>Mode</th>"); 110 html("<th class='left'>Mode</th>");
107 html("<th class='left'>Name</th>"); 111 html("<th class='left'>Name</th>");
108 html("<th class='right'>Size</th>"); 112 html("<th class='right'>Size</th>");
109 html("<th/>"); 113 html("<th/>");
110 html("</tr>\n"); 114 html("</tr>\n");
111 header = 1; 115 header = 1;
112} 116}
113 117
114static void ls_tail() 118static void ls_tail()
115{ 119{
116 if (!header) 120 if (!header)
117 return; 121 return;
118 html("</table>\n"); 122 html("</table>\n");
119 header = 0; 123 header = 0;
120} 124}
121 125
122static void ls_tree(const unsigned char *sha1, char *path) 126static void ls_tree(const unsigned char *sha1, char *path)
123{ 127{
124 struct tree *tree; 128 struct tree *tree;
125 129
126 tree = parse_tree_indirect(sha1); 130 tree = parse_tree_indirect(sha1);
127 if (!tree) { 131 if (!tree) {
128 cgit_print_error(fmt("Not a tree object: %s", 132 cgit_print_error(fmt("Not a tree object: %s",
129 sha1_to_hex(sha1))); 133 sha1_to_hex(sha1)));
130 return; 134 return;
131 } 135 }
132 136
133 ls_head(); 137 ls_head();
134 read_tree_recursive(tree, "", 0, 1, NULL, ls_item); 138 read_tree_recursive(tree, "", 0, 1, NULL, ls_item);
135 ls_tail(); 139 ls_tail();
136} 140}
137 141
138 142
139static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 143static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
140 const char *pathname, unsigned mode, int stage) 144 const char *pathname, unsigned mode, int stage)
141{ 145{
142 static int state; 146 static int state;
143 static char buffer[PATH_MAX]; 147 static char buffer[PATH_MAX];
144 char *url; 148 char *url;
145 149
146 if (state == 0) { 150 if (state == 0) {
147 memcpy(buffer, base, baselen); 151 memcpy(buffer, base, baselen);
148 strcpy(buffer+baselen, pathname); 152 strcpy(buffer+baselen, pathname);
149 url = cgit_pageurl(cgit_query_repo, "tree", 153 url = cgit_pageurl(cgit_query_repo, "tree",
150 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 154 fmt("h=%s&amp;path=%s", curr_rev, buffer));
151 html("/"); 155 html("/");
152 cgit_tree_link(xstrdup(pathname), NULL, NULL, cgit_query_head, 156 cgit_tree_link(xstrdup(pathname), NULL, NULL, cgit_query_head,
153 curr_rev, buffer); 157 curr_rev, buffer);
154 158
155 if (strcmp(match_path, buffer)) 159 if (strcmp(match_path, buffer))
156 return READ_TREE_RECURSIVE; 160 return READ_TREE_RECURSIVE;
157 161
158 if (S_ISDIR(mode)) { 162 if (S_ISDIR(mode)) {
159 state = 1; 163 state = 1;
160 ls_head(); 164 ls_head();
161 return READ_TREE_RECURSIVE; 165 return READ_TREE_RECURSIVE;
162 } else { 166 } else {
163 print_object(sha1, buffer); 167 print_object(sha1, buffer);
164 return 0; 168 return 0;
165 } 169 }
166 } 170 }
167 ls_item(sha1, base, baselen, pathname, mode, stage); 171 ls_item(sha1, base, baselen, pathname, mode, stage);
168 return 0; 172 return 0;
169} 173}
170 174
171 175
172/* 176/*
173 * Show a tree or a blob 177 * Show a tree or a blob
174 * rev: the commit pointing at the root tree object 178 * rev: the commit pointing at the root tree object
175 * path: path to tree or blob 179 * path: path to tree or blob
176 */ 180 */
177void cgit_print_tree(const char *rev, char *path) 181void cgit_print_tree(const char *rev, char *path)
178{ 182{
179 unsigned char sha1[20]; 183 unsigned char sha1[20];
180 struct commit *commit; 184 struct commit *commit;
181 const char *paths[] = {path, NULL}; 185 const char *paths[] = {path, NULL};
182 186
183 if (!rev) 187 if (!rev)
184 rev = cgit_query_head; 188 rev = cgit_query_head;
185 189
186 curr_rev = xstrdup(rev); 190 curr_rev = xstrdup(rev);
187 if (get_sha1(rev, sha1)) { 191 if (get_sha1(rev, sha1)) {
188 cgit_print_error(fmt("Invalid revision name: %s", rev)); 192 cgit_print_error(fmt("Invalid revision name: %s", rev));
189 return; 193 return;
190 } 194 }
191 commit = lookup_commit_reference(sha1); 195 commit = lookup_commit_reference(sha1);
192 if (!commit || parse_commit(commit)) { 196 if (!commit || parse_commit(commit)) {
193 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 197 cgit_print_error(fmt("Invalid commit reference: %s", rev));
194 return; 198 return;
195 } 199 }
196 200
197 html("path: <a href='"); 201 html("path: <a href='");
198 html_attr(cgit_pageurl(cgit_query_repo, "tree", fmt("h=%s", rev))); 202 html_attr(cgit_pageurl(cgit_query_repo, "tree", fmt("h=%s", rev)));
199 html("'>root</a>"); 203 html("'>root</a>");
200 204
201 if (path == NULL) { 205 if (path == NULL) {
202 ls_tree(commit->tree->object.sha1, NULL); 206 ls_tree(commit->tree->object.sha1, NULL);
203 return; 207 return;
204 } 208 }
205 209
206 match_path = path; 210 match_path = path;
207 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree); 211 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree);
208 ls_tail(); 212 ls_tail();
209} 213}