]> gitweb.ps.run Git - ps-cgit/blob - cgit.c
Import cgit prototype from git tree
[ps-cgit] / cgit.c
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 }