stagit-index.c - stagit - static git page generator | |
git clone git://git.codemadness.org/stagit | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
stagit-index.c (6286B) | |
--- | |
1 #include <err.h> | |
2 #include <limits.h> | |
3 #include <stdio.h> | |
4 #include <stdlib.h> | |
5 #include <string.h> | |
6 #include <time.h> | |
7 #include <unistd.h> | |
8 | |
9 #include <git2.h> | |
10 | |
11 static git_repository *repo; | |
12 | |
13 static const char *relpath = ""; | |
14 | |
15 static char description[255] = "Repositories"; | |
16 static char *name = ""; | |
17 static char owner[255]; | |
18 | |
19 /* Handle read or write errors for a FILE * stream */ | |
20 void | |
21 checkfileerror(FILE *fp, const char *name, int mode) | |
22 { | |
23 if (mode == 'r' && ferror(fp)) | |
24 errx(1, "read error: %s", name); | |
25 else if (mode == 'w' && (fflush(fp) || ferror(fp))) | |
26 errx(1, "write error: %s", name); | |
27 } | |
28 | |
29 void | |
30 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) | |
31 { | |
32 int r; | |
33 | |
34 r = snprintf(buf, bufsiz, "%s%s%s", | |
35 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "… | |
36 if (r < 0 || (size_t)r >= bufsiz) | |
37 errx(1, "path truncated: '%s%s%s'", | |
38 path, path[0] && path[strlen(path) - 1] != '/' ?… | |
39 } | |
40 | |
41 /* Percent-encode, see RFC3986 section 2.1. */ | |
42 void | |
43 percentencode(FILE *fp, const char *s, size_t len) | |
44 { | |
45 static char tab[] = "0123456789ABCDEF"; | |
46 unsigned char uc; | |
47 size_t i; | |
48 | |
49 for (i = 0; *s && i < len; s++, i++) { | |
50 uc = *s; | |
51 /* NOTE: do not encode '/' for paths or ",-." */ | |
52 if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') || | |
53 uc == '[' || uc == ']') { | |
54 putc('%', fp); | |
55 putc(tab[(uc >> 4) & 0x0f], fp); | |
56 putc(tab[uc & 0x0f], fp); | |
57 } else { | |
58 putc(uc, fp); | |
59 } | |
60 } | |
61 } | |
62 | |
63 /* Escape characters below as HTML 2.0 / XML 1.0. */ | |
64 void | |
65 xmlencode(FILE *fp, const char *s, size_t len) | |
66 { | |
67 size_t i; | |
68 | |
69 for (i = 0; *s && i < len; s++, i++) { | |
70 switch(*s) { | |
71 case '<': fputs("<", fp); break; | |
72 case '>': fputs(">", fp); break; | |
73 case '\'': fputs("'" , fp); break; | |
74 case '&': fputs("&", fp); break; | |
75 case '"': fputs(""", fp); break; | |
76 default: putc(*s, fp); | |
77 } | |
78 } | |
79 } | |
80 | |
81 void | |
82 printtimeshort(FILE *fp, const git_time *intime) | |
83 { | |
84 struct tm *intm; | |
85 time_t t; | |
86 char out[32]; | |
87 | |
88 t = (time_t)intime->time; | |
89 if (!(intm = gmtime(&t))) | |
90 return; | |
91 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm); | |
92 fputs(out, fp); | |
93 } | |
94 | |
95 void | |
96 writeheader(FILE *fp) | |
97 { | |
98 fputs("<!DOCTYPE html>\n" | |
99 "<html>\n<head>\n" | |
100 "<meta http-equiv=\"Content-Type\" content=\"text/html; … | |
101 "<meta name=\"viewport\" content=\"width=device-width, i… | |
102 "<title>", fp); | |
103 xmlencode(fp, description, strlen(description)); | |
104 fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" hre… | |
105 fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%… | |
106 fputs("</head>\n<body>\n", fp); | |
107 fprintf(fp, "<table>\n<tr><td><img src=\"%slogo.png\" alt=\"\" w… | |
108 "<td><span class=\"desc\">", relpath); | |
109 xmlencode(fp, description, strlen(description)); | |
110 fputs("</span></td></tr><tr><td></td><td>\n" | |
111 "</td></tr>\n</table>\n<hr/>\n<div id=\"content\">\n" | |
112 "<table id=\"index\"><thead>\n" | |
113 "<tr><td><b>Name</b></td><td><b>Description</b></td><td>… | |
114 "<td><b>Last commit</b></td></tr>" | |
115 "</thead><tbody>\n", fp); | |
116 } | |
117 | |
118 void | |
119 writefooter(FILE *fp) | |
120 { | |
121 fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp); | |
122 } | |
123 | |
124 int | |
125 writelog(FILE *fp) | |
126 { | |
127 git_commit *commit = NULL; | |
128 const git_signature *author; | |
129 git_revwalk *w = NULL; | |
130 git_oid id; | |
131 char *stripped_name = NULL, *p; | |
132 int ret = 0; | |
133 | |
134 git_revwalk_new(&w, repo); | |
135 git_revwalk_push_head(w); | |
136 | |
137 if (git_revwalk_next(&id, w) || | |
138 git_commit_lookup(&commit, repo, &id)) { | |
139 ret = -1; | |
140 goto err; | |
141 } | |
142 | |
143 author = git_commit_author(commit); | |
144 | |
145 /* strip .git suffix */ | |
146 if (!(stripped_name = strdup(name))) | |
147 err(1, "strdup"); | |
148 if ((p = strrchr(stripped_name, '.'))) | |
149 if (!strcmp(p, ".git")) | |
150 *p = '\0'; | |
151 | |
152 fputs("<tr><td><a href=\"", fp); | |
153 percentencode(fp, stripped_name, strlen(stripped_name)); | |
154 fputs("/log.html\">", fp); | |
155 xmlencode(fp, stripped_name, strlen(stripped_name)); | |
156 fputs("</a></td><td>", fp); | |
157 xmlencode(fp, description, strlen(description)); | |
158 fputs("</td><td>", fp); | |
159 xmlencode(fp, owner, strlen(owner)); | |
160 fputs("</td><td>", fp); | |
161 if (author) | |
162 printtimeshort(fp, &(author->when)); | |
163 fputs("</td></tr>", fp); | |
164 | |
165 git_commit_free(commit); | |
166 err: | |
167 git_revwalk_free(w); | |
168 free(stripped_name); | |
169 | |
170 return ret; | |
171 } | |
172 | |
173 int | |
174 main(int argc, char *argv[]) | |
175 { | |
176 FILE *fp; | |
177 char path[PATH_MAX], repodirabs[PATH_MAX + 1]; | |
178 const char *repodir; | |
179 int i, ret = 0; | |
180 | |
181 if (argc < 2) { | |
182 fprintf(stderr, "usage: %s [repodir...]\n", argv[0]); | |
183 return 1; | |
184 } | |
185 | |
186 /* do not search outside the git repository: | |
187 GIT_CONFIG_LEVEL_APP is the highest level currently */ | |
188 git_libgit2_init(); | |
189 for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++) | |
190 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, ""); | |
191 /* do not require the git repository to be owned by the current … | |
192 git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0); | |
193 | |
194 #ifdef __OpenBSD__ | |
195 if (pledge("stdio rpath", NULL) == -1) | |
196 err(1, "pledge"); | |
197 #endif | |
198 | |
199 writeheader(stdout); | |
200 | |
201 for (i = 1; i < argc; i++) { | |
202 repodir = argv[i]; | |
203 if (!realpath(repodir, repodirabs)) | |
204 err(1, "realpath"); | |
205 | |
206 if (git_repository_open_ext(&repo, repodir, | |
207 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) { | |
208 fprintf(stderr, "%s: cannot open repository\n", … | |
209 ret = 1; | |
210 continue; | |
211 } | |
212 | |
213 /* use directory name as name */ | |
214 if ((name = strrchr(repodirabs, '/'))) | |
215 name++; | |
216 else | |
217 name = ""; | |
218 | |
219 /* read description or .git/description */ | |
220 joinpath(path, sizeof(path), repodir, "description"); | |
221 if (!(fp = fopen(path, "r"))) { | |
222 joinpath(path, sizeof(path), repodir, ".git/desc… | |
223 fp = fopen(path, "r"); | |
224 } | |
225 description[0] = '\0'; | |
226 if (fp) { | |
227 if (!fgets(description, sizeof(description), fp)) | |
228 description[0] = '\0'; | |
229 checkfileerror(fp, "description", 'r'); | |
230 fclose(fp); | |
231 } | |
232 | |
233 /* read owner or .git/owner */ | |
234 joinpath(path, sizeof(path), repodir, "owner"); | |
235 if (!(fp = fopen(path, "r"))) { | |
236 joinpath(path, sizeof(path), repodir, ".git/owne… | |
237 fp = fopen(path, "r"); | |
238 } | |
239 owner[0] = '\0'; | |
240 if (fp) { | |
241 if (!fgets(owner, sizeof(owner), fp)) | |
242 owner[0] = '\0'; | |
243 checkfileerror(fp, "owner", 'r'); | |
244 fclose(fp); | |
245 owner[strcspn(owner, "\n")] = '\0'; | |
246 } | |
247 writelog(stdout); | |
248 } | |
249 writefooter(stdout); | |
250 | |
251 /* cleanup */ | |
252 git_repository_free(repo); | |
253 git_libgit2_shutdown(); | |
254 | |
255 checkfileerror(stdout, "<stdout>", 'w'); | |
256 | |
257 return ret; | |
258 } |