mixed work for more menu stuff
[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
68 Html
69 webgit_repo_logo (struct webgit_repo_info* repo)
70 {
71   return repo->logo ?
72     html (
73           html_tag ("a",
74                     html_attr ("href", repo->logolink ? repo->logolink : "/")),
75           html (
76                 html_tag ("img",
77                           html_attr ("src", repo->logo),
78                           html_attr ("alt", html (repo->name,"-logo"))
79                           )
80                 )
81           )
82     : html (
83             html_tag ("img",
84                       html_attr ("src", webgit_webskinpath (repo->query, "icons/webgit_logo.png")),
85                       html_attr ("alt", "Webgit-Logo")
86                       )
87             );
88 }
89
90
91 static struct webgit_repo_info* in_flight;       /* stupid git callback has no void* userdata; we have to pass self in a global */
92
93 static void
94 webgit_name_conf (struct webgit_repo_info* self, const char *value)
95 {
96   self->name = cwa_strndup (value, SIZE_MAX);
97 }
98
99
100 static void
101 webgit_description_conf (struct webgit_repo_info* self, const char *value)
102 {
103   self->description = cwa_strndup (value, SIZE_MAX);
104 }
105
106
107 static void
108 webgit_url_conf (struct webgit_repo_info* self, const char *value)
109 {
110   self->url = cwa_strndup (value, SIZE_MAX);
111 }
112
113
114 static void
115 webgit_logo_conf (struct webgit_repo_info* self, const char *value)
116 {
117   self->logo = cwa_strndup (value, SIZE_MAX);
118 }
119
120
121 static void
122 webgit_logolink_conf (struct webgit_repo_info* self, const char *value)
123 {
124   self->logolink = cwa_strndup (value, SIZE_MAX);
125 }
126
127
128 static void
129 webgit_readme_conf (struct webgit_repo_info* self, const char *value)
130 {
131   self->readme = cwa_strndup (value, SIZE_MAX);
132 }
133
134
135 static void
136 webgit_owner_conf (struct webgit_repo_info* self, const char *value)
137 {
138   self->owner = cwa_strndup (value, SIZE_MAX);
139 }
140
141
142 static void
143 webgit_skindir_conf (struct webgit_repo_info* self, const char *value)
144 {
145   free (self->query->skindir);
146   self->query->skindir = cwa_strndup (value, SIZE_MAX);
147 }
148
149
150 static void
151 webgit_webskindir_conf (struct webgit_repo_info* self, const char *value)
152 {
153   free (self->query->webskindir);
154   self->query->webskindir = cwa_strndup (value, SIZE_MAX);
155 }
156
157
158 static void
159 webgit_skin_conf (struct webgit_repo_info* self, const char *value)
160 {
161   free (self->query->skin);
162   self->query->skin = cwa_strndup (value, SIZE_MAX);
163 }
164
165
166 static void
167 webgit_maxage_conf (struct webgit_repo_info* self, const char *value)
168 {
169   self->maxage = atol (value) * 86400UL;
170 }
171
172
173 int
174 webgit_repo_conf_cb (const char *var, const char *value)
175 {
176 #define WEBGIT_CONF(name, _) {WEBGIT_CONF_PREFIX #name, webgit_##name##_conf},
177   struct conf_table{
178     char* name;
179     void (*cb)(struct webgit_repo_info* self, const char *value);
180   } cmds[] = {WEBGIT_CONFS {"", NULL}};
181 #undef WEBGIT_CONF
182
183   for (struct conf_table* j = cmds; j->cb; ++j)
184     {
185       if (!strcmp (j->name, var))
186         {
187           j->cb (in_flight, value);
188           break;
189         }
190     }
191   return 1;
192 }
193
194
195 static int
196 find_last (const char *refname, const unsigned char *sha1, int flags, void *ri)
197 {
198   (void) flags;
199
200   struct webgit_repo_info* self = (struct webgit_repo_info*) ri;
201
202   void* buf;
203   unsigned long size;
204
205   buf = read_object_with_reference (sha1, "commit", &size, NULL);
206
207   if (!buf)
208     return 1;
209
210   unsigned long age = ~0;
211
212   /* first pass, get last commit time */
213   const char* i = buf;
214   while (i && i < (const char*)buf+size)
215     {
216       if (*i == '\n')
217         break;
218       if (!strncmp (i, "committer ", 10))
219         {
220           char* time_beg = strchr (i, '>');
221           if (time_beg)
222             {
223               struct tm tm;
224               if (strptime (time_beg + 2, "%s %Z", &tm))
225                 {
226                   age = difftime (self->query->now, mktime (&tm));
227                 }
228             }
229         }
230       i = strchr (i, '\n');
231       if (i)
232         ++i;
233     }
234
235   /* second pass, found a newer commit */
236   if (age != ~0UL && (self->age == ~0UL || age < self->age))
237     {
238       self->age = age;
239       free (self->last_commit);
240       self->last_commit = cwa_strndup (sha1_to_hex (sha1), 40);
241       free (self->last_head);
242       self->last_head = cwa_strndup (refname, SIZE_MAX);
243
244       i = buf;
245       while (i && (void*)i < buf+size)
246         {
247           if (*i == '\n')
248             break;
249           if (!strncmp (i, "tree ", 5))
250             {
251               self->last_tree = cwa_strndup (i+5, 40);
252             }
253           else if (!strncmp (i, "committer ", 10))
254             {
255               char* email_beg = strchr (i, '<');
256               char* email_end = strchr (email_beg, '>');
257
258               self->last_committer_name = cwa_strndup (i+10, email_beg - i - 11);
259               self->last_committer_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
260             }
261           else if (!strncmp (i, "author ", 7))
262             {
263               char* email_beg = strchr (i, '<');
264               char* email_end = strchr (email_beg, '>');
265
266               self->last_author_name = cwa_strndup (i+7, email_beg - i - 8);
267               self->last_author_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
268             }
269           if ((i = strchr (i, '\n')))
270             ++i;
271         }
272     }
273   return 0;
274 }
275
276 struct webgit_repo_info*
277 webgit_repo_enter (struct webgit_query* query)
278 {
279   struct webgit_repo_info* ri = NULL;
280   LLIST_FOREACH (&query->repos, node)
281     {
282       ri = (struct webgit_repo_info*) node;
283       if (!strcmp (ri->name, query->repo))
284         {
285           if (chdir (ri->path))
286             die ("error: wrong path %s", ri->path);
287           break;
288         }
289     }
290   setup_git_directory ();
291
292   in_flight = ri;
293   git_config (webgit_repo_conf_cb);
294
295   return ri;
296 }
297
298 struct webgit_repo_info*
299 webgit_repoinfo_new (struct webgit_query* query, const char* path)
300 {
301   char buf[512];
302
303   if (!path)
304     return NULL;
305
306   struct webgit_repo_info* self = cwa_malloc (sizeof (struct webgit_repo_info));
307
308   self->query = query;
309
310   self->name = NULL;
311   self->owner = NULL;
312   self->description = NULL;
313   self->url = NULL;
314   self->logo = NULL;
315   self->logolink = NULL;
316   self->readme = NULL;
317   self->maxage = query->maxage;
318
319   llist_init (&self->node);
320   self->path = cwa_strndup (path, SIZE_MAX);
321
322   chdir (path);
323
324   const char* subdir = setup_git_directory ();
325   /* strip subdir part off */
326   self->path [strlen (path) - (subdir ? strlen (subdir):0)] = '\0';
327
328   in_flight = self;
329   git_config (webgit_repo_conf_cb);
330
331   /* use path, when no name given */
332   if (!self->name)
333     self->name = cwa_strndup (self->path, SIZE_MAX);
334
335   /* set owner if not already set by gitconfig */
336   if (!self->owner)
337     {
338       struct stat st;
339       if (stat (path, &st))
340         return NULL; // error ILLEGAL dir
341
342       struct passwd* pw;
343       if ((pw = getpwuid (st.st_uid)) != NULL)
344         self->owner = cwa_strndup (pw->pw_gecos, strchr (pw->pw_gecos, ',') - pw->pw_gecos);
345       else
346         self->owner = cwa_strndup ("unknown", 100);
347     }
348
349   /* set description if not already set by gitconfig */
350   if (!self->description)
351     {
352       FILE* desc = fopen (".git/description", "r"); /* FIXME: $GIT_DIR/description*/
353       if (desc)
354         {
355           fgets (buf, 512, desc);
356           fclose (desc);
357           char* e = strchr (buf, '\n');
358           if (e)
359             *e = '\0';
360
361           self->description = cwa_strndup (buf, 40);
362         }
363     }
364
365   /* trim description size */
366   if (self->description)
367     {
368       size_t l = strlen (self->description);
369       if (l >= 39)
370         {
371           self->description[l-1] = '.';
372           self->description[l-2] = '.';
373           self->description[l-3] = '.';
374         }
375     }
376
377
378   self->last_commit = NULL;
379   self->last_tree = NULL;
380   self->last_head = NULL;
381   self->last_committer_name = NULL;
382   self->last_committer_email = NULL;
383   self->last_author_name = NULL;
384   self->last_author_email = NULL;
385   self->age = ~0;
386
387   return self;
388 }
389
390
391 void
392 webgit_repoinfo_find_last (struct webgit_repo_info* self)
393 {
394   if (self->age != ~0UL)
395     return;
396
397   chdir (self->path);
398   setup_git_directory ();
399
400   struct webgit_repo_info ri;
401   ri.last_commit = NULL;
402   ri.last_tree = NULL;
403   ri.last_head = NULL;
404   ri.last_committer_name = NULL;
405   ri.last_committer_email = NULL;
406   ri.last_author_name = NULL;
407   ri.last_author_email = NULL;
408   ri.query = self->query;
409   ri.age = ~0;
410   for_each_branch_ref (find_last, &ri);
411
412   self->last_commit = ri.last_commit ? ri.last_commit : cwa_strndup("NO_COMMIT", SIZE_MAX);
413   self->last_tree = ri.last_tree ? ri.last_tree : cwa_strndup("NO_TREE", SIZE_MAX);
414   self->last_head = ri.last_head ? ri.last_head : cwa_strndup("NO_HEAD", SIZE_MAX);
415   self->last_committer_name = ri.last_committer_name ?
416     ri.last_committer_name : cwa_strndup("NO_COMMITTER", SIZE_MAX);
417   self->last_committer_email = ri.last_committer_email ?
418     ri.last_committer_email : cwa_strndup("NO_COMMITTER_EMAIL", SIZE_MAX);
419   self->last_author_name = ri.last_author_name ?
420     ri.last_author_name : cwa_strndup("NO_AUTHOR", SIZE_MAX);
421   self->last_author_email = ri.last_author_email ?
422     ri.last_author_email : cwa_strndup("NO_AUTHOR_EMAIL", SIZE_MAX);
423   self->age = ri.age;
424 }
425
426 void
427 webgit_repoinfo_free (struct webgit_repo_info* self)
428 {
429   if (self)
430     {
431       llist_unlink (&self->node);
432
433       free (self->path);
434       free (self->name);
435
436       free (self->owner);
437       free (self->description);
438       free (self->url);
439       free (self->logo);
440       free (self->logolink);
441       free (self->readme);
442
443       free (self->last_commit);
444       free (self->last_tree);
445       free (self->last_head);
446       free (self->last_committer_name);
447       free (self->last_committer_email);
448       free (self->last_author_name);
449       free (self->last_author_email);
450
451       free (self);
452     }
453 }
454
455 /*
456 //      Local Variables:
457 //      mode: C
458 //      c-file-style: "gnu"
459 //      indent-tabs-mode: nil
460 //      End:
461 */