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