]> git.armaanb.net Git - stagit.git/blob - urmoms.c
initial diff support, fix log link
[stagit.git] / urmoms.c
1 #include <sys/stat.h>
2
3 #include <err.h>
4 #include <libgen.h>
5 #include <limits.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #include "git2.h"
11
12 static git_repository *repo;
13
14 static const char *relpath = "";
15 static const char *repodir;
16
17 static char name[255];
18 static char description[255];
19 static int hasreadme, haslicense;
20
21 int
22 writeheader(FILE *fp)
23 {
24         fprintf(fp, "<!DOCTYPE HTML>"
25                 "<html dir=\"ltr\" lang=\"en\"><head>"
26                 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"
27                 "<meta http-equiv=\"Content-Language\" content=\"en\" />");
28         fprintf(fp, "<title>%s%s%s</title>", name, description[0] ? " - " : "", description);
29         fprintf(fp, "<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />", relpath);
30         fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />",
31                 name, relpath);
32         fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />"
33                 "</head><body><center>");
34         fprintf(fp, "<h1><img src=\"%slogo.png\" alt=\"\" /> %s</h1>", relpath, name);
35         fprintf(fp, "<span class=\"desc\">%s</span><br/>", description);
36         fprintf(fp, "<a href=\"%slog.html\">Log</a> |", relpath);
37         fprintf(fp, "<a href=\"%sfiles.html\">Files</a>| ", relpath);
38         fprintf(fp, "<a href=\"%sstats.html\">Stats</a>", relpath);
39         if (hasreadme)
40                 fprintf(fp, " | <a href=\"%sreadme.html\">README</a>", relpath);
41         if (haslicense)
42                 fprintf(fp, " | <a href=\"%slicense.html\">LICENSE</a>", relpath);
43         fprintf(fp, "</center><hr/><pre>");
44
45         return 0;
46 }
47
48 int
49 writefooter(FILE *fp)
50 {
51         fprintf(fp, "</pre></body></html>");
52
53         return 0;
54 }
55
56 FILE *
57 efopen(const char *name, const char *flags)
58 {
59         FILE *fp;
60
61         fp = fopen(name, flags);
62         if (!fp)
63                 err(1, "fopen");
64
65         return fp;
66 }
67
68 /* Escape characters below as HTML 2.0 / XML 1.0. */
69 void
70 xmlencode(FILE *fp, const char *s, size_t len)
71 {
72         size_t i;
73
74         for (i = 0; *s && i < len; s++, i++) {
75                 switch(*s) {
76                 case '<':  fputs("&lt;",   fp); break;
77                 case '>':  fputs("&gt;",   fp); break;
78                 case '\'': fputs("&apos;", fp); break;
79                 case '&':  fputs("&amp;",  fp); break;
80                 case '"':  fputs("&quot;", fp); break;
81                 default:   fputc(*s, fp);
82                 }
83         }
84 }
85
86 /* Some implementations of basename(3) return a pointer to a static
87  * internal buffer (OpenBSD). Others modify the contents of `path` (POSIX).
88  * This is a wrapper function that is compatible with both versions.
89  * The program will error out if basename(3) failed, this can only happen
90  * with the OpenBSD version. */
91 char *
92 xbasename(const char *path)
93 {
94         char *p, *b;
95
96         if (!(p = strdup(path)))
97                 err(1, "strdup");
98         if (!(b = basename(p)))
99                 err(1, "basename");
100         if (!(b = strdup(b)))
101                 err(1, "strdup");
102         free(p);
103
104         return b;
105 }
106
107 void
108 printtime(FILE *fp, const git_time *intime)
109 {
110         struct tm *intm;
111         time_t t;
112         int offset, hours, minutes;
113         char sign, out[32];
114
115         offset = intime->offset;
116         if (offset < 0) {
117                 sign = '-';
118                 offset = -offset;
119         } else {
120                 sign = '+';
121         }
122
123         hours = offset / 60;
124         minutes = offset % 60;
125
126         t = (time_t) intime->time + (intime->offset * 60);
127
128         intm = gmtime(&t);
129         strftime(out, sizeof(out), "%a %b %e %T %Y", intm);
130
131         fprintf(fp, "%s %c%02d%02d\n", out, sign, hours, minutes);
132 }
133
134 void
135 printcommit(FILE *fp, git_commit *commit)
136 {
137         const git_signature *sig;
138         char buf[GIT_OID_HEXSZ + 1];
139         int i, count;
140         const char *scan, *eol;
141
142         git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
143         fprintf(fp, "commit <a href=\"%scommit/%s.html\">%s</a>\n",
144                 relpath, buf, buf);
145
146         if (git_oid_tostr(buf, sizeof(buf), git_commit_parent_id(commit, 0)))
147                 fprintf(fp, "parent <a href=\"%scommit/%s.html\">%s</a>\n",
148                         relpath, buf, buf);
149
150         if ((count = (int)git_commit_parentcount(commit)) > 1) {
151                 fprintf(fp, "Merge:");
152                 for (i = 0; i < count; ++i) {
153                         git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
154                         fprintf(fp, " <a href=\"%scommit/%s.html\">%s</a>",
155                                 relpath, buf, buf);
156                 }
157                 fputc('\n', fp);
158         }
159         if ((sig = git_commit_author(commit)) != NULL) {
160                 fprintf(fp, "Author: ");
161                 xmlencode(fp, sig->name, strlen(sig->name));
162                 fprintf(fp, " &lt;");
163                 xmlencode(fp, sig->email, strlen(sig->email));
164                 fprintf(fp, "&gt;\nDate:   ");
165                 printtime(fp, &sig->when);
166         }
167         fputc('\n', fp);
168
169         for (scan = git_commit_message(commit); scan && *scan;) {
170                 for (eol = scan; *eol && *eol != '\n'; ++eol)   /* find eol */
171                         ;
172
173                 fprintf(fp, "    %.*s\n", (int) (eol - scan), scan);
174                 scan = *eol ? eol + 1 : NULL;
175         }
176         fputc('\n', fp);
177 }
178
179 void
180 printshowfile(git_commit *commit)
181 {
182         const git_diff_delta *delta = NULL;
183         const git_diff_hunk *hunk = NULL;
184         const git_diff_line *line = NULL;
185         git_commit *parent = NULL;
186         git_tree *commit_tree = NULL, *parent_tree = NULL;
187         git_patch *patch = NULL;
188         git_diff *diff = NULL;
189         size_t i, j, k, ndeltas, nhunks = 0, nhunklines = 0;
190         char buf[GIT_OID_HEXSZ + 1], path[PATH_MAX];
191         FILE *fp;
192         int error;
193
194         git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
195
196         snprintf(path, sizeof(path), "commit/%s.html", buf);
197         fp = efopen(path, "w+b");
198
199         writeheader(fp);
200         printcommit(fp, commit);
201
202         error = git_commit_parent(&parent, commit, 0);
203         if (error)
204                 return;
205
206         error = git_commit_tree(&commit_tree, commit);
207         if (error)
208                 return;
209         error = git_commit_tree(&parent_tree, parent);
210         if (error)
211                 return;
212
213         error = git_diff_tree_to_tree(&diff, repo, commit_tree, parent_tree, NULL);
214         if (error)
215                 return;
216
217         /* TODO: diff stat (files list and insertions/deletions) */
218
219         ndeltas = git_diff_num_deltas(diff);
220         for (i = 0; i < ndeltas; i++) {
221                 if (git_patch_from_diff(&patch, diff, i)) {
222                         git_patch_free(patch);
223                         break; /* TODO: handle error */
224                 }
225
226                 delta = git_patch_get_delta(patch);
227                 fprintf(fp, "diff --git a/<a href=\"%s%s\">%s</a> b/<a href=\"%s%s\">%s</a>\n",
228                         relpath, delta->old_file.path, delta->old_file.path,
229                         relpath, delta->new_file.path, delta->new_file.path);
230
231 #if 0
232                 switch (delta->flags) {
233                 case GIT_DIFF_FLAG_BINARY:       continue; /* TODO: binary data */
234                 case GIT_DIFF_FLAG_NOT_BINARY:   break;
235                 case GIT_DIFF_FLAG_VALID_ID:     break; /* TODO: check */
236                 case GIT_DIFF_FLAG_EXISTS:       break; /* TODO: check */
237                 }
238 #endif
239
240                 nhunks = git_patch_num_hunks(patch);
241                 for (j = 0; j < nhunks; j++) {
242                         if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
243                                 break; /* TODO: handle error ? */
244
245                         fprintf(fp, "%s\n", hunk->header);
246
247                         for (k = 0; ; k++) {
248                                 if (git_patch_get_line_in_hunk(&line, patch, j, k))
249                                         break;
250                                 if (line->old_lineno == -1)
251                                         fputc('+', fp);
252                                 else if (line->new_lineno == -1)
253                                         fputc('-', fp);
254                                 else
255                                         fputc(' ', fp);
256                                 xmlencode(fp, line->content, line->content_len);
257                         }
258                 }
259                 git_patch_free(patch);
260         }
261         git_diff_free(diff);
262
263         writefooter(fp);
264         fclose(fp);
265 }
266
267 int
268 writelog(FILE *fp)
269 {
270         git_revwalk *w = NULL;
271         git_oid id;
272         git_commit *c = NULL;
273         size_t i;
274
275         mkdir("commit", 0755);
276
277         git_revwalk_new(&w, repo);
278         git_revwalk_push_head(w);
279
280         i = 0;
281         while (!git_revwalk_next(&id, w)) {
282                 if (git_commit_lookup(&c, repo, &id))
283                         return 1; /* TODO: error */
284                 printcommit(fp, c);
285                 printshowfile(c);
286                 git_commit_free(c);
287
288                 /* DEBUG */
289                 i++;
290                 if (i > 100)
291                         break;
292         }
293         git_revwalk_free(w);
294
295         return 0;
296 }
297
298 #if 0
299 int
300 writeatom(FILE *fp)
301 {
302         git_revwalk *w = NULL;
303         git_oid id;
304         git_commit *c = NULL;
305
306         git_revwalk_new(&w, repo);
307         git_revwalk_push_head(w);
308
309         while (!git_revwalk_next(&id, w)) {
310                 if (git_commit_lookup(&c, repo, &id))
311                         return 1; /* TODO: error */
312                 printcommit(fp, c);
313                 printshowfile(c);
314                 git_commit_free(c);
315         }
316         git_revwalk_free(w);
317
318         return 0;
319 }
320 #endif
321
322 int
323 writefiles(FILE *fp)
324 {
325         git_index *index;
326         const git_index_entry *entry;
327         size_t count, i;
328
329         git_repository_index(&index, repo);
330
331         count = git_index_entrycount(index);
332         for (i = 0; i < count; i++) {
333                 entry = git_index_get_byindex(index, i);
334                 fprintf(fp, "name: %s, size: %lu, mode: %lu\n",
335                         entry->path, entry->file_size, entry->mode);
336         }
337
338         return 0;
339 }
340
341 #if 0
342 int
343 writebranches(FILE *fp)
344 {
345         git_branch_iterator *branchit = NULL;
346         git_branch_t branchtype;
347         git_reference *branchref;
348         char branchbuf[BUFSIZ] = "";
349         int status;
350
351         git_branch_iterator_new(&branchit, repo, GIT_BRANCH_LOCAL);
352
353         while ((status = git_branch_next(&branchref, &branchtype, branchit)) == GIT_ITEROVER) {
354                 git_reference_normalize_name(branchbuf, sizeof(branchbuf),
355                         git_reference_name(branchref),
356                         GIT_REF_FORMAT_ALLOW_ONELEVEL | GIT_REF_FORMAT_REFSPEC_SHORTHAND);
357
358                 /* fprintf(fp, "branch: |%s|\n", branchbuf); */
359         }
360
361         git_branch_iterator_free(branchit);
362
363         return 0;
364 }
365 #endif
366
367 void
368 writeblobhtml(FILE *fp, const git_blob *blob)
369 {
370         xmlencode(fp, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob));
371 }
372
373 int
374 main(int argc, char *argv[])
375 {
376         git_object *obj = NULL;
377         const git_error *e = NULL;
378         FILE *fp, *fpread;
379         char path[PATH_MAX], *p;
380         int status;
381
382         if (argc != 2) {
383                 fprintf(stderr, "%s <repodir>\n", argv[0]);
384                 return 1;
385         }
386         repodir = argv[1];
387
388         git_libgit2_init();
389
390         if ((status = git_repository_open(&repo, repodir)) < 0) {
391                 e = giterr_last();
392                 fprintf(stderr, "error %d/%d: %s\n", status, e->klass, e->message);
393                 return status;
394         }
395
396         /* use directory name as name */
397         p = xbasename(repodir);
398         snprintf(name, sizeof(name), "%s", p);
399         free(p);
400
401         /* read description or .git/description */
402         snprintf(path, sizeof(path), "%s%s%s",
403                 repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "description");
404         if (!(fpread = fopen(path, "r+b"))) {
405                 snprintf(path, sizeof(path), "%s%s%s",
406                         repodir, repodir[strlen(repodir)] == '/' ? "" : "/", ".git/description");
407                 fpread = fopen(path, "r+b");
408         }
409         if (fpread) {
410                 if (!fgets(description, sizeof(description), fpread))
411                         description[0] = '\0';
412                 fclose(fpread);
413         }
414
415         /* read LICENSE */
416         if (!git_revparse_single(&obj, repo, "HEAD:LICENSE")) {
417                 fp = efopen("license.html", "w+b");
418                 writeheader(fp);
419                 writeblobhtml(fp, (git_blob *)obj);
420                 if (ferror(fp))
421                         err(1, "fwrite");
422                 writefooter(fp);
423
424                 fclose(fp);
425
426                 haslicense = 1;
427         }
428
429         /* read README */
430         if (!git_revparse_single(&obj, repo, "HEAD:README")) {
431                 fp = efopen("readme.html", "w+b");
432                 writeheader(fp);
433                 writeblobhtml(fp, (git_blob *)obj);
434                 if (ferror(fp))
435                         err(1, "fwrite");
436                 writefooter(fp);
437                 fclose(fp);
438
439                 hasreadme = 1;
440         }
441
442         fp = efopen("log.html", "w+b");
443         writeheader(fp);
444         writelog(fp);
445         writefooter(fp);
446         fclose(fp);
447
448 #if 0
449         fp = efopen("atom.xml", "w+b");
450         writeatom(fp);
451         fclose(fp);
452 #endif
453
454         fp = efopen("files.html", "w+b");
455         writeheader(fp);
456         writefiles(fp);
457         writefooter(fp);
458         fclose(fp);
459
460         /* cleanup */
461         git_repository_free(repo);
462         git_libgit2_shutdown();
463
464         return 0;
465 }