diff options
Diffstat (limited to 'cgit.c')
-rw-r--r-- | cgit.c | 410 |
1 files changed, 410 insertions, 0 deletions
@@ -0,0 +1,410 @@ | |||
1 | #include "cgit.h" | ||
2 | |||
3 | static const char cgit_doctype[] = | ||
4 | "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" | ||
5 | " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; | ||
6 | |||
7 | static const char cgit_error[] = | ||
8 | "<div class='error'>%s</div>"; | ||
9 | |||
10 | static const char cgit_lib_error[] = | ||
11 | "<div class='error'>%s: %s</div>"; | ||
12 | |||
13 | |||
14 | char *cgit_root = "/var/git"; | ||
15 | char *cgit_root_title = "Git repository browser"; | ||
16 | char *cgit_css = "/cgit.css"; | ||
17 | char *cgit_logo = "/git-logo.png"; | ||
18 | char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; | ||
19 | char *cgit_virtual_root = NULL; | ||
20 | |||
21 | char *cgit_repo_name = NULL; | ||
22 | char *cgit_repo_desc = NULL; | ||
23 | char *cgit_repo_owner = NULL; | ||
24 | |||
25 | char *cgit_query_repo = NULL; | ||
26 | char *cgit_query_page = NULL; | ||
27 | char *cgit_query_head = NULL; | ||
28 | |||
29 | int cgit_parse_query(char *txt, configfn fn) | ||
30 | { | ||
31 | char *t = txt, *value = NULL, c; | ||
32 | |||
33 | if (!txt) | ||
34 | return 0; | ||
35 | |||
36 | while((c=*t) != '\0') { | ||
37 | if (c=='=') { | ||
38 | *t = '\0'; | ||
39 | value = t+1; | ||
40 | } else if (c=='&') { | ||
41 | *t = '\0'; | ||
42 | (*fn)(txt, value); | ||
43 | txt = t+1; | ||
44 | value = NULL; | ||
45 | } | ||
46 | t++; | ||
47 | } | ||
48 | if (t!=txt) | ||
49 | (*fn)(txt, value); | ||
50 | return 0; | ||
51 | } | ||
52 | |||
53 | void cgit_global_config_cb(const char *name, const char *value) | ||
54 | { | ||
55 | if (!strcmp(name, "root")) | ||
56 | cgit_root = xstrdup(value); | ||
57 | else if (!strcmp(name, "root-title")) | ||
58 | cgit_root_title = xstrdup(value); | ||
59 | else if (!strcmp(name, "css")) | ||
60 | cgit_css = xstrdup(value); | ||
61 | else if (!strcmp(name, "logo")) | ||
62 | cgit_logo = xstrdup(value); | ||
63 | else if (!strcmp(name, "logo-link")) | ||
64 | cgit_logo_link = xstrdup(value); | ||
65 | else if (!strcmp(name, "virtual-root")) | ||
66 | cgit_virtual_root = xstrdup(value); | ||
67 | } | ||
68 | |||
69 | void cgit_repo_config_cb(const char *name, const char *value) | ||
70 | { | ||
71 | if (!strcmp(name, "name")) | ||
72 | cgit_repo_name = xstrdup(value); | ||
73 | else if (!strcmp(name, "desc")) | ||
74 | cgit_repo_desc = xstrdup(value); | ||
75 | else if (!strcmp(name, "owner")) | ||
76 | cgit_repo_owner = xstrdup(value); | ||
77 | } | ||
78 | |||
79 | void cgit_querystring_cb(const char *name, const char *value) | ||
80 | { | ||
81 | if (!strcmp(name,"r")) | ||
82 | cgit_query_repo = xstrdup(value); | ||
83 | else if (!strcmp(name, "p")) | ||
84 | cgit_query_page = xstrdup(value); | ||
85 | else if (!strcmp(name, "h")) | ||
86 | cgit_query_head = xstrdup(value); | ||
87 | } | ||
88 | |||
89 | char *cgit_repourl(const char *reponame) | ||
90 | { | ||
91 | if (cgit_virtual_root) { | ||
92 | return fmt("%s/%s/", cgit_virtual_root, reponame); | ||
93 | } else { | ||
94 | return fmt("?r=%s", reponame); | ||
95 | } | ||
96 | } | ||
97 | |||
98 | char *cgit_pageurl(const char *reponame, const char *pagename, | ||
99 | const char *query) | ||
100 | { | ||
101 | if (cgit_virtual_root) { | ||
102 | return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, | ||
103 | pagename, query); | ||
104 | } else { | ||
105 | return fmt("?r=%s&p=%s&%s", reponame, pagename, query); | ||
106 | } | ||
107 | } | ||
108 | |||
109 | static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1, | ||
110 | int flags, void *cb_data) | ||
111 | { | ||
112 | struct commit *commit; | ||
113 | char buf[256], *url; | ||
114 | |||
115 | commit = lookup_commit(sha1); | ||
116 | if (commit && !parse_commit(commit)){ | ||
117 | html("<tr><td>"); | ||
118 | url = cgit_pageurl(cgit_query_repo, "log", | ||
119 | fmt("h=%s", refname)); | ||
120 | html_link_open(url, NULL, NULL); | ||
121 | strncpy(buf, refname, sizeof(buf)); | ||
122 | html_txt(buf); | ||
123 | html_link_close(); | ||
124 | html("</td><td>"); | ||
125 | pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, buf, | ||
126 | sizeof(buf), 0, NULL, NULL, 0); | ||
127 | html_txt(buf); | ||
128 | html("</td></tr>\n"); | ||
129 | } else { | ||
130 | html("<tr><td>"); | ||
131 | html_txt(buf); | ||
132 | html("</td><td>"); | ||
133 | htmlf("*** bad ref %s", sha1_to_hex(sha1)); | ||
134 | html("</td></tr>\n"); | ||
135 | } | ||
136 | return 0; | ||
137 | } | ||
138 | |||
139 | static void cgit_print_docstart(char *title) | ||
140 | { | ||
141 | html("Content-Type: text/html; charset=utf-8\n"); | ||
142 | html("\n"); | ||
143 | html(cgit_doctype); | ||
144 | html("<html>\n"); | ||
145 | html("<head>\n"); | ||
146 | html("<title>"); | ||
147 | html_txt(title); | ||
148 | html("</title>\n"); | ||
149 | html("<link rel='stylesheet' type='text/css' href='"); | ||
150 | html_attr(cgit_css); | ||
151 | html("'/>\n"); | ||
152 | html("</head>\n"); | ||
153 | html("<body>\n"); | ||
154 | } | ||
155 | |||
156 | static void cgit_print_docend() | ||
157 | { | ||
158 | html("</body>\n</html>\n"); | ||
159 | } | ||
160 | |||
161 | static void cgit_print_pageheader(char *title) | ||
162 | { | ||
163 | html("<div id='header'>"); | ||
164 | htmlf("<a href='%s'>", cgit_logo_link); | ||
165 | htmlf("<img id='logo' src='%s'/>\n", cgit_logo); | ||
166 | htmlf("</a>"); | ||
167 | html_txt(title); | ||
168 | html("</div>"); | ||
169 | } | ||
170 | |||
171 | static void cgit_print_repolist() | ||
172 | { | ||
173 | DIR *d; | ||
174 | struct dirent *de; | ||
175 | struct stat st; | ||
176 | char *name; | ||
177 | |||
178 | cgit_print_docstart(cgit_root_title); | ||
179 | cgit_print_pageheader(cgit_root_title); | ||
180 | |||
181 | if (!(d = opendir("."))) { | ||
182 | htmlf(cgit_lib_error, "Unable to scan repository directory", | ||
183 | strerror(errno)); | ||
184 | cgit_print_docend(); | ||
185 | return; | ||
186 | } | ||
187 | |||
188 | html("<h2>Repositories</h2>\n"); | ||
189 | html("<table class='list'>"); | ||
190 | html("<tr><th>Name</th><th>Description</th><th>Owner</th></tr>\n"); | ||
191 | while ((de = readdir(d)) != NULL) { | ||
192 | if (de->d_name[0] == '.') | ||
193 | continue; | ||
194 | if (stat(de->d_name, &st) < 0) | ||
195 | continue; | ||
196 | if (!S_ISDIR(st.st_mode)) | ||
197 | continue; | ||
198 | |||
199 | cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL; | ||
200 | name = fmt("%s/.git/info/cgit", de->d_name); | ||
201 | if (cgit_read_config(name, cgit_repo_config_cb)) | ||
202 | continue; | ||
203 | |||
204 | html("<tr><td>"); | ||
205 | html_link_open(cgit_repourl(de->d_name), NULL, NULL); | ||
206 | html_txt(cgit_repo_name); | ||
207 | html_link_close(); | ||
208 | html("</td><td>"); | ||
209 | html_txt(cgit_repo_desc); | ||
210 | html("</td><td>"); | ||
211 | html_txt(cgit_repo_owner); | ||
212 | html("</td></tr>\n"); | ||
213 | } | ||
214 | closedir(d); | ||
215 | html("</table>"); | ||
216 | cgit_print_docend(); | ||
217 | } | ||
218 | |||
219 | static void cgit_print_branches() | ||
220 | { | ||
221 | html("<table class='list'>"); | ||
222 | html("<tr><th>Branch name</th><th>Head commit</th></tr>\n"); | ||
223 | for_each_branch_ref(cgit_print_branch_cb, NULL); | ||
224 | html("</table>"); | ||
225 | } | ||
226 | |||
227 | static int get_one_line(char *txt) | ||
228 | { | ||
229 | char *t; | ||
230 | |||
231 | for(t=txt; *t != '\n' && t != '\0'; t++) | ||
232 | ; | ||
233 | *t = '\0'; | ||
234 | return t-txt-1; | ||
235 | } | ||
236 | |||
237 | static void cgit_print_commit_shortlog(struct commit *commit) | ||
238 | { | ||
239 | char *h, *t, *p; | ||
240 | char *tree = NULL, *author = NULL, *subject = NULL; | ||
241 | int len; | ||
242 | time_t sec; | ||
243 | struct tm *time; | ||
244 | char buf[32]; | ||
245 | |||
246 | h = t = commit->buffer; | ||
247 | |||
248 | if (strncmp(h, "tree ", 5)) | ||
249 | die("Bad commit format: %s", | ||
250 | sha1_to_hex(commit->object.sha1)); | ||
251 | |||
252 | len = get_one_line(h); | ||
253 | tree = h+5; | ||
254 | h += len + 2; | ||
255 | |||
256 | while (!strncmp(h, "parent ", 7)) | ||
257 | h += get_one_line(h) + 2; | ||
258 | |||
259 | if (!strncmp(h, "author ", 7)) { | ||
260 | author = h+7; | ||
261 | h += get_one_line(h) + 2; | ||
262 | t = author; | ||
263 | while(t!=h && *t!='<') | ||
264 | t++; | ||
265 | *t='\0'; | ||
266 | p = t; | ||
267 | while(--t!=author && *t==' ') | ||
268 | *t='\0'; | ||
269 | while(++p!=h && *p!='>') | ||
270 | ; | ||
271 | while(++p!=h && !isdigit(*p)) | ||
272 | ; | ||
273 | |||
274 | t = p; | ||
275 | while(++p && isdigit(*p)) | ||
276 | ; | ||
277 | *p = '\0'; | ||
278 | sec = atoi(t); | ||
279 | time = gmtime(&sec); | ||
280 | } | ||
281 | |||
282 | while((len = get_one_line(h)) > 0) | ||
283 | h += len+2; | ||
284 | |||
285 | h++; | ||
286 | len = get_one_line(h); | ||
287 | |||
288 | subject = h; | ||
289 | |||
290 | html("<tr><td>"); | ||
291 | strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time); | ||
292 | html_txt(buf); | ||
293 | html("</td><td>"); | ||
294 | char *qry = fmt("h=%s", sha1_to_hex(commit->object.sha1)); | ||
295 | char *url = cgit_pageurl(cgit_query_repo, "view", qry); | ||
296 | html_link_open(url, NULL, NULL); | ||
297 | html_txt(subject); | ||
298 | html_link_close(); | ||
299 | html("</td><td>"); | ||
300 | html_txt(author); | ||
301 | html("</td></tr>\n"); | ||
302 | } | ||
303 | |||
304 | static void cgit_print_log(const char *tip, int ofs, int cnt) | ||
305 | { | ||
306 | struct rev_info rev; | ||
307 | struct commit *commit; | ||
308 | const char *argv[2] = {NULL, tip}; | ||
309 | int n = 0; | ||
310 | |||
311 | init_revisions(&rev, NULL); | ||
312 | rev.abbrev = DEFAULT_ABBREV; | ||
313 | rev.commit_format = CMIT_FMT_DEFAULT; | ||
314 | rev.verbose_header = 1; | ||
315 | rev.show_root_diff = 0; | ||
316 | setup_revisions(2, argv, &rev, NULL); | ||
317 | prepare_revision_walk(&rev); | ||
318 | |||
319 | html("<h2>Log</h2>"); | ||
320 | html("<table class='list'>"); | ||
321 | html("<tr><th>Date</th><th>Message</th><th>Author</th></tr>\n"); | ||
322 | while ((commit = get_revision(&rev)) != NULL && n++ < 100) { | ||
323 | cgit_print_commit_shortlog(commit); | ||
324 | free(commit->buffer); | ||
325 | commit->buffer = NULL; | ||
326 | free_commit_list(commit->parents); | ||
327 | commit->parents = NULL; | ||
328 | } | ||
329 | html("</table>\n"); | ||
330 | } | ||
331 | |||
332 | static void cgit_print_repo_summary() | ||
333 | { | ||
334 | html("<h2>"); | ||
335 | html_txt("Repo summary page"); | ||
336 | html("</h2>"); | ||
337 | cgit_print_branches(); | ||
338 | } | ||
339 | |||
340 | static void cgit_print_object(char *hex) | ||
341 | { | ||
342 | unsigned char sha1[20]; | ||
343 | //struct object *object; | ||
344 | char type[20]; | ||
345 | unsigned char *buf; | ||
346 | unsigned long size; | ||
347 | |||
348 | if (get_sha1_hex(hex, sha1)){ | ||
349 | htmlf(cgit_error, "Bad hex value"); | ||
350 | return; | ||
351 | } | ||
352 | |||
353 | if (sha1_object_info(sha1, type, NULL)){ | ||
354 | htmlf(cgit_error, "Bad object name"); | ||
355 | return; | ||
356 | } | ||
357 | |||
358 | buf = read_sha1_file(sha1, type, &size); | ||
359 | if (!buf) { | ||
360 | htmlf(cgit_error, "Error reading object"); | ||
361 | return; | ||
362 | } | ||
363 | |||
364 | buf[size] = '\0'; | ||
365 | html("<h2>Object view</h2>"); | ||
366 | htmlf("sha1=%s<br/>type=%s<br/>size=%i<br/>", hex, type, size); | ||
367 | html("<pre>"); | ||
368 | html_txt(buf); | ||
369 | html("</pre>"); | ||
370 | } | ||
371 | |||
372 | static void cgit_print_repo_page() | ||
373 | { | ||
374 | if (chdir(cgit_query_repo) || | ||
375 | cgit_read_config(".git/info/cgit", cgit_repo_config_cb)) { | ||
376 | char *title = fmt("%s - %s", cgit_root_title, "Bad request"); | ||
377 | cgit_print_docstart(title); | ||
378 | cgit_print_pageheader(title); | ||
379 | htmlf(cgit_lib_error, "Unable to scan repository", | ||
380 | strerror(errno)); | ||
381 | cgit_print_docend(); | ||
382 | return; | ||
383 | } | ||
384 | |||
385 | char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc); | ||
386 | cgit_print_docstart(title); | ||
387 | cgit_print_pageheader(title); | ||
388 | if (!cgit_query_page) | ||
389 | cgit_print_repo_summary(); | ||
390 | else if (!strcmp(cgit_query_page, "log")) { | ||
391 | cgit_print_log(cgit_query_head, 0, 100); | ||
392 | } else if (!strcmp(cgit_query_page, "view")) { | ||
393 | cgit_print_object(cgit_query_head); | ||
394 | } | ||
395 | cgit_print_docend(); | ||
396 | } | ||
397 | |||
398 | int main(int argc, const char **argv) | ||
399 | { | ||
400 | if (cgit_read_config("/etc/cgitrc", cgit_global_config_cb)) | ||
401 | die("Error reading config: %d %s", errno, strerror(errno)); | ||
402 | |||
403 | chdir(cgit_root); | ||
404 | cgit_parse_query(getenv("QUERY_STRING"), cgit_querystring_cb); | ||
405 | if (cgit_query_repo) | ||
406 | cgit_print_repo_page(); | ||
407 | else | ||
408 | cgit_print_repolist(); | ||
409 | return 0; | ||
410 | } | ||