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