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