stagit-index.c (5373B)
1 #include <err.h>
2 #include <limits.h>
3 #include <stdbool.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <time.h>
8 #include <unistd.h>
9
10 #include <git2.h>
11
12 static git_repository *repo;
13
14 static const char *relpath = "";
15
16 static char description[255] = "Secrets from the Second Foundation.";
17 static char const title[255] = "Trantor Holocron";
18 static char *name = "";
19 static char owner[255];
20
21 void
22 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
23 {
24 int r;
25
26 r = snprintf(buf, bufsiz, "%s%s%s",
27 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
28 if (r < 0 || (size_t)r >= bufsiz)
29 errx(1, "path truncated: '%s%s%s'",
30 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
31 }
32
33 /* Escape characters below as HTML 2.0 / XML 1.0. */
34 void
35 xmlencode(FILE *fp, const char *s, size_t len)
36 {
37 size_t i;
38
39 for (i = 0; *s && i < len; s++, i++) {
40 switch(*s) {
41 case '<': fputs("<", fp); break;
42 case '>': fputs(">", fp); break;
43 case '\'': fputs("'" , fp); break;
44 case '&': fputs("&", fp); break;
45 case '"': fputs(""", fp); break;
46 default: fputc(*s, fp);
47 }
48 }
49 }
50
51 void
52 printtimeshort(FILE *fp, const git_time *intime)
53 {
54 struct tm *intm;
55 time_t t;
56 char out[32];
57
58 t = (time_t)intime->time;
59 if (!(intm = gmtime(&t)))
60 return;
61 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
62 fputs(out, fp);
63 }
64
65 void
66 writeheader(FILE *fp)
67 {
68 fputs("<!DOCTYPE html>\n"
69 "<html lang=en>\n<head>\n"
70 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
71 "<title>", fp);
72 xmlencode(fp, title, strlen(title));
73 fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
74 fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
75 fputs("</head>\n<body>\n<header>\n", fp);
76 fprintf(fp, "<img style=\"float:right\" width=\"80\" height=\"80\" alt=\"\" src=\"/%slogo.svg\">\n", relpath);
77 fprintf(fp, "<h1>%s</h1>\n", title);
78 fprintf(fp, "<p><em>");
79 xmlencode(fp, description, strlen(description));
80 fputs("</em></p>\n"
81 "</header>\n<main>\n"
82 "<table id=\"index\"><thead><tr>\n"
83 "<th>Name</th><th>Description</th><th>Owner</th><th>Last commit</th>"
84 "</tr></thead><tbody>\n", fp);
85 }
86
87 void
88 writefooter(FILE *fp)
89 {
90 fputs("</tbody>\n</table>\n</main>\n</body>\n</html>\n", fp);
91 }
92
93 int
94 writelog(FILE *fp, bool is_public)
95 {
96 git_commit *commit = NULL;
97 const git_signature *author;
98 git_revwalk *w = NULL;
99 git_oid id;
100 char *stripped_name = NULL, *p;
101 int ret = 0;
102
103 git_revwalk_new(&w, repo);
104 git_revwalk_push_head(w);
105 git_revwalk_simplify_first_parent(w);
106
107 if (git_revwalk_next(&id, w) ||
108 git_commit_lookup(&commit, repo, &id)) {
109 ret = -1;
110 goto err;
111 }
112
113 author = git_commit_author(commit);
114
115 /* strip .git suffix */
116 if (!(stripped_name = strdup(name)))
117 err(1, "strdup");
118 if ((p = strrchr(stripped_name, '.')))
119 if (!strcmp(p, ".git"))
120 *p = '\0';
121
122 fputs("<tr><td>", fp);
123 if (is_public) {
124 fputs("<a href=\"", fp);
125 xmlencode(fp, stripped_name, strlen(stripped_name));
126 fputs("/\">", fp);
127 xmlencode(fp, stripped_name, strlen(stripped_name));
128 fputs("</a>", fp);
129 } else {
130 xmlencode(fp, stripped_name, strlen(stripped_name));
131 }
132 fputs("</td><td>", fp);
133 xmlencode(fp, description, strlen(description));
134 fputs("</td><td>", fp);
135 xmlencode(fp, owner, strlen(owner));
136 fputs("</td><td>", fp);
137 if (author)
138 printtimeshort(fp, &(author->when));
139 fputs("</td></tr>", fp);
140
141 git_commit_free(commit);
142 err:
143 git_revwalk_free(w);
144 free(stripped_name);
145
146 return ret;
147 }
148
149 int
150 main(int argc, char *argv[])
151 {
152 FILE *fp;
153 char path[PATH_MAX], repodirabs[PATH_MAX + 1];
154 const char *repodir;
155 int i, ret = 0;
156 bool is_public = false;
157
158 if (argc < 2) {
159 fprintf(stderr, "%s [repodir...]\n", argv[0]);
160 return 1;
161 }
162
163 git_libgit2_init();
164
165 #ifdef __OpenBSD__
166 if (pledge("stdio rpath", NULL) == -1)
167 err(1, "pledge");
168 #endif
169
170 writeheader(stdout);
171
172 for (i = 1; i < argc; i++) {
173 repodir = argv[i];
174 if (!realpath(repodir, repodirabs))
175 err(1, "realpath");
176
177 if (git_repository_open_ext(&repo, repodir,
178 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
179 fprintf(stderr, "%s: cannot open repository\n", argv[0]);
180 ret = 1;
181 continue;
182 }
183
184 /* use directory name as name */
185 if ((name = strrchr(repodirabs, '/')))
186 name++;
187 else
188 name = "";
189
190 /* read description or .git/description */
191 joinpath(path, sizeof(path), repodir, "description");
192 if (!(fp = fopen(path, "r"))) {
193 joinpath(path, sizeof(path), repodir, ".git/description");
194 fp = fopen(path, "r");
195 }
196 description[0] = '\0';
197 if (fp) {
198 if (!fgets(description, sizeof(description), fp))
199 description[0] = '\0';
200 fclose(fp);
201 }
202
203 /* read owner or .git/owner */
204 joinpath(path, sizeof(path), repodir, "owner");
205 if (!(fp = fopen(path, "r"))) {
206 joinpath(path, sizeof(path), repodir, ".git/owner");
207 fp = fopen(path, "r");
208 }
209 owner[0] = '\0';
210 if (fp) {
211 if (!fgets(owner, sizeof(owner), fp))
212 owner[0] = '\0';
213 owner[strcspn(owner, "\n")] = '\0';
214 fclose(fp);
215 }
216
217 /* check if repo is public */
218 joinpath(path, sizeof(path), repodir, "git-daemon-export-ok");
219 is_public = fopen(path, "r");
220
221 writelog(stdout, is_public);
222 }
223 writefooter(stdout);
224
225 /* cleanup */
226 git_repository_free(repo);
227 git_libgit2_shutdown();
228
229 return ret;
230 }