|
|
stagit.c - stagit - static git page generator |
|
|
 |
git clone git://git.codemadness.org/stagit (git://git.codemadness.org) |
|
|
 |
Log |
|
|
 |
Files |
|
|
 |
Refs |
|
|
 |
README |
|
|
 |
LICENSE |
|
|
|
--- |
|
|
|
stagit.c (36246B) |
|
|
|
--- |
|
|
|
1 #include <sys/stat.h> |
|
|
|
2 #include <sys/types.h> |
|
|
|
3 |
|
|
|
4 #include <err.h> |
|
|
|
5 #include <errno.h> |
|
|
|
6 #include <libgen.h> |
|
|
|
7 #include <limits.h> |
|
|
|
8 #include <stdint.h> |
|
|
|
9 #include <stdio.h> |
|
|
|
10 #include <stdlib.h> |
|
|
|
11 #include <string.h> |
|
|
|
12 #include <time.h> |
|
|
|
13 #include <unistd.h> |
|
|
|
14 |
|
|
|
15 #include <git2.h> |
|
|
|
16 |
|
|
|
17 #include "compat.h" |
|
|
|
18 |
|
|
|
19 #define LEN(s) (sizeof(s)/sizeof(*s)) |
|
|
|
20 |
|
|
|
21 struct deltainfo { |
|
|
|
22 git_patch *patch; |
|
|
|
23 |
|
|
|
24 size_t addcount; |
|
|
|
25 size_t delcount; |
|
|
|
26 }; |
|
|
|
27 |
|
|
|
28 struct commitinfo { |
|
|
|
29 const git_oid *id; |
|
|
|
30 |
|
|
|
31 char oid[GIT_OID_HEXSZ + 1]; |
|
|
|
32 char parentoid[GIT_OID_HEXSZ + 1]; |
|
|
|
33 |
|
|
|
34 const git_signature *author; |
|
|
|
35 const git_signature *committer; |
|
|
|
36 const char *summary; |
|
|
|
37 const char *msg; |
|
|
|
38 |
|
|
|
39 git_diff *diff; |
|
|
|
40 git_commit *commit; |
|
|
|
41 git_commit *parent; |
|
|
|
42 git_tree *commit_tree; |
|
|
|
43 git_tree *parent_tree; |
|
|
|
44 |
|
|
|
45 size_t addcount; |
|
|
|
46 size_t delcount; |
|
|
|
47 size_t filecount; |
|
|
|
48 |
|
|
|
49 struct deltainfo **deltas; |
|
|
|
50 size_t ndeltas; |
|
|
|
51 }; |
|
|
|
52 |
|
|
|
53 /* reference and associated data for sorting */ |
|
|
|
54 struct referenceinfo { |
|
|
|
55 struct git_reference *ref; |
|
|
|
56 struct commitinfo *ci; |
|
|
|
57 }; |
|
|
|
58 |
|
|
|
59 static git_repository *repo; |
|
|
|
60 |
|
|
|
61 static const char *baseurl = ""; /* base URL to make absolute RSS/Atom URI */ |
|
|
|
62 static const char *relpath = ""; |
|
|
|
63 static const char *repodir; |
|
|
|
64 |
|
|
|
65 static char *name = ""; |
|
|
|
66 static char *strippedname = ""; |
|
|
|
67 static char description[255]; |
|
|
|
68 static char cloneurl[1024]; |
|
|
|
69 static char *submodules; |
|
|
|
70 static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:COPYING" }; |
|
|
|
71 static char *license; |
|
|
|
72 static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" }; |
|
|
|
73 static char *readme; |
|
|
|
74 static long long nlogcommits = -1; /* -1 indicates not used */ |
|
|
|
75 |
|
|
|
76 /* cache */ |
|
|
|
77 static git_oid lastoid; |
|
|
|
78 static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */ |
|
|
|
79 static FILE *rcachefp, *wcachefp; |
|
|
|
80 static const char *cachefile; |
|
|
|
81 |
|
|
|
82 /* Handle read or write errors for a FILE * stream */ |
|
|
|
83 void |
|
|
|
84 checkfileerror(FILE *fp, const char *name, int mode) |
|
|
|
85 { |
|
|
|
86 if (mode == 'r' && ferror(fp)) |
|
|
|
87 errx(1, "read error: %s", name); |
|
|
|
88 else if (mode == 'w' && (fflush(fp) || ferror(fp))) |
|
|
|
89 errx(1, "write error: %s", name); |
|
|
|
90 } |
|
|
|
91 |
|
|
|
92 void |
|
|
|
93 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) |
|
|
|
94 { |
|
|
|
95 int r; |
|
|
|
96 |
|
|
|
97 r = snprintf(buf, bufsiz, "%s%s%s", |
|
|
|
98 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); |
|
|
|
99 if (r < 0 || (size_t)r >= bufsiz) |
|
|
|
100 errx(1, "path truncated: '%s%s%s'", |
|
|
|
101 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); |
|
|
|
102 } |
|
|
|
103 |
|
|
|
104 void |
|
|
|
105 deltainfo_free(struct deltainfo *di) |
|
|
|
106 { |
|
|
|
107 if (!di) |
|
|
|
108 return; |
|
|
|
109 git_patch_free(di->patch); |
|
|
|
110 memset(di, 0, sizeof(*di)); |
|
|
|
111 free(di); |
|
|
|
112 } |
|
|
|
113 |
|
|
|
114 int |
|
|
|
115 commitinfo_getstats(struct commitinfo *ci) |
|
|
|
116 { |
|
|
|
117 struct deltainfo *di; |
|
|
|
118 git_diff_options opts; |
|
|
|
119 git_diff_find_options fopts; |
|
|
|
120 const git_diff_delta *delta; |
|
|
|
121 const git_diff_hunk *hunk; |
|
|
|
122 const git_diff_line *line; |
|
|
|
123 git_patch *patch = NULL; |
|
|
|
124 size_t ndeltas, nhunks, nhunklines; |
|
|
|
125 size_t i, j, k; |
|
|
|
126 |
|
|
|
127 if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit))) |
|
|
|
128 goto err; |
|
|
|
129 if (!git_commit_parent(&(ci->parent), ci->commit, 0)) { |
|
|
|
130 if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) { |
|
|
|
131 ci->parent = NULL; |
|
|
|
132 ci->parent_tree = NULL; |
|
|
|
133 } |
|
|
|
134 } |
|
|
|
135 |
|
|
|
136 git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION); |
|
|
|
137 opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH | |
|
|
|
138 GIT_DIFF_IGNORE_SUBMODULES | |
|
|
|
139 GIT_DIFF_INCLUDE_TYPECHANGE; |
|
|
|
140 if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts)) |
|
|
|
141 goto err; |
|
|
|
142 |
|
|
|
143 if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION)) |
|
|
|
144 goto err; |
|
|
|
145 /* find renames and copies, exact matches (no heuristic) for renames. */ |
|
|
|
146 fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES | |
|
|
|
147 GIT_DIFF_FIND_EXACT_MATCH_ONLY; |
|
|
|
148 if (git_diff_find_similar(ci->diff, &fopts)) |
|
|
|
149 goto err; |
|
|
|
150 |
|
|
|
151 ndeltas = git_diff_num_deltas(ci->diff); |
|
|
|
152 if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *)))) |
|
|
|
153 err(1, "calloc"); |
|
|
|
154 |
|
|
|
155 for (i = 0; i < ndeltas; i++) { |
|
|
|
156 if (git_patch_from_diff(&patch, ci->diff, i)) |
|
|
|
157 goto err; |
|
|
|
158 |
|
|
|
159 if (!(di = calloc(1, sizeof(struct deltainfo)))) |
|
|
|
160 err(1, "calloc"); |
|
|
|
161 di->patch = patch; |
|
|
|
162 ci->deltas[i] = di; |
|
|
|
163 |
|
|
|
164 delta = git_patch_get_delta(patch); |
|
|
|
165 |
|
|
|
166 /* skip stats for binary data */ |
|
|
|
167 if (delta->flags & GIT_DIFF_FLAG_BINARY) |
|
|
|
168 continue; |
|
|
|
169 |
|
|
|
170 nhunks = git_patch_num_hunks(patch); |
|
|
|
171 for (j = 0; j < nhunks; j++) { |
|
|
|
172 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j)) |
|
|
|
173 break; |
|
|
|
174 for (k = 0; ; k++) { |
|
|
|
175 if (git_patch_get_line_in_hunk(&line, patch, j, k)) |
|
|
|
176 break; |
|
|
|
177 if (line->old_lineno == -1) { |
|
|
|
178 di->addcount++; |
|
|
|
179 ci->addcount++; |
|
|
|
180 } else if (line->new_lineno == -1) { |
|
|
|
181 di->delcount++; |
|
|
|
182 ci->delcount++; |
|
|
|
183 } |
|
|
|
184 } |
|
|
|
185 } |
|
|
|
186 } |
|
|
|
187 ci->ndeltas = i; |
|
|
|
188 ci->filecount = i; |
|
|
|
189 |
|
|
|
190 return 0; |
|
|
|
191 |
|
|
|
192 err: |
|
|
|
193 git_diff_free(ci->diff); |
|
|
|
194 ci->diff = NULL; |
|
|
|
195 git_tree_free(ci->commit_tree); |
|
|
|
196 ci->commit_tree = NULL; |
|
|
|
197 git_tree_free(ci->parent_tree); |
|
|
|
198 ci->parent_tree = NULL; |
|
|
|
199 git_commit_free(ci->parent); |
|
|
|
200 ci->parent = NULL; |
|
|
|
201 |
|
|
|
202 if (ci->deltas) |
|
|
|
203 for (i = 0; i < ci->ndeltas; i++) |
|
|
|
204 deltainfo_free(ci->deltas[i]); |
|
|
|
205 free(ci->deltas); |
|
|
|
206 ci->deltas = NULL; |
|
|
|
207 ci->ndeltas = 0; |
|
|
|
208 ci->addcount = 0; |
|
|
|
209 ci->delcount = 0; |
|
|
|
210 ci->filecount = 0; |
|
|
|
211 |
|
|
|
212 return -1; |
|
|
|
213 } |
|
|
|
214 |
|
|
|
215 void |
|
|
|
216 commitinfo_free(struct commitinfo *ci) |
|
|
|
217 { |
|
|
|
218 size_t i; |
|
|
|
219 |
|
|
|
220 if (!ci) |
|
|
|
221 return; |
|
|
|
222 if (ci->deltas) |
|
|
|
223 for (i = 0; i < ci->ndeltas; i++) |
|
|
|
224 deltainfo_free(ci->deltas[i]); |
|
|
|
225 |
|
|
|
226 free(ci->deltas); |
|
|
|
227 git_diff_free(ci->diff); |
|
|
|
228 git_tree_free(ci->commit_tree); |
|
|
|
229 git_tree_free(ci->parent_tree); |
|
|
|
230 git_commit_free(ci->commit); |
|
|
|
231 git_commit_free(ci->parent); |
|
|
|
232 memset(ci, 0, sizeof(*ci)); |
|
|
|
233 free(ci); |
|
|
|
234 } |
|
|
|
235 |
|
|
|
236 struct commitinfo * |
|
|
|
237 commitinfo_getbyoid(const git_oid *id) |
|
|
|
238 { |
|
|
|
239 struct commitinfo *ci; |
|
|
|
240 |
|
|
|
241 if (!(ci = calloc(1, sizeof(struct commitinfo)))) |
|
|
|
242 err(1, "calloc"); |
|
|
|
243 |
|
|
|
244 if (git_commit_lookup(&(ci->commit), repo, id)) |
|
|
|
245 goto err; |
|
|
|
246 ci->id = id; |
|
|
|
247 |
|
|
|
248 git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit)); |
|
|
|
249 git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0)); |
|
|
|
250 |
|
|
|
251 ci->author = git_commit_author(ci->commit); |
|
|
|
252 ci->committer = git_commit_committer(ci->commit); |
|
|
|
253 ci->summary = git_commit_summary(ci->commit); |
|
|
|
254 ci->msg = git_commit_message(ci->commit); |
|
|
|
255 |
|
|
|
256 return ci; |
|
|
|
257 |
|
|
|
258 err: |
|
|
|
259 commitinfo_free(ci); |
|
|
|
260 |
|
|
|
261 return NULL; |
|
|
|
262 } |
|
|
|
263 |
|
|
|
264 int |
|
|
|
265 refs_cmp(const void *v1, const void *v2) |
|
|
|
266 { |
|
|
|
267 const struct referenceinfo *r1 = v1, *r2 = v2; |
|
|
|
268 time_t t1, t2; |
|
|
|
269 int r; |
|
|
|
270 |
|
|
|
271 if ((r = git_reference_is_tag(r1->ref) - git_reference_is_tag(r2->ref))) |
|
|
|
272 return r; |
|
|
|
273 |
|
|
|
274 t1 = r1->ci->author ? r1->ci->author->when.time : 0; |
|
|
|
275 t2 = r2->ci->author ? r2->ci->author->when.time : 0; |
|
|
|
276 if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1))) |
|
|
|
277 return r; |
|
|
|
278 |
|
|
|
279 return strcmp(git_reference_shorthand(r1->ref), |
|
|
|
280 git_reference_shorthand(r2->ref)); |
|
|
|
281 } |
|
|
|
282 |
|
|
|
283 int |
|
|
|
284 getrefs(struct referenceinfo **pris, size_t *prefcount) |
|
|
|
285 { |
|
|
|
286 struct referenceinfo *ris = NULL; |
|
|
|
287 struct commitinfo *ci = NULL; |
|
|
|
288 git_reference_iterator *it = NULL; |
|
|
|
289 const git_oid *id = NULL; |
|
|
|
290 git_object *obj = NULL; |
|
|
|
291 git_reference *dref = NULL, *r, *ref = NULL; |
|
|
|
292 size_t i, refcount; |
|
|
|
293 |
|
|
|
294 *pris = NULL; |
|
|
|
295 *prefcount = 0; |
|
|
|
296 |
|
|
|
297 if (git_reference_iterator_new(&it, repo)) |
|
|
|
298 return -1; |
|
|
|
299 |
|
|
|
300 for (refcount = 0; !git_reference_next(&ref, it); ) { |
|
|
|
301 if (!git_reference_is_branch(ref) && !git_reference_is_tag(ref)) { |
|
|
|
302 git_reference_free(ref); |
|
|
|
303 ref = NULL; |
|
|
|
304 continue; |
|
|
|
305 } |
|
|
|
306 |
|
|
|
307 switch (git_reference_type(ref)) { |
|
|
|
308 case GIT_REF_SYMBOLIC: |
|
|
|
309 if (git_reference_resolve(&dref, ref)) |
|
|
|
310 goto err; |
|
|
|
311 r = dref; |
|
|
|
312 break; |
|
|
|
313 case GIT_REF_OID: |
|
|
|
314 r = ref; |
|
|
|
315 break; |
|
|
|
316 default: |
|
|
|
317 continue; |
|
|
|
318 } |
|
|
|
319 if (!git_reference_target(r) || |
|
|
|
320 git_reference_peel(&obj, r, GIT_OBJ_ANY)) |
|
|
|
321 goto err; |
|
|
|
322 if (!(id = git_object_id(obj))) |
|
|
|
323 goto err; |
|
|
|
324 if (!(ci = commitinfo_getbyoid(id))) |
|
|
|
325 break; |
|
|
|
326 |
|
|
|
327 if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris)))) |
|
|
|
328 err(1, "realloc"); |
|
|
|
329 ris[refcount].ci = ci; |
|
|
|
330 ris[refcount].ref = r; |
|
|
|
331 refcount++; |
|
|
|
332 |
|
|
|
333 git_object_free(obj); |
|
|
|
334 obj = NULL; |
|
|
|
335 git_reference_free(dref); |
|
|
|
336 dref = NULL; |
|
|
|
337 } |
|
|
|
338 git_reference_iterator_free(it); |
|
|
|
339 |
|
|
|
340 /* sort by type, date then shorthand name */ |
|
|
|
341 qsort(ris, refcount, sizeof(*ris), refs_cmp); |
|
|
|
342 |
|
|
|
343 *pris = ris; |
|
|
|
344 *prefcount = refcount; |
|
|
|
345 |
|
|
|
346 return 0; |
|
|
|
347 |
|
|
|
348 err: |
|
|
|
349 git_object_free(obj); |
|
|
|
350 git_reference_free(dref); |
|
|
|
351 commitinfo_free(ci); |
|
|
|
352 for (i = 0; i < refcount; i++) { |
|
|
|
353 commitinfo_free(ris[i].ci); |
|
|
|
354 git_reference_free(ris[i].ref); |
|
|
|
355 } |
|
|
|
356 free(ris); |
|
|
|
357 |
|
|
|
358 return -1; |
|
|
|
359 } |
|
|
|
360 |
|
|
|
361 FILE * |
|
|
|
362 efopen(const char *filename, const char *flags) |
|
|
|
363 { |
|
|
|
364 FILE *fp; |
|
|
|
365 |
|
|
|
366 if (!(fp = fopen(filename, flags))) |
|
|
|
367 err(1, "fopen: '%s'", filename); |
|
|
|
368 |
|
|
|
369 return fp; |
|
|
|
370 } |
|
|
|
371 |
|
|
|
372 /* Percent-encode, see RFC3986 section 2.1. */ |
|
|
|
373 void |
|
|
|
374 percentencode(FILE *fp, const char *s, size_t len) |
|
|
|
375 { |
|
|
|
376 static char tab[] = "0123456789ABCDEF"; |
|
|
|
377 unsigned char uc; |
|
|
|
378 size_t i; |
|
|
|
379 |
|
|
|
380 for (i = 0; *s && i < len; s++, i++) { |
|
|
|
381 uc = *s; |
|
|
|
382 /* NOTE: do not encode '/' for paths or ",-." */ |
|
|
|
383 if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') || |
|
|
|
384 uc == '[' || uc == ']') { |
|
|
|
385 putc('%', fp); |
|
|
|
386 putc(tab[(uc >> 4) & 0x0f], fp); |
|
|
|
387 putc(tab[uc & 0x0f], fp); |
|
|
|
388 } else { |
|
|
|
389 putc(uc, fp); |
|
|
|
390 } |
|
|
|
391 } |
|
|
|
392 } |
|
|
|
393 |
|
|
|
394 /* Escape characters below as HTML 2.0 / XML 1.0. */ |
|
|
|
395 void |
|
|
|
396 xmlencode(FILE *fp, const char *s, size_t len) |
|
|
|
397 { |
|
|
|
398 size_t i; |
|
|
|
399 |
|
|
|
400 for (i = 0; *s && i < len; s++, i++) { |
|
|
|
401 switch(*s) { |
|
|
|
402 case '<': fputs("<", fp); break; |
|
|
|
403 case '>': fputs(">", fp); break; |
|
|
|
404 case '\'': fputs("'", fp); break; |
|
|
|
405 case '&': fputs("&", fp); break; |
|
|
|
406 case '"': fputs(""", fp); break; |
|
|
|
407 default: putc(*s, fp); |
|
|
|
408 } |
|
|
|
409 } |
|
|
|
410 } |
|
|
|
411 |
|
|
|
412 /* Escape characters below as HTML 2.0 / XML 1.0, ignore printing '\r', '\n' */ |
|
|
|
413 void |
|
|
|
414 xmlencodeline(FILE *fp, const char *s, size_t len) |
|
|
|
415 { |
|
|
|
416 size_t i; |
|
|
|
417 |
|
|
|
418 for (i = 0; *s && i < len; s++, i++) { |
|
|
|
419 switch(*s) { |
|
|
|
420 case '<': fputs("<", fp); break; |
|
|
|
421 case '>': fputs(">", fp); break; |
|
|
|
422 case '\'': fputs("'", fp); break; |
|
|
|
423 case '&': fputs("&", fp); break; |
|
|
|
424 case '"': fputs(""", fp); break; |
|
|
|
425 case '\r': break; /* ignore CR */ |
|
|
|
426 case '\n': break; /* ignore LF */ |
|
|
|
427 default: putc(*s, fp); |
|
|
|
428 } |
|
|
|
429 } |
|
|
|
430 } |
|
|
|
431 |
|
|
|
432 int |
|
|
|
433 mkdirp(const char *path) |
|
|
|
434 { |
|
|
|
435 char tmp[PATH_MAX], *p; |
|
|
|
436 |
|
|
|
437 if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp)) |
|
|
|
438 errx(1, "path truncated: '%s'", path); |
|
|
|
439 for (p = tmp + (tmp[0] == '/'); *p; p++) { |
|
|
|
440 if (*p != '/') |
|
|
|
441 continue; |
|
|
|
442 *p = '\0'; |
|
|
|
443 if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST) |
|
|
|
444 return -1; |
|
|
|
445 *p = '/'; |
|
|
|
446 } |
|
|
|
447 if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST) |
|
|
|
448 return -1; |
|
|
|
449 return 0; |
|
|
|
450 } |
|
|
|
451 |
|
|
|
452 void |
|
|
|
453 printtimez(FILE *fp, const git_time *intime) |
|
|
|
454 { |
|
|
|
455 struct tm *intm; |
|
|
|
456 time_t t; |
|
|
|
457 char out[32]; |
|
|
|
458 |
|
|
|
459 t = (time_t)intime->time; |
|
|
|
460 if (!(intm = gmtime(&t))) |
|
|
|
461 return; |
|
|
|
462 strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm); |
|
|
|
463 fputs(out, fp); |
|
|
|
464 } |
|
|
|
465 |
|
|
|
466 void |
|
|
|
467 printtime(FILE *fp, const git_time *intime) |
|
|
|
468 { |
|
|
|
469 struct tm *intm; |
|
|
|
470 time_t t; |
|
|
|
471 char out[32]; |
|
|
|
472 |
|
|
|
473 t = (time_t)intime->time + (intime->offset * 60); |
|
|
|
474 if (!(intm = gmtime(&t))) |
|
|
|
475 return; |
|
|
|
476 strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm); |
|
|
|
477 if (intime->offset < 0) |
|
|
|
478 fprintf(fp, "%s -%02d%02d", out, |
|
|
|
479 -(intime->offset) / 60, -(intime->offset) % 60); |
|
|
|
480 else |
|
|
|
481 fprintf(fp, "%s +%02d%02d", out, |
|
|
|
482 intime->offset / 60, intime->offset % 60); |
|
|
|
483 } |
|
|
|
484 |
|
|
|
485 void |
|
|
|
486 printtimeshort(FILE *fp, const git_time *intime) |
|
|
|
487 { |
|
|
|
488 struct tm *intm; |
|
|
|
489 time_t t; |
|
|
|
490 char out[32]; |
|
|
|
491 |
|
|
|
492 t = (time_t)intime->time; |
|
|
|
493 if (!(intm = gmtime(&t))) |
|
|
|
494 return; |
|
|
|
495 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm); |
|
|
|
496 fputs(out, fp); |
|
|
|
497 } |
|
|
|
498 |
|
|
|
499 void |
|
|
|
500 writeheader(FILE *fp, const char *title) |
|
|
|
501 { |
|
|
|
502 fputs("<!DOCTYPE html>\n" |
|
|
|
503 "<html>\n<head>\n" |
|
|
|
504 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" |
|
|
|
505 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n" |
|
|
|
506 "<title>", fp); |
|
|
|
507 xmlencode(fp, title, strlen(title)); |
|
|
|
508 if (title[0] && strippedname[0]) |
|
|
|
509 fputs(" - ", fp); |
|
|
|
510 xmlencode(fp, strippedname, strlen(strippedname)); |
|
|
|
511 if (description[0]) |
|
|
|
512 fputs(" - ", fp); |
|
|
|
513 xmlencode(fp, description, strlen(description)); |
|
|
|
514 fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath); |
|
|
|
515 fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp); |
|
|
|
516 xmlencode(fp, name, strlen(name)); |
|
|
|
517 fprintf(fp, " Atom Feed\" href=\"%satom.xml\" />\n", relpath); |
|
|
|
518 fputs("<link rel=\"alternate\" type=\"application/atom+xml\" title=\"", fp); |
|
|
|
519 xmlencode(fp, name, strlen(name)); |
|
|
|
520 fprintf(fp, " Atom Feed (tags)\" href=\"%stags.xml\" />\n", relpath); |
|
|
|
521 fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath); |
|
|
|
522 fputs("</head>\n<body>\n<table><tr><td>", fp); |
|
|
|
523 fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>", |
|
|
|
524 relpath, relpath); |
|
|
|
525 fputs("</td><td><h1>", fp); |
|
|
|
526 xmlencode(fp, strippedname, strlen(strippedname)); |
|
|
|
527 fputs("</h1><span class=\"desc\">", fp); |
|
|
|
528 xmlencode(fp, description, strlen(description)); |
|
|
|
529 fputs("</span></td></tr>", fp); |
|
|
|
530 if (cloneurl[0]) { |
|
|
|
531 fputs("<tr class=\"url\"><td></td><td>git clone <a href=\"", fp); |
|
|
|
532 xmlencode(fp, cloneurl, strlen(cloneurl)); /* not percent-encoded */ |
|
|
|
533 fputs("\">", fp); |
|
|
|
534 xmlencode(fp, cloneurl, strlen(cloneurl)); |
|
|
|
535 fputs("</a></td></tr>", fp); |
|
|
|
536 } |
|
|
|
537 fputs("<tr><td></td><td>\n", fp); |
|
|
|
538 fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath); |
|
|
|
539 fprintf(fp, "<a href=\"%sfiles.html\">Files</a> | ", relpath); |
|
|
|
540 fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath); |
|
|
|
541 if (submodules) |
|
|
|
542 fprintf(fp, " | <a href=\"%sfile/%s.html\">Submodules</a>", |
|
|
|
543 relpath, submodules); |
|
|
|
544 if (readme) |
|
|
|
545 fprintf(fp, " | <a href=\"%sfile/%s.html\">README</a>", |
|
|
|
546 relpath, readme); |
|
|
|
547 if (license) |
|
|
|
548 fprintf(fp, " | <a href=\"%sfile/%s.html\">LICENSE</a>", |
|
|
|
549 relpath, license); |
|
|
|
550 fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp); |
|
|
|
551 } |
|
|
|
552 |
|
|
|
553 void |
|
|
|
554 writefooter(FILE *fp) |
|
|
|
555 { |
|
|
|
556 fputs("</div>\n</body>\n</html>\n", fp); |
|
|
|
557 } |
|
|
|
558 |
|
|
|
559 size_t |
|
|
|
560 writeblobhtml(FILE *fp, const git_blob *blob) |
|
|
|
561 { |
|
|
|
562 size_t n = 0, i, len, prev; |
|
|
|
563 const char *nfmt = "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu</a> "; |
|
|
|
564 const char *s = git_blob_rawcontent(blob); |
|
|
|
565 |
|
|
|
566 len = git_blob_rawsize(blob); |
|
|
|
567 fputs("<pre id=\"blob\">\n", fp); |
|
|
|
568 |
|
|
|
569 if (len > 0) { |
|
|
|
570 for (i = 0, prev = 0; i < len; i++) { |
|
|
|
571 if (s[i] != '\n') |
|
|
|
572 continue; |
|
|
|
573 n++; |
|
|
|
574 fprintf(fp, nfmt, n, n, n); |
|
|
|
575 xmlencodeline(fp, &s[prev], i - prev + 1); |
|
|
|
576 putc('\n', fp); |
|
|
|
577 prev = i + 1; |
|
|
|
578 } |
|
|
|
579 /* trailing data */ |
|
|
|
580 if ((len - prev) > 0) { |
|
|
|
581 n++; |
|
|
|
582 fprintf(fp, nfmt, n, n, n); |
|
|
|
583 xmlencodeline(fp, &s[prev], len - prev); |
|
|
|
584 } |
|
|
|
585 } |
|
|
|
586 |
|
|
|
587 fputs("</pre>\n", fp); |
|
|
|
588 |
|
|
|
589 return n; |
|
|
|
590 } |
|
|
|
591 |
|
|
|
592 void |
|
|
|
593 printcommit(FILE *fp, struct commitinfo *ci) |
|
|
|
594 { |
|
|
|
595 fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n", |
|
|
|
596 relpath, ci->oid, ci->oid); |
|
|
|
597 |
|
|
|
598 if (ci->parentoid[0]) |
|
|
|
599 fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n", |
|
|
|
600 relpath, ci->parentoid, ci->parentoid); |
|
|
|
601 |
|
|
|
602 if (ci->author) { |
|
|
|
603 fputs("<b>Author:</b> ", fp); |
|
|
|
604 xmlencode(fp, ci->author->name, strlen(ci->author->name)); |
|
|
|
605 fputs(" <<a href=\"mailto:", fp); |
|
|
|
606 xmlencode(fp, ci->author->email, strlen(ci->author->email)); /* not percent-encoded */ |
|
|
|
607 fputs("\">", fp); |
|
|
|
608 xmlencode(fp, ci->author->email, strlen(ci->author->email)); |
|
|
|
609 fputs("</a>>\n<b>Date:</b> ", fp); |
|
|
|
610 printtime(fp, &(ci->author->when)); |
|
|
|
611 putc('\n', fp); |
|
|
|
612 } |
|
|
|
613 if (ci->msg) { |
|
|
|
614 putc('\n', fp); |
|
|
|
615 xmlencode(fp, ci->msg, strlen(ci->msg)); |
|
|
|
616 putc('\n', fp); |
|
|
|
617 } |
|
|
|
618 } |
|
|
|
619 |
|
|
|
620 void |
|
|
|
621 printshowfile(FILE *fp, struct commitinfo *ci) |
|
|
|
622 { |
|
|
|
623 const git_diff_delta *delta; |
|
|
|
624 const git_diff_hunk *hunk; |
|
|
|
625 const git_diff_line *line; |
|
|
|
626 git_patch *patch; |
|
|
|
627 size_t nhunks, nhunklines, changed, add, del, total, i, j, k; |
|
|
|
628 char linestr[80]; |
|
|
|
629 int c; |
|
|
|
630 |
|
|
|
631 printcommit(fp, ci); |
|
|
|
632 |
|
|
|
633 if (!ci->deltas) |
|
|
|
634 return; |
|
|
|
635 |
|
|
|
636 if (ci->filecount > 1000 || |
|
|
|
637 ci->ndeltas > 1000 || |
|
|
|
638 ci->addcount > 100000 || |
|
|
|
639 ci->delcount > 100000) { |
|
|
|
640 fputs("Diff is too large, output suppressed.\n", fp); |
|
|
|
641 return; |
|
|
|
642 } |
|
|
|
643 |
|
|
|
644 /* diff stat */ |
|
|
|
645 fputs("<b>Diffstat:</b>\n<table>", fp); |
|
|
|
646 for (i = 0; i < ci->ndeltas; i++) { |
|
|
|
647 delta = git_patch_get_delta(ci->deltas[i]->patch); |
|
|
|
648 |
|
|
|
649 switch (delta->status) { |
|
|
|
650 case GIT_DELTA_ADDED: c = 'A'; break; |
|
|
|
651 case GIT_DELTA_COPIED: c = 'C'; break; |
|
|
|
652 case GIT_DELTA_DELETED: c = 'D'; break; |
|
|
|
653 case GIT_DELTA_MODIFIED: c = 'M'; break; |
|
|
|
654 case GIT_DELTA_RENAMED: c = 'R'; break; |
|
|
|
655 case GIT_DELTA_TYPECHANGE: c = 'T'; break; |
|
|
|
656 default: c = ' '; break; |
|
|
|
657 } |
|
|
|
658 if (c == ' ') |
|
|
|
659 fprintf(fp, "<tr><td>%c", c); |
|
|
|
660 else |
|
|
|
661 fprintf(fp, "<tr><td class=\"%c\">%c", c, c); |
|
|
|
662 |
|
|
|
663 fprintf(fp, "</td><td><a href=\"#h%zu\">", i); |
|
|
|
664 xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); |
|
|
|
665 if (strcmp(delta->old_file.path, delta->new_file.path)) { |
|
|
|
666 fputs(" -> ", fp); |
|
|
|
667 xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path)); |
|
|
|
668 } |
|
|
|
669 |
|
|
|
670 add = ci->deltas[i]->addcount; |
|
|
|
671 del = ci->deltas[i]->delcount; |
|
|
|
672 changed = add + del; |
|
|
|
673 total = sizeof(linestr) - 2; |
|
|
|
674 if (changed > total) { |
|
|
|
675 if (add) |
|
|
|
676 add = ((float)total / changed * add) + 1; |
|
|
|
677 if (del) |
|
|
|
678 del = ((float)total / changed * del) + 1; |
|
|
|
679 } |
|
|
|
680 memset(&linestr, '+', add); |
|
|
|
681 memset(&linestr[add], '-', del); |
|
|
|
682 |
|
|
|
683 fprintf(fp, "</a></td><td> | </td><td class=\"num\">%zu</td><td><span class=\"i\">", |
|
|
|
684 ci->deltas[i]->addcount + ci->deltas[i]->delcount); |
|
|
|
685 fwrite(&linestr, 1, add, fp); |
|
|
|
686 fputs("</span><span class=\"d\">", fp); |
|
|
|
687 fwrite(&linestr[add], 1, del, fp); |
|
|
|
688 fputs("</span></td></tr>\n", fp); |
|
|
|
689 } |
|
|
|
690 fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n", |
|
|
|
691 ci->filecount, ci->filecount == 1 ? "" : "s", |
|
|
|
692 ci->addcount, ci->addcount == 1 ? "" : "s", |
|
|
|
693 ci->delcount, ci->delcount == 1 ? "" : "s"); |
|
|
|
694 |
|
|
|
695 fputs("<hr/>", fp); |
|
|
|
696 |
|
|
|
697 for (i = 0; i < ci->ndeltas; i++) { |
|
|
|
698 patch = ci->deltas[i]->patch; |
|
|
|
699 delta = git_patch_get_delta(patch); |
|
|
|
700 fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath); |
|
|
|
701 percentencode(fp, delta->old_file.path, strlen(delta->old_file.path)); |
|
|
|
702 fputs(".html\">", fp); |
|
|
|
703 xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); |
|
|
|
704 fprintf(fp, "</a> b/<a href=\"%sfile/", relpath); |
|
|
|
705 percentencode(fp, delta->new_file.path, strlen(delta->new_file.path)); |
|
|
|
706 fprintf(fp, ".html\">"); |
|
|
|
707 xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path)); |
|
|
|
708 fprintf(fp, "</a></b>\n"); |
|
|
|
709 |
|
|
|
710 /* check binary data */ |
|
|
|
711 if (delta->flags & GIT_DIFF_FLAG_BINARY) { |
|
|
|
712 fputs("Binary files differ.\n", fp); |
|
|
|
713 continue; |
|
|
|
714 } |
|
|
|
715 |
|
|
|
716 nhunks = git_patch_num_hunks(patch); |
|
|
|
717 for (j = 0; j < nhunks; j++) { |
|
|
|
718 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j)) |
|
|
|
719 break; |
|
|
|
720 |
|
|
|
721 fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j); |
|
|
|
722 xmlencode(fp, hunk->header, hunk->header_len); |
|
|
|
723 fputs("</a>", fp); |
|
|
|
724 |
|
|
|
725 for (k = 0; ; k++) { |
|
|
|
726 if (git_patch_get_line_in_hunk(&line, patch, j, k)) |
|
|
|
727 break; |
|
|
|
728 if (line->old_lineno == -1) |
|
|
|
729 fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+", |
|
|
|
730 i, j, k, i, j, k); |
|
|
|
731 else if (line->new_lineno == -1) |
|
|
|
732 fprintf(fp, "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-", |
|
|
|
733 i, j, k, i, j, k); |
|
|
|
734 else |
|
|
|
735 putc(' ', fp); |
|
|
|
736 xmlencodeline(fp, line->content, line->content_len); |
|
|
|
737 putc('\n', fp); |
|
|
|
738 if (line->old_lineno == -1 || line->new_lineno == -1) |
|
|
|
739 fputs("</a>", fp); |
|
|
|
740 } |
|
|
|
741 } |
|
|
|
742 } |
|
|
|
743 } |
|
|
|
744 |
|
|
|
745 void |
|
|
|
746 writelogline(FILE *fp, struct commitinfo *ci) |
|
|
|
747 { |
|
|
|
748 fputs("<tr><td>", fp); |
|
|
|
749 if (ci->author) |
|
|
|
750 printtimeshort(fp, &(ci->author->when)); |
|
|
|
751 fputs("</td><td>", fp); |
|
|
|
752 if (ci->summary) { |
|
|
|
753 fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid); |
|
|
|
754 xmlencode(fp, ci->summary, strlen(ci->summary)); |
|
|
|
755 fputs("</a>", fp); |
|
|
|
756 } |
|
|
|
757 fputs("</td><td>", fp); |
|
|
|
758 if (ci->author) |
|
|
|
759 xmlencode(fp, ci->author->name, strlen(ci->author->name)); |
|
|
|
760 fputs("</td><td class=\"num\" align=\"right\">", fp); |
|
|
|
761 fprintf(fp, "%zu", ci->filecount); |
|
|
|
762 fputs("</td><td class=\"num\" align=\"right\">", fp); |
|
|
|
763 fprintf(fp, "+%zu", ci->addcount); |
|
|
|
764 fputs("</td><td class=\"num\" align=\"right\">", fp); |
|
|
|
765 fprintf(fp, "-%zu", ci->delcount); |
|
|
|
766 fputs("</td></tr>\n", fp); |
|
|
|
767 } |
|
|
|
768 |
|
|
|
769 int |
|
|
|
770 writelog(FILE *fp, const git_oid *oid) |
|
|
|
771 { |
|
|
|
772 struct commitinfo *ci; |
|
|
|
773 git_revwalk *w = NULL; |
|
|
|
774 git_oid id; |
|
|
|
775 char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1]; |
|
|
|
776 FILE *fpfile; |
|
|
|
777 size_t remcommits = 0; |
|
|
|
778 int r; |
|
|
|
779 |
|
|
|
780 git_revwalk_new(&w, repo); |
|
|
|
781 git_revwalk_push(w, oid); |
|
|
|
782 |
|
|
|
783 while (!git_revwalk_next(&id, w)) { |
|
|
|
784 relpath = ""; |
|
|
|
785 |
|
|
|
786 if (cachefile && !memcmp(&id, &lastoid, sizeof(id))) |
|
|
|
787 break; |
|
|
|
788 |
|
|
|
789 git_oid_tostr(oidstr, sizeof(oidstr), &id); |
|
|
|
790 r = snprintf(path, sizeof(path), "commit/%s.html", oidstr); |
|
|
|
791 if (r < 0 || (size_t)r >= sizeof(path)) |
|
|
|
792 errx(1, "path truncated: 'commit/%s.html'", oidstr); |
|
|
|
793 r = access(path, F_OK); |
|
|
|
794 |
|
|
|
795 /* optimization: if there are no log lines to write and |
|
|
|
796 the commit file already exists: skip the diffstat */ |
|
|
|
797 if (!nlogcommits) { |
|
|
|
798 remcommits++; |
|
|
|
799 if (!r) |
|
|
|
800 continue; |
|
|
|
801 } |
|
|
|
802 |
|
|
|
803 if (!(ci = commitinfo_getbyoid(&id))) |
|
|
|
804 break; |
|
|
|
805 /* diffstat: for stagit HTML required for the log.html line */ |
|
|
|
806 if (commitinfo_getstats(ci) == -1) |
|
|
|
807 goto err; |
|
|
|
808 |
|
|
|
809 if (nlogcommits != 0) { |
|
|
|
810 writelogline(fp, ci); |
|
|
|
811 if (nlogcommits > 0) |
|
|
|
812 nlogcommits--; |
|
|
|
813 } |
|
|
|
814 |
|
|
|
815 if (cachefile) |
|
|
|
816 writelogline(wcachefp, ci); |
|
|
|
817 |
|
|
|
818 /* check if file exists if so skip it */ |
|
|
|
819 if (r) { |
|
|
|
820 relpath = "../"; |
|
|
|
821 fpfile = efopen(path, "w"); |
|
|
|
822 writeheader(fpfile, ci->summary); |
|
|
|
823 fputs("<pre>", fpfile); |
|
|
|
824 printshowfile(fpfile, ci); |
|
|
|
825 fputs("</pre>\n", fpfile); |
|
|
|
826 writefooter(fpfile); |
|
|
|
827 checkfileerror(fpfile, path, 'w'); |
|
|
|
828 fclose(fpfile); |
|
|
|
829 } |
|
|
|
830 err: |
|
|
|
831 commitinfo_free(ci); |
|
|
|
832 } |
|
|
|
833 git_revwalk_free(w); |
|
|
|
834 |
|
|
|
835 if (nlogcommits == 0 && remcommits != 0) { |
|
|
|
836 fprintf(fp, "<tr><td></td><td colspan=\"5\">" |
|
|
|
837 "%zu more commits remaining, fetch the repository" |
|
|
|
838 "</td></tr>\n", remcommits); |
|
|
|
839 } |
|
|
|
840 |
|
|
|
841 relpath = ""; |
|
|
|
842 |
|
|
|
843 return 0; |
|
|
|
844 } |
|
|
|
845 |
|
|
|
846 void |
|
|
|
847 printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag) |
|
|
|
848 { |
|
|
|
849 fputs("<entry>\n", fp); |
|
|
|
850 |
|
|
|
851 fprintf(fp, "<id>%s</id>\n", ci->oid); |
|
|
|
852 if (ci->author) { |
|
|
|
853 fputs("<published>", fp); |
|
|
|
854 printtimez(fp, &(ci->author->when)); |
|
|
|
855 fputs("</published>\n", fp); |
|
|
|
856 } |
|
|
|
857 if (ci->committer) { |
|
|
|
858 fputs("<updated>", fp); |
|
|
|
859 printtimez(fp, &(ci->committer->when)); |
|
|
|
860 fputs("</updated>\n", fp); |
|
|
|
861 } |
|
|
|
862 if (ci->summary) { |
|
|
|
863 fputs("<title>", fp); |
|
|
|
864 if (tag && tag[0]) { |
|
|
|
865 fputs("[", fp); |
|
|
|
866 xmlencode(fp, tag, strlen(tag)); |
|
|
|
867 fputs("] ", fp); |
|
|
|
868 } |
|
|
|
869 xmlencode(fp, ci->summary, strlen(ci->summary)); |
|
|
|
870 fputs("</title>\n", fp); |
|
|
|
871 } |
|
|
|
872 fprintf(fp, "<link rel=\"alternate\" type=\"text/html\" href=\"%scommit/%s.html\" />\n", |
|
|
|
873 baseurl, ci->oid); |
|
|
|
874 |
|
|
|
875 if (ci->author) { |
|
|
|
876 fputs("<author>\n<name>", fp); |
|
|
|
877 xmlencode(fp, ci->author->name, strlen(ci->author->name)); |
|
|
|
878 fputs("</name>\n<email>", fp); |
|
|
|
879 xmlencode(fp, ci->author->email, strlen(ci->author->email)); |
|
|
|
880 fputs("</email>\n</author>\n", fp); |
|
|
|
881 } |
|
|
|
882 |
|
|
|
883 fputs("<content>", fp); |
|
|
|
884 fprintf(fp, "commit %s\n", ci->oid); |
|
|
|
885 if (ci->parentoid[0]) |
|
|
|
886 fprintf(fp, "parent %s\n", ci->parentoid); |
|
|
|
887 if (ci->author) { |
|
|
|
888 fputs("Author: ", fp); |
|
|
|
889 xmlencode(fp, ci->author->name, strlen(ci->author->name)); |
|
|
|
890 fputs(" <", fp); |
|
|
|
891 xmlencode(fp, ci->author->email, strlen(ci->author->email)); |
|
|
|
892 fputs(">\nDate: ", fp); |
|
|
|
893 printtime(fp, &(ci->author->when)); |
|
|
|
894 putc('\n', fp); |
|
|
|
895 } |
|
|
|
896 if (ci->msg) { |
|
|
|
897 putc('\n', fp); |
|
|
|
898 xmlencode(fp, ci->msg, strlen(ci->msg)); |
|
|
|
899 } |
|
|
|
900 fputs("\n</content>\n</entry>\n", fp); |
|
|
|
901 } |
|
|
|
902 |
|
|
|
903 int |
|
|
|
904 writeatom(FILE *fp, int all) |
|
|
|
905 { |
|
|
|
906 struct referenceinfo *ris = NULL; |
|
|
|
907 size_t refcount = 0; |
|
|
|
908 struct commitinfo *ci; |
|
|
|
909 git_revwalk *w = NULL; |
|
|
|
910 git_oid id; |
|
|
|
911 size_t i, m = 100; /* last 'm' commits */ |
|
|
|
912 |
|
|
|
913 fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
|
|
|
914 "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp); |
|
|
|
915 xmlencode(fp, strippedname, strlen(strippedname)); |
|
|
|
916 fputs(", branch HEAD</title>\n<subtitle>", fp); |
|
|
|
917 xmlencode(fp, description, strlen(description)); |
|
|
|
918 fputs("</subtitle>\n", fp); |
|
|
|
919 |
|
|
|
920 /* all commits or only tags? */ |
|
|
|
921 if (all) { |
|
|
|
922 git_revwalk_new(&w, repo); |
|
|
|
923 git_revwalk_push_head(w); |
|
|
|
924 for (i = 0; i < m && !git_revwalk_next(&id, w); i++) { |
|
|
|
925 if (!(ci = commitinfo_getbyoid(&id))) |
|
|
|
926 break; |
|
|
|
927 printcommitatom(fp, ci, ""); |
|
|
|
928 commitinfo_free(ci); |
|
|
|
929 } |
|
|
|
930 git_revwalk_free(w); |
|
|
|
931 } else if (getrefs(&ris, &refcount) != -1) { |
|
|
|
932 /* references: tags */ |
|
|
|
933 for (i = 0; i < refcount; i++) { |
|
|
|
934 if (git_reference_is_tag(ris[i].ref)) |
|
|
|
935 printcommitatom(fp, ris[i].ci, |
|
|
|
936 git_reference_shorthand(ris[i].ref)); |
|
|
|
937 |
|
|
|
938 commitinfo_free(ris[i].ci); |
|
|
|
939 git_reference_free(ris[i].ref); |
|
|
|
940 } |
|
|
|
941 free(ris); |
|
|
|
942 } |
|
|
|
943 |
|
|
|
944 fputs("</feed>\n", fp); |
|
|
|
945 |
|
|
|
946 return 0; |
|
|
|
947 } |
|
|
|
948 |
|
|
|
949 size_t |
|
|
|
950 writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize) |
|
|
|
951 { |
|
|
|
952 char tmp[PATH_MAX] = "", *d; |
|
|
|
953 const char *p; |
|
|
|
954 size_t lc = 0; |
|
|
|
955 FILE *fp; |
|
|
|
956 |
|
|
|
957 if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp)) |
|
|
|
958 errx(1, "path truncated: '%s'", fpath); |
|
|
|
959 if (!(d = dirname(tmp))) |
|
|
|
960 err(1, "dirname"); |
|
|
|
961 if (mkdirp(d)) |
|
|
|
962 return -1; |
|
|
|
963 |
|
|
|
964 for (p = fpath, tmp[0] = '\0'; *p; p++) { |
|
|
|
965 if (*p == '/' && strlcat(tmp, "../", sizeof(tmp)) >= sizeof(tmp)) |
|
|
|
966 errx(1, "path truncated: '../%s'", tmp); |
|
|
|
967 } |
|
|
|
968 relpath = tmp; |
|
|
|
969 |
|
|
|
970 fp = efopen(fpath, "w"); |
|
|
|
971 writeheader(fp, filename); |
|
|
|
972 fputs("<p> ", fp); |
|
|
|
973 xmlencode(fp, filename, strlen(filename)); |
|
|
|
974 fprintf(fp, " (%zuB)", filesize); |
|
|
|
975 fputs("</p><hr/>", fp); |
|
|
|
976 |
|
|
|
977 if (git_blob_is_binary((git_blob *)obj)) |
|
|
|
978 fputs("<p>Binary file.</p>\n", fp); |
|
|
|
979 else |
|
|
|
980 lc = writeblobhtml(fp, (git_blob *)obj); |
|
|
|
981 |
|
|
|
982 writefooter(fp); |
|
|
|
983 checkfileerror(fp, fpath, 'w'); |
|
|
|
984 fclose(fp); |
|
|
|
985 |
|
|
|
986 relpath = ""; |
|
|
|
987 |
|
|
|
988 return lc; |
|
|
|
989 } |
|
|
|
990 |
|
|
|
991 const char * |
|
|
|
992 filemode(git_filemode_t m) |
|
|
|
993 { |
|
|
|
994 static char mode[11]; |
|
|
|
995 |
|
|
|
996 memset(mode, '-', sizeof(mode) - 1); |
|
|
|
997 mode[10] = '\0'; |
|
|
|
998 |
|
|
|
999 if (S_ISREG(m)) |
|
|
|
1000 mode[0] = '-'; |
|
|
|
1001 else if (S_ISBLK(m)) |
|
|
|
1002 mode[0] = 'b'; |
|
|
|
1003 else if (S_ISCHR(m)) |
|
|
|
1004 mode[0] = 'c'; |
|
|
|
1005 else if (S_ISDIR(m)) |
|
|
|
1006 mode[0] = 'd'; |
|
|
|
1007 else if (S_ISFIFO(m)) |
|
|
|
1008 mode[0] = 'p'; |
|
|
|
1009 else if (S_ISLNK(m)) |
|
|
|
1010 mode[0] = 'l'; |
|
|
|
1011 else if (S_ISSOCK(m)) |
|
|
|
1012 mode[0] = 's'; |
|
|
|
1013 else |
|
|
|
1014 mode[0] = '?'; |
|
|
|
1015 |
|
|
|
1016 if (m & S_IRUSR) mode[1] = 'r'; |
|
|
|
1017 if (m & S_IWUSR) mode[2] = 'w'; |
|
|
|
1018 if (m & S_IXUSR) mode[3] = 'x'; |
|
|
|
1019 if (m & S_IRGRP) mode[4] = 'r'; |
|
|
|
1020 if (m & S_IWGRP) mode[5] = 'w'; |
|
|
|
1021 if (m & S_IXGRP) mode[6] = 'x'; |
|
|
|
1022 if (m & S_IROTH) mode[7] = 'r'; |
|
|
|
1023 if (m & S_IWOTH) mode[8] = 'w'; |
|
|
|
1024 if (m & S_IXOTH) mode[9] = 'x'; |
|
|
|
1025 |
|
|
|
1026 if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S'; |
|
|
|
1027 if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S'; |
|
|
|
1028 if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T'; |
|
|
|
1029 |
|
|
|
1030 return mode; |
|
|
|
1031 } |
|
|
|
1032 |
|
|
|
1033 int |
|
|
|
1034 writefilestree(FILE *fp, git_tree *tree, const char *path) |
|
|
|
1035 { |
|
|
|
1036 const git_tree_entry *entry = NULL; |
|
|
|
1037 git_object *obj = NULL; |
|
|
|
1038 const char *entryname; |
|
|
|
1039 char filepath[PATH_MAX], entrypath[PATH_MAX], oid[8]; |
|
|
|
1040 size_t count, i, lc, filesize; |
|
|
|
1041 int r, ret; |
|
|
|
1042 |
|
|
|
1043 count = git_tree_entrycount(tree); |
|
|
|
1044 for (i = 0; i < count; i++) { |
|
|
|
1045 if (!(entry = git_tree_entry_byindex(tree, i)) || |
|
|
|
1046 !(entryname = git_tree_entry_name(entry))) |
|
|
|
1047 return -1; |
|
|
|
1048 joinpath(entrypath, sizeof(entrypath), path, entryname); |
|
|
|
1049 |
|
|
|
1050 r = snprintf(filepath, sizeof(filepath), "file/%s.html", |
|
|
|
1051 entrypath); |
|
|
|
1052 if (r < 0 || (size_t)r >= sizeof(filepath)) |
|
|
|
1053 errx(1, "path truncated: 'file/%s.html'", entrypath); |
|
|
|
1054 |
|
|
|
1055 if (!git_tree_entry_to_object(&obj, repo, entry)) { |
|
|
|
1056 switch (git_object_type(obj)) { |
|
|
|
1057 case GIT_OBJ_BLOB: |
|
|
|
1058 break; |
|
|
|
1059 case GIT_OBJ_TREE: |
|
|
|
1060 /* NOTE: recurses */ |
|
|
|
1061 ret = writefilestree(fp, (git_tree *)obj, |
|
|
|
1062 entrypath); |
|
|
|
1063 git_object_free(obj); |
|
|
|
1064 if (ret) |
|
|
|
1065 return ret; |
|
|
|
1066 continue; |
|
|
|
1067 default: |
|
|
|
1068 git_object_free(obj); |
|
|
|
1069 continue; |
|
|
|
1070 } |
|
|
|
1071 |
|
|
|
1072 filesize = git_blob_rawsize((git_blob *)obj); |
|
|
|
1073 lc = writeblob(obj, filepath, entryname, filesize); |
|
|
|
1074 |
|
|
|
1075 fputs("<tr><td>", fp); |
|
|
|
1076 fputs(filemode(git_tree_entry_filemode(entry)), fp); |
|
|
|
1077 fprintf(fp, "</td><td><a href=\"%s", relpath); |
|
|
|
1078 percentencode(fp, filepath, strlen(filepath)); |
|
|
|
1079 fputs("\">", fp); |
|
|
|
1080 xmlencode(fp, entrypath, strlen(entrypath)); |
|
|
|
1081 fputs("</a></td><td class=\"num\" align=\"right\">", fp); |
|
|
|
1082 if (lc > 0) |
|
|
|
1083 fprintf(fp, "%zuL", lc); |
|
|
|
1084 else |
|
|
|
1085 fprintf(fp, "%zuB", filesize); |
|
|
|
1086 fputs("</td></tr>\n", fp); |
|
|
|
1087 git_object_free(obj); |
|
|
|
1088 } else if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) { |
|
|
|
1089 /* commit object in tree is a submodule */ |
|
|
|
1090 fprintf(fp, "<tr><td>m---------</td><td><a href=\"%sfile/.gitmodules.html\">", |
|
|
|
1091 relpath); |
|
|
|
1092 xmlencode(fp, entrypath, strlen(entrypath)); |
|
|
|
1093 fputs("</a> @ ", fp); |
|
|
|
1094 git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry)); |
|
|
|
1095 xmlencode(fp, oid, strlen(oid)); |
|
|
|
1096 fputs("</td><td class=\"num\" align=\"right\"></td></tr>\n", fp); |
|
|
|
1097 } |
|
|
|
1098 } |
|
|
|
1099 |
|
|
|
1100 return 0; |
|
|
|
1101 } |
|
|
|
1102 |
|
|
|
1103 int |
|
|
|
1104 writefiles(FILE *fp, const git_oid *id) |
|
|
|
1105 { |
|
|
|
1106 git_tree *tree = NULL; |
|
|
|
1107 git_commit *commit = NULL; |
|
|
|
1108 int ret = -1; |
|
|
|
1109 |
|
|
|
1110 fputs("<table id=\"files\"><thead>\n<tr>" |
|
|
|
1111 "<td><b>Mode</b></td><td><b>Name</b></td>" |
|
|
|
1112 "<td class=\"num\" align=\"right\"><b>Size</b></td>" |
|
|
|
1113 "</tr>\n</thead><tbody>\n", fp); |
|
|
|
1114 |
|
|
|
1115 if (!git_commit_lookup(&commit, repo, id) && |
|
|
|
1116 !git_commit_tree(&tree, commit)) |
|
|
|
1117 ret = writefilestree(fp, tree, ""); |
|
|
|
1118 |
|
|
|
1119 fputs("</tbody></table>", fp); |
|
|
|
1120 |
|
|
|
1121 git_commit_free(commit); |
|
|
|
1122 git_tree_free(tree); |
|
|
|
1123 |
|
|
|
1124 return ret; |
|
|
|
1125 } |
|
|
|
1126 |
|
|
|
1127 int |
|
|
|
1128 writerefs(FILE *fp) |
|
|
|
1129 { |
|
|
|
1130 struct referenceinfo *ris = NULL; |
|
|
|
1131 struct commitinfo *ci; |
|
|
|
1132 size_t count, i, j, refcount; |
|
|
|
1133 const char *titles[] = { "Branches", "Tags" }; |
|
|
|
1134 const char *ids[] = { "branches", "tags" }; |
|
|
|
1135 const char *s; |
|
|
|
1136 |
|
|
|
1137 if (getrefs(&ris, &refcount) == -1) |
|
|
|
1138 return -1; |
|
|
|
1139 |
|
|
|
1140 for (i = 0, j = 0, count = 0; i < refcount; i++) { |
|
|
|
1141 if (j == 0 && git_reference_is_tag(ris[i].ref)) { |
|
|
|
1142 if (count) |
|
|
|
1143 fputs("</tbody></table><br/>\n", fp); |
|
|
|
1144 count = 0; |
|
|
|
1145 j = 1; |
|
|
|
1146 } |
|
|
|
1147 |
|
|
|
1148 /* print header if it has an entry (first). */ |
|
|
|
1149 if (++count == 1) { |
|
|
|
1150 fprintf(fp, "<h2>%s</h2><table id=\"%s\">" |
|
|
|
1151 "<thead>\n<tr><td><b>Name</b></td>" |
|
|
|
1152 "<td><b>Last commit date</b></td>" |
|
|
|
1153 "<td><b>Author</b></td>\n</tr>\n" |
|
|
|
1154 "</thead><tbody>\n", |
|
|
|
1155 titles[j], ids[j]); |
|
|
|
1156 } |
|
|
|
1157 |
|
|
|
1158 ci = ris[i].ci; |
|
|
|
1159 s = git_reference_shorthand(ris[i].ref); |
|
|
|
1160 |
|
|
|
1161 fputs("<tr><td>", fp); |
|
|
|
1162 xmlencode(fp, s, strlen(s)); |
|
|
|
1163 fputs("</td><td>", fp); |
|
|
|
1164 if (ci->author) |
|
|
|
1165 printtimeshort(fp, &(ci->author->when)); |
|
|
|
1166 fputs("</td><td>", fp); |
|
|
|
1167 if (ci->author) |
|
|
|
1168 xmlencode(fp, ci->author->name, strlen(ci->author->name)); |
|
|
|
1169 fputs("</td></tr>\n", fp); |
|
|
|
1170 } |
|
|
|
1171 /* table footer */ |
|
|
|
1172 if (count) |
|
|
|
1173 fputs("</tbody></table><br/>\n", fp); |
|
|
|
1174 |
|
|
|
1175 for (i = 0; i < refcount; i++) { |
|
|
|
1176 commitinfo_free(ris[i].ci); |
|
|
|
1177 git_reference_free(ris[i].ref); |
|
|
|
1178 } |
|
|
|
1179 free(ris); |
|
|
|
1180 |
|
|
|
1181 return 0; |
|
|
|
1182 } |
|
|
|
1183 |
|
|
|
1184 void |
|
|
|
1185 usage(char *argv0) |
|
|
|
1186 { |
|
|
|
1187 fprintf(stderr, "usage: %s [-c cachefile | -l commits] " |
|
|
|
1188 "[-u baseurl] repodir\n", argv0); |
|
|
|
1189 exit(1); |
|
|
|
1190 } |
|
|
|
1191 |
|
|
|
1192 int |
|
|
|
1193 main(int argc, char *argv[]) |
|
|
|
1194 { |
|
|
|
1195 git_object *obj = NULL; |
|
|
|
1196 const git_oid *head = NULL; |
|
|
|
1197 mode_t mask; |
|
|
|
1198 FILE *fp, *fpread; |
|
|
|
1199 char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p; |
|
|
|
1200 char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ]; |
|
|
|
1201 size_t n; |
|
|
|
1202 int i, fd; |
|
|
|
1203 |
|
|
|
1204 for (i = 1; i < argc; i++) { |
|
|
|
1205 if (argv[i][0] != '-') { |
|
|
|
1206 if (repodir) |
|
|
|
1207 usage(argv[0]); |
|
|
|
1208 repodir = argv[i]; |
|
|
|
1209 } else if (argv[i][1] == 'c') { |
|
|
|
1210 if (nlogcommits > 0 || i + 1 >= argc) |
|
|
|
1211 usage(argv[0]); |
|
|
|
1212 cachefile = argv[++i]; |
|
|
|
1213 } else if (argv[i][1] == 'l') { |
|
|
|
1214 if (cachefile || i + 1 >= argc) |
|
|
|
1215 usage(argv[0]); |
|
|
|
1216 errno = 0; |
|
|
|
1217 nlogcommits = strtoll(argv[++i], &p, 10); |
|
|
|
1218 if (argv[i][0] == '\0' || *p != '\0' || |
|
|
|
1219 nlogcommits <= 0 || errno) |
|
|
|
1220 usage(argv[0]); |
|
|
|
1221 } else if (argv[i][1] == 'u') { |
|
|
|
1222 if (i + 1 >= argc) |
|
|
|
1223 usage(argv[0]); |
|
|
|
1224 baseurl = argv[++i]; |
|
|
|
1225 } |
|
|
|
1226 } |
|
|
|
1227 if (!repodir) |
|
|
|
1228 usage(argv[0]); |
|
|
|
1229 |
|
|
|
1230 if (!realpath(repodir, repodirabs)) |
|
|
|
1231 err(1, "realpath"); |
|
|
|
1232 |
|
|
|
1233 /* do not search outside the git repository: |
|
|
|
1234 GIT_CONFIG_LEVEL_APP is the highest level currently */ |
|
|
|
1235 git_libgit2_init(); |
|
|
|
1236 for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++) |
|
|
|
1237 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, ""); |
|
|
|
1238 /* do not require the git repository to be owned by the current user */ |
|
|
|
1239 git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0); |
|
|
|
1240 |
|
|
|
1241 #ifdef __OpenBSD__ |
|
|
|
1242 if (unveil(repodir, "r") == -1) |
|
|
|
1243 err(1, "unveil: %s", repodir); |
|
|
|
1244 if (unveil(".", "rwc") == -1) |
|
|
|
1245 err(1, "unveil: ."); |
|
|
|
1246 if (cachefile && unveil(cachefile, "rwc") == -1) |
|
|
|
1247 err(1, "unveil: %s", cachefile); |
|
|
|
1248 |
|
|
|
1249 if (cachefile) { |
|
|
|
1250 if (pledge("stdio rpath wpath cpath fattr", NULL) == -1) |
|
|
|
1251 err(1, "pledge"); |
|
|
|
1252 } else { |
|
|
|
1253 if (pledge("stdio rpath wpath cpath", NULL) == -1) |
|
|
|
1254 err(1, "pledge"); |
|
|
|
1255 } |
|
|
|
1256 #endif |
|
|
|
1257 |
|
|
|
1258 if (git_repository_open_ext(&repo, repodir, |
|
|
|
1259 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) { |
|
|
|
1260 fprintf(stderr, "%s: cannot open repository\n", argv[0]); |
|
|
|
1261 return 1; |
|
|
|
1262 } |
|
|
|
1263 |
|
|
|
1264 /* find HEAD */ |
|
|
|
1265 if (!git_revparse_single(&obj, repo, "HEAD")) |
|
|
|
1266 head = git_object_id(obj); |
|
|
|
1267 git_object_free(obj); |
|
|
|
1268 |
|
|
|
1269 /* use directory name as name */ |
|
|
|
1270 if ((name = strrchr(repodirabs, '/'))) |
|
|
|
1271 name++; |
|
|
|
1272 else |
|
|
|
1273 name = ""; |
|
|
|
1274 |
|
|
|
1275 /* strip .git suffix */ |
|
|
|
1276 if (!(strippedname = strdup(name))) |
|
|
|
1277 err(1, "strdup"); |
|
|
|
1278 if ((p = strrchr(strippedname, '.'))) |
|
|
|
1279 if (!strcmp(p, ".git")) |
|
|
|
1280 *p = '\0'; |
|
|
|
1281 |
|
|
|
1282 /* read description or .git/description */ |
|
|
|
1283 joinpath(path, sizeof(path), repodir, "description"); |
|
|
|
1284 if (!(fpread = fopen(path, "r"))) { |
|
|
|
1285 joinpath(path, sizeof(path), repodir, ".git/description"); |
|
|
|
1286 fpread = fopen(path, "r"); |
|
|
|
1287 } |
|
|
|
1288 if (fpread) { |
|
|
|
1289 if (!fgets(description, sizeof(description), fpread)) |
|
|
|
1290 description[0] = '\0'; |
|
|
|
1291 checkfileerror(fpread, path, 'r'); |
|
|
|
1292 fclose(fpread); |
|
|
|
1293 } |
|
|
|
1294 |
|
|
|
1295 /* read url or .git/url */ |
|
|
|
1296 joinpath(path, sizeof(path), repodir, "url"); |
|
|
|
1297 if (!(fpread = fopen(path, "r"))) { |
|
|
|
1298 joinpath(path, sizeof(path), repodir, ".git/url"); |
|
|
|
1299 fpread = fopen(path, "r"); |
|
|
|
1300 } |
|
|
|
1301 if (fpread) { |
|
|
|
1302 if (!fgets(cloneurl, sizeof(cloneurl), fpread)) |
|
|
|
1303 cloneurl[0] = '\0'; |
|
|
|
1304 checkfileerror(fpread, path, 'r'); |
|
|
|
1305 fclose(fpread); |
|
|
|
1306 cloneurl[strcspn(cloneurl, "\n")] = '\0'; |
|
|
|
1307 } |
|
|
|
1308 |
|
|
|
1309 /* check LICENSE */ |
|
|
|
1310 for (i = 0; i < LEN(licensefiles) && !license; i++) { |
|
|
|
1311 if (!git_revparse_single(&obj, repo, licensefiles[i]) && |
|
|
|
1312 git_object_type(obj) == GIT_OBJ_BLOB) |
|
|
|
1313 license = licensefiles[i] + strlen("HEAD:"); |
|
|
|
1314 git_object_free(obj); |
|
|
|
1315 } |
|
|
|
1316 |
|
|
|
1317 /* check README */ |
|
|
|
1318 for (i = 0; i < LEN(readmefiles) && !readme; i++) { |
|
|
|
1319 if (!git_revparse_single(&obj, repo, readmefiles[i]) && |
|
|
|
1320 git_object_type(obj) == GIT_OBJ_BLOB) |
|
|
|
1321 readme = readmefiles[i] + strlen("HEAD:"); |
|
|
|
1322 git_object_free(obj); |
|
|
|
1323 } |
|
|
|
1324 |
|
|
|
1325 if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") && |
|
|
|
1326 git_object_type(obj) == GIT_OBJ_BLOB) |
|
|
|
1327 submodules = ".gitmodules"; |
|
|
|
1328 git_object_free(obj); |
|
|
|
1329 |
|
|
|
1330 /* log for HEAD */ |
|
|
|
1331 fp = efopen("log.html", "w"); |
|
|
|
1332 relpath = ""; |
|
|
|
1333 mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO); |
|
|
|
1334 writeheader(fp, "Log"); |
|
|
|
1335 fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>" |
|
|
|
1336 "<td><b>Commit message</b></td>" |
|
|
|
1337 "<td><b>Author</b></td><td class=\"num\" align=\"right\"><b>Files</b></td>" |
|
|
|
1338 "<td class=\"num\" align=\"right\"><b>+</b></td>" |
|
|
|
1339 "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp); |
|
|
|
1340 |
|
|
|
1341 if (cachefile && head) { |
|
|
|
1342 /* read from cache file (does not need to exist) */ |
|
|
|
1343 if ((rcachefp = fopen(cachefile, "r"))) { |
|
|
|
1344 if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp)) |
|
|
|
1345 errx(1, "%s: no object id", cachefile); |
|
|
|
1346 if (git_oid_fromstr(&lastoid, lastoidstr)) |
|
|
|
1347 errx(1, "%s: invalid object id", cachefile); |
|
|
|
1348 } |
|
|
|
1349 |
|
|
|
1350 /* write log to (temporary) cache */ |
|
|
|
1351 if ((fd = mkstemp(tmppath)) == -1) |
|
|
|
1352 err(1, "mkstemp"); |
|
|
|
1353 if (!(wcachefp = fdopen(fd, "w"))) |
|
|
|
1354 err(1, "fdopen: '%s'", tmppath); |
|
|
|
1355 /* write last commit id (HEAD) */ |
|
|
|
1356 git_oid_tostr(buf, sizeof(buf), head); |
|
|
|
1357 fprintf(wcachefp, "%s\n", buf); |
|
|
|
1358 |
|
|
|
1359 writelog(fp, head); |
|
|
|
1360 |
|
|
|
1361 if (rcachefp) { |
|
|
|
1362 /* append previous log to log.html and the new cache */ |
|
|
|
1363 while (!feof(rcachefp)) { |
|
|
|
1364 n = fread(buf, 1, sizeof(buf), rcachefp); |
|
|
|
1365 if (ferror(rcachefp)) |
|
|
|
1366 break; |
|
|
|
1367 if (fwrite(buf, 1, n, fp) != n || |
|
|
|
1368 fwrite(buf, 1, n, wcachefp) != n) |
|
|
|
1369 break; |
|
|
|
1370 } |
|
|
|
1371 checkfileerror(rcachefp, cachefile, 'r'); |
|
|
|
1372 fclose(rcachefp); |
|
|
|
1373 } |
|
|
|
1374 checkfileerror(wcachefp, tmppath, 'w'); |
|
|
|
1375 fclose(wcachefp); |
|
|
|
1376 } else { |
|
|
|
1377 if (head) |
|
|
|
1378 writelog(fp, head); |
|
|
|
1379 } |
|
|
|
1380 |
|
|
|
1381 fputs("</tbody></table>", fp); |
|
|
|
1382 writefooter(fp); |
|
|
|
1383 checkfileerror(fp, "log.html", 'w'); |
|
|
|
1384 fclose(fp); |
|
|
|
1385 |
|
|
|
1386 /* files for HEAD */ |
|
|
|
1387 fp = efopen("files.html", "w"); |
|
|
|
1388 writeheader(fp, "Files"); |
|
|
|
1389 if (head) |
|
|
|
1390 writefiles(fp, head); |
|
|
|
1391 writefooter(fp); |
|
|
|
1392 checkfileerror(fp, "files.html", 'w'); |
|
|
|
1393 fclose(fp); |
|
|
|
1394 |
|
|
|
1395 /* summary page with branches and tags */ |
|
|
|
1396 fp = efopen("refs.html", "w"); |
|
|
|
1397 writeheader(fp, "Refs"); |
|
|
|
1398 writerefs(fp); |
|
|
|
1399 writefooter(fp); |
|
|
|
1400 checkfileerror(fp, "refs.html", 'w'); |
|
|
|
1401 fclose(fp); |
|
|
|
1402 |
|
|
|
1403 /* Atom feed */ |
|
|
|
1404 fp = efopen("atom.xml", "w"); |
|
|
|
1405 writeatom(fp, 1); |
|
|
|
1406 checkfileerror(fp, "atom.xml", 'w'); |
|
|
|
1407 fclose(fp); |
|
|
|
1408 |
|
|
|
1409 /* Atom feed for tags / releases */ |
|
|
|
1410 fp = efopen("tags.xml", "w"); |
|
|
|
1411 writeatom(fp, 0); |
|
|
|
1412 checkfileerror(fp, "tags.xml", 'w'); |
|
|
|
1413 fclose(fp); |
|
|
|
1414 |
|
|
|
1415 /* rename new cache file on success */ |
|
|
|
1416 if (cachefile && head) { |
|
|
|
1417 if (rename(tmppath, cachefile)) |
|
|
|
1418 err(1, "rename: '%s' to '%s'", tmppath, cachefile); |
|
|
|
1419 umask((mask = umask(0))); |
|
|
|
1420 if (chmod(cachefile, |
|
|
|
1421 (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask)) |
|
|
|
1422 err(1, "chmod: '%s'", cachefile); |
|
|
|
1423 } |
|
|
|
1424 |
|
|
|
1425 /* cleanup */ |
|
|
|
1426 git_repository_free(repo); |
|
|
|
1427 git_libgit2_shutdown(); |
|
|
|
1428 |
|
|
|
1429 return 0; |
|
|
|
1430 } |
|