3c4ecf0b49366ca20771ea8f86899016c712ba07
[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, "author ", 7))
186             {
187               const char* author_beg = i+7;
188               char* email_beg = strchr (i, '<');
189               char* email_end = strchr (email_beg, '>');
190
191               self->last_author = cwa_strndup (i+7, email_beg - i - 8);
192               self->last_email = cwa_strndup (email_beg + 1, email_end - email_beg - 1);
193             }
194           (i = strchr (i, '\n')) && ++i;
195         }
196     }
197   return 0;
198 }
199
200
201 struct webgit_repo_info*
202 webgit_repoinfo_new (struct webgit_query* query, const char* path)
203 {
204   char buf[512];
205
206   if (!path)
207     return NULL;
208
209   struct webgit_repo_info* self = cwa_malloc (sizeof (struct webgit_repo_info));
210
211   self->query = query;
212
213   self->name = NULL;
214   self->owner = NULL;
215   self->description = NULL;
216   self->giturl = NULL;
217   self->readme = NULL;
218
219
220   llist_init (&self->node);
221   self->path = cwa_strndup (path, SIZE_MAX);
222
223   chdir (path);
224
225   const char* subdir = setup_git_directory ();
226   /* strip subdir part off */
227   self->path [strlen (path) - (subdir ? strlen (subdir):0)] = '\0';
228
229   in_flight = self;
230   git_config (webgit_repo_conf_cb);
231
232   if (!self->name)
233     {
234       /* strip 'name' out of path when not in .git/config */
235       const char* n;
236       while ((n = strrchr (self->path, '/')))
237         if (n[1])
238           {
239             ++n;
240             break;
241           }
242
243       if (!n)
244         n = self->path;
245
246       self->name = cwa_strndup (n, SIZE_MAX);
247     }
248
249   /* set owner if not already set by gitconfig */
250   if (!self->owner)
251     {
252       struct stat st;
253       if (stat (path, &st))
254         return NULL; // error ILLEGAL dir
255
256       struct passwd* pw;
257       if ((pw = getpwuid (st.st_uid)) != NULL)
258         self->owner = cwa_strndup (pw->pw_gecos, strchr (pw->pw_gecos, ',') - pw->pw_gecos);
259       else
260         self->owner = cwa_strndup ("unknown", 100);
261     }
262
263   /* set description if not already set by gitconfig */
264   if (!self->description)
265     {
266       FILE* desc = fopen (".git/description", "r");
267       if (desc)
268         {
269           fgets (buf, 512, desc);
270           fclose (desc);
271           char* e = strchr (buf, '\n');
272           if (e)
273             *e = '\0';
274
275           self->description = cwa_strndup (buf, 40);
276         }
277     }
278
279   /* trim description size */
280   if (self->description)
281     {
282       size_t l = strlen (self->description);
283       if (l >= 39)
284         {
285           self->description[l-1] = '.';
286           self->description[l-2] = '.';
287           self->description[l-3] = '.';
288         }
289     }
290
291
292
293   /* find 'last' information */
294   struct webgit_repo_info ri;
295   ri.last_commit = NULL;
296   ri.last_tree = NULL;
297   ri.last_head = NULL;
298   ri.last_author = NULL;
299   ri.last_email = NULL;
300   ri.query = query;
301   ri.age = ~0;
302   for_each_branch_ref (find_last, &ri);
303
304   self->last_commit = ri.last_commit ? ri.last_commit : cwa_strndup("NO_COMMIT", SIZE_MAX);
305   self->last_tree = ri.last_tree ? ri.last_tree : cwa_strndup("NO_TREE", SIZE_MAX);
306   self->last_head = ri.last_head ? ri.last_head : cwa_strndup("NO_HEAD", SIZE_MAX);
307   self->last_author = ri.last_author ? ri.last_author : cwa_strndup("NO_AUTHOR", SIZE_MAX);
308   self->last_email = ri.last_email ? ri.last_email : cwa_strndup("NO_EMAIL", SIZE_MAX);
309   self->age = ri.age;
310
311   return self;
312 }
313
314 void
315 webgit_repoinfo_free (struct webgit_repo_info* self)
316 {
317   if (self)
318     {
319       llist_unlink (&self->node);
320
321       free (self->path);
322       free (self->name);
323
324       free (self->owner);
325       free (self->description);
326       free (self->giturl);
327       free (self->readme);
328
329       free (self->last_commit);
330       free (self->last_tree);
331       free (self->last_head);
332       free (self->last_author);
333       free (self->last_email);
334
335       free (self);
336     }
337 }