d8ad0dfefd585da7d8be97c3e7bb30f67c322ed5
[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 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <pwd.h>
29 #include <stdint.h>
30 #include <time.h>
31
32
33 Html
34 ctgit_repo_link (struct ctgit_query* query,
35                  const char* repo_prefix,
36                  int repo_prefix_len,
37                  const char* repo,
38                  int repo_len,
39                  const char* object,
40                  int object_len,
41                  const char* action,
42                  Html text)
43 {
44   return html (
45                html_tag ("a",
46                          html_attr ("href", html (
47                                                   html_fmt ("%s?repo=%.*s%s%.*s",
48                                                             query->request->script_name,
49                                                             repo_prefix_len, repo_prefix,
50                                                             repo_prefix && repo ? "/" : "",
51                                                             repo_len, repo,
52                                                             object_len, object),
53                                                   action ?
54                                                   html_fmt ("&action=%s", action) : html (),
55                                                   object ?
56                                                   html_fmt ("&object=%.*s", object_len, object) : html ()
57                                                   )
58                                     )
59                          ),
60                text
61                );
62 }
63
64 static struct ctgit_repo_info* in_flight;       /* stupid git callback has no void* userdata; we have to pass self in a global */
65
66 static void
67 ctgit_name_conf (struct ctgit_repo_info* self, const char *value)
68 {
69   self->name = cwa_strndup (value, SIZE_MAX);
70 }
71
72
73 static void
74 ctgit_description_conf (struct ctgit_repo_info* self, const char *value)
75 {
76   self->description = cwa_strndup (value, SIZE_MAX);
77 }
78
79
80 static void
81 ctgit_giturl_conf (struct ctgit_repo_info* self, const char *value)
82 {
83   self->giturl = cwa_strndup (value, SIZE_MAX);
84 }
85
86
87 static void
88 ctgit_readme_conf (struct ctgit_repo_info* self, const char *value)
89 {
90   self->readme = cwa_strndup (value, SIZE_MAX);
91 }
92
93
94 static void
95 ctgit_owner_conf (struct ctgit_repo_info* self, const char *value)
96 {
97   self->owner = cwa_strndup (value, SIZE_MAX);
98 }
99
100
101 int
102 ctgit_repo_conf_cb (const char *var, const char *value)
103 {
104 #define CTGIT_CONF(name, _) {CTGIT_CONF_PREFIX #name, ctgit_##name##_conf},
105   struct conf_table{
106     char* name;
107     void (*cb)(struct ctgit_repo_info* self, const char *value);
108   } cmds[] = {CTGIT_CONFS {"", NULL}};
109 #undef CTGIT_CONF
110
111   for (struct conf_table* j = cmds; j->cb; ++j)
112     {
113       if (!strcmp (j->name, var))
114         {
115           j->cb (in_flight, value);
116           break;
117         }
118     }
119   return 1;
120 }
121
122 struct ctgit_repo_info*
123 ctgit_repoinfo_new (struct ctgit_query* query, const char* path)
124 {
125   if (!path)
126     return NULL;
127
128   struct ctgit_repo_info* self = cwa_malloc (sizeof(struct ctgit_repo_info));
129
130   self->name = NULL;
131   self->owner = NULL;
132   self->description = NULL;
133   self->giturl = NULL;
134   self->readme = NULL;
135
136   self->last_commit = NULL;
137   self->last_tree = NULL;
138   self->last_head = NULL;
139
140   llist_init (&self->node);
141   self->path = cwa_strndup (path, SIZE_MAX);
142
143   chdir (path);
144
145   const char* subdir = setup_git_directory ();
146   /* strip subdir part off */
147   self->path [strlen (path) - (subdir ? strlen (subdir):0)] = '\0';
148
149   struct stat st;
150
151   if (!stat (".git/refs/heads", &st))
152     self->age = difftime (query->now, st.st_mtime);
153   else
154     self->age = ~0;
155
156   /* find the head which points to the last commit */
157   FILE* branches = ctgit_git_open ("branch --no-color --no-abbrev -v");
158   char name[128] = ".git/refs/heads/";
159   char sha1[41];
160   char* head = name + 16;
161
162   while (fscanf (branches, "%*[* ] %111s %40s", head, sha1) == 2)
163     {
164       while (fgetc(branches) != '\n');
165
166       if (!stat (name, &st))
167         if (difftime (query->now, st.st_mtime) == self->age)
168           break;
169     }
170   ctgit_git_close (branches);
171   self->last_commit = cwa_strndup (sha1, 40);
172   self->last_head = cwa_strndup (name + 5, SIZE_MAX);
173
174   /* find the tree for the last commit */
175   FILE* commit = ctgit_git_open ("cat-file commit %s", sha1);
176   if (fscanf (commit, "tree %40s", sha1) == 1)
177     {
178       self->last_tree = cwa_strndup (sha1, 40);
179     }
180   ctgit_git_close (commit);
181
182   /* get [ctgit] subsection out of .git/config */
183   in_flight = self;
184   git_config (ctgit_repo_conf_cb);
185
186   if (!self->name)
187     {
188       /* strip 'name' out of path when not in .git/config */
189       const char* n;
190       while ((n = strrchr (self->path, '/')))
191         if (n[1])
192           {
193             ++n;
194             break;
195           }
196
197       if (!n)
198         n = self->path;
199
200       self->name = cwa_strndup (n, SIZE_MAX);
201     }
202
203   char buf[512];
204   if (stat (path, &st))
205     return NULL; // error ILLEGAL dir
206
207   /* set owner if not already set by gitconfig */
208   if (!self->owner)
209     {
210       struct passwd* pw;
211       if ((pw = getpwuid (st.st_uid)) != NULL)
212         self->owner = cwa_strndup (pw->pw_gecos, strchr (pw->pw_gecos, ',') - pw->pw_gecos);
213       else
214         self->owner = cwa_strndup ("unknown", 100);
215     }
216
217   /* set description if not already set by gitconfig */
218   if (!self->description)
219     {
220       FILE* desc = fopen (".git/description", "r");
221       if (desc)
222         {
223           fgets (buf, 512, desc);
224           fclose (desc);
225           char* e = strchr (buf, '\n');
226           if (e)
227             *e = '\0';
228
229           self->description = cwa_strndup (buf, 40);
230         }
231     }
232
233   /* trim description size */
234   if (self->description)
235     {
236       size_t l = strlen (self->description);
237       if (l >= 39)
238         {
239           self->description[l-1] = '.';
240           self->description[l-2] = '.';
241           self->description[l-3] = '.';
242         }
243     }
244
245   return self;
246 }
247
248 void
249 ctgit_repoinfo_free (struct ctgit_repo_info* self)
250 {
251   if (self)
252     {
253       llist_unlink (&self->node);
254
255       free (self->path);
256       free (self->name);
257
258       free (self->owner);
259       free (self->description);
260       free (self->giturl);
261       free (self->readme);
262
263       free (self->last_commit);
264       free (self->last_tree);
265       free (self->last_head);
266
267       free (self);
268     }
269 }