factored the single object types out into their own sources
[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_owner_conf (struct webgit_repo_info* self, const char *value)
100 {
101   self->owner = cwa_strndup (value, SIZE_MAX);
102 }
103
104
105 int
106 webgit_repo_conf_cb (const char *var, const char *value)
107 {
108 #define WEBGIT_CONF(name, _) {WEBGIT_CONF_PREFIX #name, webgit_##name##_conf},
109   struct conf_table{
110     char* name;
111     void (*cb)(struct webgit_repo_info* self, const char *value);
112   } cmds[] = {WEBGIT_CONFS {"", NULL}};
113 #undef WEBGIT_CONF
114
115   for (struct conf_table* j = cmds; j->cb; ++j)
116     {
117       if (!strcmp (j->name, var))
118         {
119           j->cb (in_flight, value);
120           break;
121         }
122     }
123   return 1;
124 }
125
126
127 static int
128 find_last (const char *refname, const unsigned char *sha1, int flags, void *ri)
129 {
130   (void) flags;
131
132   struct webgit_repo_info* self = (struct webgit_repo_info*) ri;
133
134   void* buf;
135   unsigned long size;
136
137   buf = read_object_with_reference (sha1, "commit", &size, NULL);
138
139   if (!buf)
140     return 1;
141
142   unsigned long age = ~0;
143
144   /* first pass, get last commit time */
145   const char* i = buf;
146   while (i && i < (const char*)buf+size)
147     {
148       if (*i == '\n')
149         break;
150       if (!strncmp (i, "committer ", 10))
151         {
152           char* time_beg = strchr (i, '>');
153           if (time_beg)
154             {
155               struct tm tm;
156               if (strptime (time_beg + 2, "%s %Z", &tm))
157                 {
158                   age = difftime (self->query->now, mktime (&tm));
159                 }
160             }
161         }
162       i = strchr (i, '\n');
163       if (i)
164         ++i;
165     }
166
167   /* second pass, found a newer commit */
168   if (age != ~0UL && (self->age == ~0UL || age < self->age))
169     {
170       self->age = age;
171       free (self->last_commit);
172       self->last_commit = cwa_strndup (sha1_to_hex (sha1), 40);
173       free (self->last_head);
174       self->last_head = cwa_strndup (refname, SIZE_MAX);
175
176       i = buf;
177       while (i && (void*)i < buf+size)
178         {
179           if (*i == '\n')
180             break;
181           if (!strncmp (i, "tree ", 5))
182             {
183               self->last_tree = cwa_strndup (i+5, 40);
184             }
185           else if (!strncmp (i, "committer ", 10))
186             {
187               char* email_beg = strchr (i, '<');
188               char* email_end = strchr (email_beg, '>');
189
190               self->last_committer_name = cwa_strndup (i+10, email_beg - i - 11);
191               self->last_committer_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
192             }
193           else if (!strncmp (i, "author ", 7))
194             {
195               char* email_beg = strchr (i, '<');
196               char* email_end = strchr (email_beg, '>');
197
198               self->last_author_name = cwa_strndup (i+7, email_beg - i - 8);
199               self->last_author_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
200             }
201           if ((i = strchr (i, '\n')))
202             ++i;
203         }
204     }
205   return 0;
206 }
207
208 struct webgit_repo_info*
209 webgit_repo_enter (struct webgit_query* query)
210 {
211   struct webgit_repo_info* ri = NULL;
212   LLIST_FOREACH (&query->repos, node)
213     {
214       ri = (struct webgit_repo_info*) node;
215       if (!strcmp (ri->name, query->repo))
216         {
217           if (chdir (ri->path))
218             die ("error: wrong path %s", ri->path);
219           break;
220         }
221     }
222   setup_git_directory ();
223
224   in_flight = ri;
225   git_config (webgit_repo_conf_cb);
226
227   return ri;
228 }
229
230 struct webgit_repo_info*
231 webgit_repoinfo_new (struct webgit_query* query, const char* path)
232 {
233   char buf[512];
234
235   if (!path)
236     return NULL;
237
238   struct webgit_repo_info* self = cwa_malloc (sizeof (struct webgit_repo_info));
239
240   self->query = query;
241
242   self->name = NULL;
243   self->owner = NULL;
244   self->description = NULL;
245   self->url = NULL;
246   self->readme = NULL;
247
248
249   llist_init (&self->node);
250   self->path = cwa_strndup (path, SIZE_MAX);
251
252   chdir (path);
253
254   const char* subdir = setup_git_directory ();
255   /* strip subdir part off */
256   self->path [strlen (path) - (subdir ? strlen (subdir):0)] = '\0';
257
258   in_flight = self;
259   git_config (webgit_repo_conf_cb);
260
261   /* use path, when no name given */
262   if (!self->name)
263     self->name = cwa_strndup (self->path, SIZE_MAX);
264
265   /* set owner if not already set by gitconfig */
266   if (!self->owner)
267     {
268       struct stat st;
269       if (stat (path, &st))
270         return NULL; // error ILLEGAL dir
271
272       struct passwd* pw;
273       if ((pw = getpwuid (st.st_uid)) != NULL)
274         self->owner = cwa_strndup (pw->pw_gecos, strchr (pw->pw_gecos, ',') - pw->pw_gecos);
275       else
276         self->owner = cwa_strndup ("unknown", 100);
277     }
278
279   /* set description if not already set by gitconfig */
280   if (!self->description)
281     {
282       FILE* desc = fopen (".git/description", "r"); /* FIXME: $GIT_DIR/description*/
283       if (desc)
284         {
285           fgets (buf, 512, desc);
286           fclose (desc);
287           char* e = strchr (buf, '\n');
288           if (e)
289             *e = '\0';
290
291           self->description = cwa_strndup (buf, 40);
292         }
293     }
294
295   /* trim description size */
296   if (self->description)
297     {
298       size_t l = strlen (self->description);
299       if (l >= 39)
300         {
301           self->description[l-1] = '.';
302           self->description[l-2] = '.';
303           self->description[l-3] = '.';
304         }
305     }
306
307
308   self->last_commit = NULL;
309   self->last_tree = NULL;
310   self->last_head = NULL;
311   self->last_committer_name = NULL;
312   self->last_committer_email = NULL;
313   self->last_author_name = NULL;
314   self->last_author_email = NULL;
315   self->age = ~0;
316
317   return self;
318 }
319
320
321 void
322 webgit_repoinfo_find_last (struct webgit_repo_info* self)
323 {
324   if (self->age != ~0UL)
325     return;
326
327   chdir (self->path);
328   setup_git_directory ();
329
330   struct webgit_repo_info ri;
331   ri.last_commit = NULL;
332   ri.last_tree = NULL;
333   ri.last_head = NULL;
334   ri.last_committer_name = NULL;
335   ri.last_committer_email = NULL;
336   ri.last_author_name = NULL;
337   ri.last_author_email = NULL;
338   ri.query = self->query;
339   ri.age = ~0;
340   for_each_branch_ref (find_last, &ri);
341
342   self->last_commit = ri.last_commit ? ri.last_commit : cwa_strndup("NO_COMMIT", SIZE_MAX);
343   self->last_tree = ri.last_tree ? ri.last_tree : cwa_strndup("NO_TREE", SIZE_MAX);
344   self->last_head = ri.last_head ? ri.last_head : cwa_strndup("NO_HEAD", SIZE_MAX);
345   self->last_committer_name = ri.last_committer_name ?
346     ri.last_committer_name : cwa_strndup("NO_COMMITTER", SIZE_MAX);
347   self->last_committer_email = ri.last_committer_email ?
348     ri.last_committer_email : cwa_strndup("NO_COMMITTER_EMAIL", SIZE_MAX);
349   self->last_author_name = ri.last_author_name ?
350     ri.last_author_name : cwa_strndup("NO_AUTHOR", SIZE_MAX);
351   self->last_author_email = ri.last_author_email ?
352     ri.last_author_email : cwa_strndup("NO_AUTHOR_EMAIL", SIZE_MAX);
353   self->age = ri.age;
354 }
355
356 void
357 webgit_repoinfo_free (struct webgit_repo_info* self)
358 {
359   if (self)
360     {
361       llist_unlink (&self->node);
362
363       free (self->path);
364       free (self->name);
365
366       free (self->owner);
367       free (self->description);
368       free (self->url);
369       free (self->readme);
370
371       free (self->last_commit);
372       free (self->last_tree);
373       free (self->last_head);
374       free (self->last_committer_name);
375       free (self->last_committer_email);
376       free (self->last_author_name);
377       free (self->last_author_email);
378
379       free (self);
380     }
381 }