add stylesheet and rxpd option / config
[webgit] / src / repo.c
1 /*
2     cehtehs git web frontend
3
4   Copyright (C)
5     2007,               Christian Thaeter <ct@pipapo.org>
6
7   This program is free software; you can redistribute it and/or
8   modify it under the terms of the GNU General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, write to the Free Software
19   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "repo.h"
23 #include "age.h"
24
25 #define SHA1_HEADER <openssl/sha.h>
26 #include "git/cache.h"
27 #include "git/refs.h"
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <pwd.h>
33 #include <stdint.h>
34 #include <time.h>
35
36
37 Html
38 webgit_repo_link (struct webgit_query* query,
39                   const char* repo_prefix,
40                   int repo_prefix_len,
41                   const char* repo,
42                   int repo_len,
43                   const char* object,
44                   int object_len,
45                   const char* action,
46                   Html text)
47 {
48   return html (
49                html_tag ("a",
50                          html_attr ("href", html (
51                                                   html_fmt ("%s?repo=%.*s%s%.*s",
52                                                             query->request->script_name,
53                                                             repo_prefix_len, repo_prefix,
54                                                             repo_prefix && repo ? "/" : "",
55                                                             repo_len, repo,
56                                                             object_len, object),
57                                                   action ?
58                                                   html_fmt ("&action=%s", action) : html (),
59                                                   object ?
60                                                   html_fmt ("&object=%.*s", object_len, object) : html ()
61                                                   )
62                                     )
63                          ),
64                text
65                );
66 }
67
68 static struct webgit_repo_info* in_flight;       /* stupid git callback has no void* userdata; we have to pass self in a global */
69
70 static void
71 webgit_name_conf (struct webgit_repo_info* self, const char *value)
72 {
73   self->name = cwa_strndup (value, SIZE_MAX);
74 }
75
76
77 static void
78 webgit_description_conf (struct webgit_repo_info* self, const char *value)
79 {
80   self->description = cwa_strndup (value, SIZE_MAX);
81 }
82
83
84 static void
85 webgit_url_conf (struct webgit_repo_info* self, const char *value)
86 {
87   self->url = cwa_strndup (value, SIZE_MAX);
88 }
89
90
91 static void
92 webgit_readme_conf (struct webgit_repo_info* self, const char *value)
93 {
94   self->readme = cwa_strndup (value, SIZE_MAX);
95 }
96
97
98 static void
99 webgit_stylesheet_conf (struct webgit_repo_info* self, const char *value)
100 {
101   free (self->query->stylesheet);
102   self->query->stylesheet = cwa_strndup (value, SIZE_MAX);
103 }
104
105
106 static void
107 webgit_owner_conf (struct webgit_repo_info* self, const char *value)
108 {
109   self->owner = cwa_strndup (value, SIZE_MAX);
110 }
111
112
113 int
114 webgit_repo_conf_cb (const char *var, const char *value)
115 {
116 #define WEBGIT_CONF(name, _) {WEBGIT_CONF_PREFIX #name, webgit_##name##_conf},
117   struct conf_table{
118     char* name;
119     void (*cb)(struct webgit_repo_info* self, const char *value);
120   } cmds[] = {WEBGIT_CONFS {"", NULL}};
121 #undef WEBGIT_CONF
122
123   for (struct conf_table* j = cmds; j->cb; ++j)
124     {
125       if (!strcmp (j->name, var))
126         {
127           j->cb (in_flight, value);
128           break;
129         }
130     }
131   return 1;
132 }
133
134
135 static int
136 find_last (const char *refname, const unsigned char *sha1, int flags, void *ri)
137 {
138   (void) flags;
139
140   struct webgit_repo_info* self = (struct webgit_repo_info*) ri;
141
142   void* buf;
143   unsigned long size;
144
145   buf = read_object_with_reference (sha1, "commit", &size, NULL);
146
147   if (!buf)
148     return 1;
149
150   unsigned long age = ~0;
151
152   /* first pass, get last commit time */
153   const char* i = buf;
154   while (i && i < (const char*)buf+size)
155     {
156       if (*i == '\n')
157         break;
158       if (!strncmp (i, "committer ", 10))
159         {
160           char* time_beg = strchr (i, '>');
161           if (time_beg)
162             {
163               struct tm tm;
164               if (strptime (time_beg + 2, "%s %Z", &tm))
165                 {
166                   age = difftime (self->query->now, mktime (&tm));
167                 }
168             }
169         }
170       i = strchr (i, '\n');
171       if (i)
172         ++i;
173     }
174
175   /* second pass, found a newer commit */
176   if (age != ~0UL && (self->age == ~0UL || age < self->age))
177     {
178       self->age = age;
179       free (self->last_commit);
180       self->last_commit = cwa_strndup (sha1_to_hex (sha1), 40);
181       free (self->last_head);
182       self->last_head = cwa_strndup (refname, SIZE_MAX);
183
184       i = buf;
185       while (i && (void*)i < buf+size)
186         {
187           if (*i == '\n')
188             break;
189           if (!strncmp (i, "tree ", 5))
190             {
191               self->last_tree = cwa_strndup (i+5, 40);
192             }
193           else if (!strncmp (i, "committer ", 10))
194             {
195               char* email_beg = strchr (i, '<');
196               char* email_end = strchr (email_beg, '>');
197
198               self->last_committer_name = cwa_strndup (i+10, email_beg - i - 11);
199               self->last_committer_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
200             }
201           else if (!strncmp (i, "author ", 7))
202             {
203               char* email_beg = strchr (i, '<');
204               char* email_end = strchr (email_beg, '>');
205
206               self->last_author_name = cwa_strndup (i+7, email_beg - i - 8);
207               self->last_author_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
208             }
209           if ((i = strchr (i, '\n')))
210             ++i;
211         }
212     }
213   return 0;
214 }
215
216 struct webgit_repo_info*
217 webgit_repo_enter (struct webgit_query* query)
218 {
219   struct webgit_repo_info* ri = NULL;
220   LLIST_FOREACH (&query->repos, node)
221     {
222       ri = (struct webgit_repo_info*) node;
223       if (!strcmp (ri->name, query->repo))
224         {
225           if (chdir (ri->path))
226             die ("error: wrong path %s", ri->path);
227           break;
228         }
229     }
230   setup_git_directory ();
231
232   in_flight = ri;
233   git_config (webgit_repo_conf_cb);
234
235   return ri;
236 }
237
238 struct webgit_repo_info*
239 webgit_repoinfo_new (struct webgit_query* query, const char* path)
240 {
241   char buf[512];
242
243   if (!path)
244     return NULL;
245
246   struct webgit_repo_info* self = cwa_malloc (sizeof (struct webgit_repo_info));
247
248   self->query = query;
249
250   self->name = NULL;
251   self->owner = NULL;
252   self->description = NULL;
253   self->url = NULL;
254   self->readme = NULL;
255
256
257   llist_init (&self->node);
258   self->path = cwa_strndup (path, SIZE_MAX);
259
260   chdir (path);
261
262   const char* subdir = setup_git_directory ();
263   /* strip subdir part off */
264   self->path [strlen (path) - (subdir ? strlen (subdir):0)] = '\0';
265
266   in_flight = self;
267   git_config (webgit_repo_conf_cb);
268
269   /* use path, when no name given */
270   if (!self->name)
271     self->name = cwa_strndup (self->path, SIZE_MAX);
272
273   /* set owner if not already set by gitconfig */
274   if (!self->owner)
275     {
276       struct stat st;
277       if (stat (path, &st))
278         return NULL; // error ILLEGAL dir
279
280       struct passwd* pw;
281       if ((pw = getpwuid (st.st_uid)) != NULL)
282         self->owner = cwa_strndup (pw->pw_gecos, strchr (pw->pw_gecos, ',') - pw->pw_gecos);
283       else
284         self->owner = cwa_strndup ("unknown", 100);
285     }
286
287   /* set description if not already set by gitconfig */
288   if (!self->description)
289     {
290       FILE* desc = fopen (".git/description", "r"); /* FIXME: $GIT_DIR/description*/
291       if (desc)
292         {
293           fgets (buf, 512, desc);
294           fclose (desc);
295           char* e = strchr (buf, '\n');
296           if (e)
297             *e = '\0';
298
299           self->description = cwa_strndup (buf, 40);
300         }
301     }
302
303   /* trim description size */
304   if (self->description)
305     {
306       size_t l = strlen (self->description);
307       if (l >= 39)
308         {
309           self->description[l-1] = '.';
310           self->description[l-2] = '.';
311           self->description[l-3] = '.';
312         }
313     }
314
315
316   self->last_commit = NULL;
317   self->last_tree = NULL;
318   self->last_head = NULL;
319   self->last_committer_name = NULL;
320   self->last_committer_email = NULL;
321   self->last_author_name = NULL;
322   self->last_author_email = NULL;
323   self->age = ~0;
324
325   return self;
326 }
327
328
329 void
330 webgit_repoinfo_find_last (struct webgit_repo_info* self)
331 {
332   if (self->age != ~0UL)
333     return;
334
335   chdir (self->path);
336   setup_git_directory ();
337
338   struct webgit_repo_info ri;
339   ri.last_commit = NULL;
340   ri.last_tree = NULL;
341   ri.last_head = NULL;
342   ri.last_committer_name = NULL;
343   ri.last_committer_email = NULL;
344   ri.last_author_name = NULL;
345   ri.last_author_email = NULL;
346   ri.query = self->query;
347   ri.age = ~0;
348   for_each_branch_ref (find_last, &ri);
349
350   self->last_commit = ri.last_commit ? ri.last_commit : cwa_strndup("NO_COMMIT", SIZE_MAX);
351   self->last_tree = ri.last_tree ? ri.last_tree : cwa_strndup("NO_TREE", SIZE_MAX);
352   self->last_head = ri.last_head ? ri.last_head : cwa_strndup("NO_HEAD", SIZE_MAX);
353   self->last_committer_name = ri.last_committer_name ?
354     ri.last_committer_name : cwa_strndup("NO_COMMITTER", SIZE_MAX);
355   self->last_committer_email = ri.last_committer_email ?
356     ri.last_committer_email : cwa_strndup("NO_COMMITTER_EMAIL", SIZE_MAX);
357   self->last_author_name = ri.last_author_name ?
358     ri.last_author_name : cwa_strndup("NO_AUTHOR", SIZE_MAX);
359   self->last_author_email = ri.last_author_email ?
360     ri.last_author_email : cwa_strndup("NO_AUTHOR_EMAIL", SIZE_MAX);
361   self->age = ri.age;
362 }
363
364 void
365 webgit_repoinfo_free (struct webgit_repo_info* self)
366 {
367   if (self)
368     {
369       llist_unlink (&self->node);
370
371       free (self->path);
372       free (self->name);
373
374       free (self->owner);
375       free (self->description);
376       free (self->url);
377       free (self->readme);
378
379       free (self->last_commit);
380       free (self->last_tree);
381       free (self->last_head);
382       free (self->last_committer_name);
383       free (self->last_committer_email);
384       free (self->last_author_name);
385       free (self->last_author_email);
386
387       free (self);
388     }
389 }