support for last_committer and last_author when scanning repos
[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
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_giturl_conf (struct webgit_repo_info* self, const char *value)
85 {
86   self->giturl = cwa_strndup (value, SIZE_MAX);
87 }
88
89
90 static void
91 webgit_readme_conf (struct webgit_repo_info* self, const char *value)
92 {
93   self->readme = cwa_strndup (value, SIZE_MAX);
94 }
95
96
97 static void
98 webgit_owner_conf (struct webgit_repo_info* self, const char *value)
99 {
100   self->owner = cwa_strndup (value, SIZE_MAX);
101 }
102
103
104 int
105 webgit_repo_conf_cb (const char *var, const char *value)
106 {
107 #define WEBGIT_CONF(name, _) {WEBGIT_CONF_PREFIX #name, webgit_##name##_conf},
108   struct conf_table{
109     char* name;
110     void (*cb)(struct webgit_repo_info* self, const char *value);
111   } cmds[] = {WEBGIT_CONFS {"", NULL}};
112 #undef WEBGIT_CONF
113
114   for (struct conf_table* j = cmds; j->cb; ++j)
115     {
116       if (!strcmp (j->name, var))
117         {
118           j->cb (in_flight, value);
119           break;
120         }
121     }
122   return 1;
123 }
124
125
126 static int
127 find_last (const char *refname, const unsigned char *sha1, int flags, void *ri)
128 {
129   struct webgit_repo_info* self = (struct webgit_repo_info*) ri;
130
131   void* buf;
132   unsigned long size;
133
134   buf = read_object_with_reference (sha1, "commit", &size, NULL);
135
136   if (!buf)
137     return 1;
138
139   const char* author_beg = NULL;
140   const char* author_end = NULL;
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 != ~0 && (self->age == ~0 || 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 && 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               const char* name_beg = i+10;
188               char* email_beg = strchr (i, '<');
189               char* email_end = strchr (email_beg, '>');
190
191               self->last_committer_name = cwa_strndup (i+10, email_beg - i - 11);
192               self->last_committer_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
193             }
194           else if (!strncmp (i, "author ", 7))
195             {
196               const char* name_beg = i+7;
197               char* email_beg = strchr (i, '<');
198               char* email_end = strchr (email_beg, '>');
199
200               self->last_author_name = cwa_strndup (i+7, email_beg - i - 8);
201               self->last_author_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
202             }
203           (i = strchr (i, '\n')) && ++i;
204         }
205     }
206   return 0;
207 }
208
209
210 struct webgit_repo_info*
211 webgit_repoinfo_new (struct webgit_query* query, const char* path)
212 {
213   char buf[512];
214
215   if (!path)
216     return NULL;
217
218   struct webgit_repo_info* self = cwa_malloc (sizeof (struct webgit_repo_info));
219
220   self->query = query;
221
222   self->name = NULL;
223   self->owner = NULL;
224   self->description = NULL;
225   self->giturl = NULL;
226   self->readme = NULL;
227
228
229   llist_init (&self->node);
230   self->path = cwa_strndup (path, SIZE_MAX);
231
232   chdir (path);
233
234   const char* subdir = setup_git_directory ();
235   /* strip subdir part off */
236   self->path [strlen (path) - (subdir ? strlen (subdir):0)] = '\0';
237
238   in_flight = self;
239   git_config (webgit_repo_conf_cb);
240
241   /* use path, when no name given */
242   if (!self->name)
243     self->name = cwa_strndup (self->path, SIZE_MAX);
244
245   /* set owner if not already set by gitconfig */
246   if (!self->owner)
247     {
248       struct stat st;
249       if (stat (path, &st))
250         return NULL; // error ILLEGAL dir
251
252       struct passwd* pw;
253       if ((pw = getpwuid (st.st_uid)) != NULL)
254         self->owner = cwa_strndup (pw->pw_gecos, strchr (pw->pw_gecos, ',') - pw->pw_gecos);
255       else
256         self->owner = cwa_strndup ("unknown", 100);
257     }
258
259   /* set description if not already set by gitconfig */
260   if (!self->description)
261     {
262       FILE* desc = fopen (".git/description", "r");
263       if (desc)
264         {
265           fgets (buf, 512, desc);
266           fclose (desc);
267           char* e = strchr (buf, '\n');
268           if (e)
269             *e = '\0';
270
271           self->description = cwa_strndup (buf, 40);
272         }
273     }
274
275   /* trim description size */
276   if (self->description)
277     {
278       size_t l = strlen (self->description);
279       if (l >= 39)
280         {
281           self->description[l-1] = '.';
282           self->description[l-2] = '.';
283           self->description[l-3] = '.';
284         }
285     }
286
287
288
289   /* find 'last' information */
290   struct webgit_repo_info ri;
291   ri.last_commit = NULL;
292   ri.last_tree = NULL;
293   ri.last_head = NULL;
294   ri.last_committer_name = NULL;
295   ri.last_committer_email = NULL;
296   ri.last_author_name = NULL;
297   ri.last_author_email = NULL;
298   ri.query = query;
299   ri.age = ~0;
300   for_each_branch_ref (find_last, &ri);
301
302   self->last_commit = ri.last_commit ? ri.last_commit : cwa_strndup("NO_COMMIT", SIZE_MAX);
303   self->last_tree = ri.last_tree ? ri.last_tree : cwa_strndup("NO_TREE", SIZE_MAX);
304   self->last_head = ri.last_head ? ri.last_head : cwa_strndup("NO_HEAD", SIZE_MAX);
305   self->last_committer_name = ri.last_committer_name ?
306     ri.last_committer_name : cwa_strndup("NO_COMMITTER", SIZE_MAX);
307   self->last_committer_email = ri.last_committer_email ?
308     ri.last_committer_email : cwa_strndup("NO_COMMITTER_EMAIL", SIZE_MAX);
309   self->last_author_name = ri.last_author_name ?
310     ri.last_author_name : cwa_strndup("NO_AUTHOR", SIZE_MAX);
311   self->last_author_email = ri.last_author_email ?
312     ri.last_author_email : cwa_strndup("NO_AUTHOR_EMAIL", SIZE_MAX);
313   self->age = ri.age;
314
315   return self;
316 }
317
318 void
319 webgit_repoinfo_free (struct webgit_repo_info* self)
320 {
321   if (self)
322     {
323       llist_unlink (&self->node);
324
325       free (self->path);
326       free (self->name);
327
328       free (self->owner);
329       free (self->description);
330       free (self->giturl);
331       free (self->readme);
332
333       free (self->last_commit);
334       free (self->last_tree);
335       free (self->last_head);
336       free (self->last_committer_name);
337       free (self->last_committer_email);
338       free (self->last_author_name);
339       free (self->last_author_email);
340
341       free (self);
342     }
343 }