]> git.armaanb.net Git - stagit.git/blob - urmoms.c
f0e5869bf2ad3dc966b9e440360ade78ef3f52eb
[stagit.git] / urmoms.c
1 #include <sys/stat.h>
2
3 #include <err.h>
4 #include <errno.h>
5 #include <inttypes.h>
6 #include <libgen.h>
7 #include <limits.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12
13 #include <git2.h>
14
15 #include "config.h"
16
17 struct commitinfo {
18         const git_oid *id;
19
20         char oid[GIT_OID_HEXSZ + 1];
21         char parentoid[GIT_OID_HEXSZ + 1];
22
23         const git_signature *author;
24         const char *summary;
25         const char *msg;
26
27         git_diff_stats *stats;
28         git_diff       *diff;
29         git_commit     *commit;
30         git_commit     *parent;
31         git_tree       *commit_tree;
32         git_tree       *parent_tree;
33
34         size_t addcount;
35         size_t delcount;
36         size_t filecount;
37 };
38
39 static git_repository *repo;
40
41 static const char *relpath = "";
42 static const char *repodir;
43
44 static char name[255];
45 static char description[255];
46 static int hasreadme, haslicense;
47
48 void
49 commitinfo_free(struct commitinfo *ci)
50 {
51         if (!ci)
52                 return;
53
54         git_diff_stats_free(ci->stats);
55         git_diff_free(ci->diff);
56         git_tree_free(ci->commit_tree);
57         git_tree_free(ci->parent_tree);
58         git_commit_free(ci->commit);
59 }
60
61 struct commitinfo *
62 commitinfo_getbyoid(const git_oid *id)
63 {
64         struct commitinfo *ci;
65         git_diff_options opts;
66         int error;
67
68         if (!(ci = calloc(1, sizeof(struct commitinfo))))
69                 err(1, "calloc");
70
71         ci->id = id;
72         if (git_commit_lookup(&(ci->commit), repo, id))
73                 goto err;
74
75         /* TODO: show tags when commit has it */
76         git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
77         git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
78
79         ci->author = git_commit_author(ci->commit);
80         ci->summary = git_commit_summary(ci->commit);
81         ci->msg = git_commit_message(ci->commit);
82
83         if ((error = git_commit_tree(&(ci->commit_tree), ci->commit)))
84                 goto err; /* TODO: handle error */
85         if (!(error = git_commit_parent(&(ci->parent), ci->commit, 0))) {
86                 if ((error = git_commit_tree(&(ci->parent_tree), ci->parent)))
87                         goto err;
88         } else {
89                 ci->parent = NULL;
90                 ci->parent_tree = NULL;
91         }
92
93         git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
94         opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
95         if ((error = git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts)))
96                 goto err;
97         if (git_diff_get_stats(&(ci->stats), ci->diff))
98                 goto err;
99
100         ci->addcount = git_diff_stats_insertions(ci->stats);
101         ci->delcount = git_diff_stats_deletions(ci->stats);
102         ci->filecount = git_diff_stats_files_changed(ci->stats);
103
104         /* TODO: show tag when commit has it */
105
106         return ci;
107
108 err:
109         commitinfo_free(ci);
110         free(ci);
111
112         return NULL;
113 }
114
115 FILE *
116 efopen(const char *name, const char *flags)
117 {
118         FILE *fp;
119
120         if (!(fp = fopen(name, flags)))
121                 err(1, "fopen");
122
123         return fp;
124 }
125
126 /* Escape characters below as HTML 2.0 / XML 1.0. */
127 void
128 xmlencode(FILE *fp, const char *s, size_t len)
129 {
130         size_t i;
131
132         for (i = 0; *s && i < len; s++, i++) {
133                 switch(*s) {
134                 case '<':  fputs("&lt;",   fp); break;
135                 case '>':  fputs("&gt;",   fp); break;
136                 case '\'': fputs("&apos;", fp); break;
137                 case '&':  fputs("&amp;",  fp); break;
138                 case '"':  fputs("&quot;", fp); break;
139                 default:   fputc(*s, fp);
140                 }
141         }
142 }
143
144 /* Some implementations of basename(3) return a pointer to a static
145  * internal buffer (OpenBSD). Others modify the contents of `path` (POSIX).
146  * This is a wrapper function that is compatible with both versions.
147  * The program will error out if basename(3) failed, this can only happen
148  * with the OpenBSD version. */
149 char *
150 xbasename(const char *path)
151 {
152         char *p, *b;
153
154         if (!(p = strdup(path)))
155                 err(1, "strdup");
156         if (!(b = basename(p)))
157                 err(1, "basename");
158         if (!(b = strdup(b)))
159                 err(1, "strdup");
160         free(p);
161
162         return b;
163 }
164
165 int
166 mkdirp(const char *path)
167 {
168         char tmp[PATH_MAX], *p;
169
170         strlcpy(tmp, path, sizeof(tmp)); /* TODO: bring in libutil? */
171         for (p = tmp + (tmp[0] == '/'); *p; p++) {
172                 if (*p != '/')
173                         continue;
174                 *p = '\0';
175                 if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
176                         return -1;
177                 *p = '/';
178         }
179         if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
180                 return -1;
181         return 0;
182 }
183
184 void
185 printtimeformat(FILE *fp, const git_time *intime, const char *fmt)
186 {
187         struct tm *intm;
188         time_t t;
189         char out[32];
190
191         t = (time_t) intime->time + (intime->offset * 60);
192         intm = gmtime(&t);
193         strftime(out, sizeof(out), fmt, intm);
194         fputs(out, fp);
195 }
196
197 void
198 printtimez(FILE *fp, const git_time *intime)
199 {
200         printtimeformat(fp, intime, "%Y-%m-%dT%H:%M:%SZ");
201 }
202
203 void
204 printtime(FILE *fp, const git_time *intime)
205 {
206         printtimeformat(fp, intime, "%a %b %e %T %Y");
207 }
208
209 void
210 printtimeshort(FILE *fp, const git_time *intime)
211 {
212         printtimeformat(fp, intime, "%Y-%m-%d %H:%M");
213 }
214
215 int
216 writeheader(FILE *fp)
217 {
218         fputs("<!DOCTYPE HTML>"
219                 "<html dir=\"ltr\" lang=\"en\">\n<head>\n"
220                 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
221                 "<meta http-equiv=\"Content-Language\" content=\"en\" />\n<title>", fp);
222         xmlencode(fp, name, strlen(name));
223         if (description[0])
224                 fputs(" - ", fp);
225         xmlencode(fp, description, strlen(description));
226         fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
227         fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n",
228                 name, relpath);
229         fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
230         fputs("</head>\n<body>\n<table><tr><td>", fp);
231         fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
232                 relpath, relpath);
233         fputs("</td><td><h1>", fp);
234         xmlencode(fp, name, strlen(name));
235         fputs("</h1><span class=\"desc\">", fp);
236         xmlencode(fp, description, strlen(description));
237         fputs("</span></td></tr><tr><td></td><td>\n", fp);
238         fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
239         fprintf(fp, "<a href=\"%sfiles.html\">Files</a>", relpath);
240         if (hasreadme)
241                 fprintf(fp, " | <a href=\"%sfile/README.html\">README</a>", relpath);
242         if (haslicense)
243                 fprintf(fp, " | <a href=\"%sfile/LICENSE.html\">LICENSE</a>", relpath);
244         fputs("</td></tr></table>\n<hr/><div id=\"content\">\n", fp);
245
246         return 0;
247 }
248
249 int
250 writefooter(FILE *fp)
251 {
252         return !fputs("</div></body>\n</html>", fp);
253 }
254
255 void
256 writeblobhtml(FILE *fp, const git_blob *blob)
257 {
258         off_t i = 0;
259         size_t n = 1;
260         char *nfmt = "<a href=\"#l%d\" id=\"l%d\">%d</a>\n";
261         const char *s = git_blob_rawcontent(blob);
262         git_off_t len = git_blob_rawsize(blob);
263
264         fputs("<table id=\"blob\"><tr><td class=\"num\"><pre>\n", fp);
265
266         if (len) {
267                 fprintf(fp, nfmt, n, n, n);
268                 while (i < len - 1) {
269                         if (s[i] == '\n') {
270                                 n++;
271                                 fprintf(fp, nfmt, n, n, n);
272                         }
273                         i++;
274                 }
275         }
276
277         fputs("</pre></td><td><pre>\n", fp);
278         xmlencode(fp, s, (size_t)len);
279         fputs("</pre></td></tr></table>\n", fp);
280 }
281
282 void
283 printcommit(FILE *fp, struct commitinfo *ci)
284 {
285         /* TODO: show tag when commit has it */
286         fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
287                 relpath, ci->oid, ci->oid);
288
289         if (ci->parentoid[0])
290                 fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n",
291                         relpath, ci->parentoid, ci->parentoid);
292
293 #if 0
294         if ((count = (int)git_commit_parentcount(commit)) > 1) {
295                 fprintf(fp, "<b>Merge:</b>");
296                 for (i = 0; i < count; i++) {
297                         git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
298                         fprintf(fp, " <a href=\"%scommit/%s.html\">%s</a>",
299                                 relpath, buf, buf);
300                 }
301                 fputc('\n', fp);
302         }
303 #endif
304         if (ci->author) {
305                 fprintf(fp, "<b>Author:</b> ");
306                 xmlencode(fp, ci->author->name, strlen(ci->author->name));
307                 fprintf(fp, " &lt;<a href=\"mailto:");
308                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
309                 fputs("\">", fp);
310                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
311                 fputs("</a>&gt;\n<b>Date:</b>   ", fp);
312                 printtime(fp, &(ci->author->when));
313                 fputc('\n', fp);
314         }
315         fputc('\n', fp);
316
317         if (ci->msg)
318                 xmlencode(fp, ci->msg, strlen(ci->msg));
319
320         fputc('\n', fp);
321 }
322
323 void
324 printshowfile(struct commitinfo *ci)
325 {
326         const git_diff_delta *delta;
327         const git_diff_hunk *hunk;
328         const git_diff_line *line;
329         git_patch *patch;
330         git_buf statsbuf;
331         size_t ndeltas, nhunks, nhunklines;
332         FILE *fp;
333         size_t i, j, k;
334         char path[PATH_MAX];
335
336         snprintf(path, sizeof(path), "commit/%s.html", ci->oid);
337         /* check if file exists if so skip it */
338         if (!access(path, F_OK))
339                 return;
340
341         fp = efopen(path, "w");
342         writeheader(fp);
343         fputs("<pre>\n", fp);
344         printcommit(fp, ci);
345
346         memset(&statsbuf, 0, sizeof(statsbuf));
347
348         /* diff stat */
349         if (ci->stats) {
350                 if (!git_diff_stats_to_buf(&statsbuf, ci->stats,
351                     GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_SHORT, 80)) {
352                         if (statsbuf.ptr && statsbuf.ptr[0]) {
353                                 fprintf(fp, "<b>Diffstat:</b>\n");
354                                 fputs(statsbuf.ptr, fp);
355                         }
356                 }
357         }
358
359         fputs("<hr/>", fp);
360
361         ndeltas = git_diff_num_deltas(ci->diff);
362         for (i = 0; i < ndeltas; i++) {
363                 if (git_patch_from_diff(&patch, ci->diff, i)) {
364                         git_patch_free(patch);
365                         break; /* TODO: handle error */
366                 }
367
368                 delta = git_patch_get_delta(patch);
369                 fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s.html\">%s</a> b/<a href=\"%sfile/%s.html\">%s</a></b>\n",
370                         relpath, delta->old_file.path, delta->old_file.path,
371                         relpath, delta->new_file.path, delta->new_file.path);
372
373                 /* check binary data */
374                 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
375                         fputs("Binary files differ\n", fp);
376                         git_patch_free(patch);
377                         continue;
378                 }
379
380                 nhunks = git_patch_num_hunks(patch);
381                 for (j = 0; j < nhunks; j++) {
382                         if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
383                                 break; /* TODO: handle error ? */
384
385                         fprintf(fp, "<span class=\"h\">%s</span>\n", hunk->header);
386
387                         for (k = 0; ; k++) {
388                                 if (git_patch_get_line_in_hunk(&line, patch, j, k))
389                                         break;
390                                 if (line->old_lineno == -1)
391                                         fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"i\">+",
392                                                 j, k, j, k);
393                                 else if (line->new_lineno == -1)
394                                         fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"d\">-",
395                                                 j, k, j, k);
396                                 else
397                                         fputc(' ', fp);
398                                 xmlencode(fp, line->content, line->content_len);
399                                 if (line->old_lineno == -1 || line->new_lineno == -1)
400                                         fputs("</a>", fp);
401                         }
402                 }
403                 git_patch_free(patch);
404         }
405         git_buf_free(&statsbuf);
406
407         fputs( "</pre>\n", fp);
408         writefooter(fp);
409         fclose(fp);
410         return;
411 }
412
413 void
414 writelog(FILE *fp)
415 {
416         struct commitinfo *ci;
417         git_revwalk *w = NULL;
418         git_oid id;
419         size_t len;
420
421         mkdir("commit", 0755);
422
423         git_revwalk_new(&w, repo);
424         git_revwalk_push_head(w);
425         git_revwalk_sorting(w, GIT_SORT_TIME);
426         git_revwalk_simplify_first_parent(w);
427
428         /* TODO: also make "expanded" log ? (with message body) */
429         fputs("<table id=\"log\"><thead>\n<tr><td>Age</td><td>Commit message</td>"
430                   "<td>Author</td><td>Files</td><td class=\"num\">+</td>"
431                   "<td class=\"num\">-</td></tr>\n</thead><tbody>\n", fp);
432         while (!git_revwalk_next(&id, w)) {
433                 relpath = "";
434
435                 if (!(ci = commitinfo_getbyoid(&id)))
436                         break;
437
438                 fputs("<tr><td>", fp);
439                 if (ci->author)
440                         printtimeshort(fp, &(ci->author->when));
441                 fputs("</td><td>", fp);
442                 if (ci->summary) {
443                         fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
444                         if ((len = strlen(ci->summary)) > summarylen) {
445                                 xmlencode(fp, ci->summary, summarylen - 1);
446                                 fputs("…", fp);
447                         } else {
448                                 xmlencode(fp, ci->summary, len);
449                         }
450                         fputs("</a>", fp);
451                 }
452                 fputs("</td><td>", fp);
453                 if (ci->author)
454                         xmlencode(fp, ci->author->name, strlen(ci->author->name));
455                 fputs("</td><td class=\"num\">", fp);
456                 fprintf(fp, "%zu", ci->filecount);
457                 fputs("</td><td class=\"num\">", fp);
458                 fprintf(fp, "+%zu", ci->addcount);
459                 fputs("</td><td class=\"num\">", fp);
460                 fprintf(fp, "-%zu", ci->delcount);
461                 fputs("</td></tr>\n", fp);
462
463                 relpath = "../";
464                 printshowfile(ci);
465
466                 commitinfo_free(ci);
467         }
468         fprintf(fp, "</tbody></table>");
469
470         git_revwalk_free(w);
471         relpath = "";
472 }
473
474 void
475 printcommitatom(FILE *fp, struct commitinfo *ci)
476 {
477         fputs("<entry>\n", fp);
478
479         fprintf(fp, "<id>%s</id>\n", ci->oid);
480         if (ci->author) {
481                 fputs("<updated>", fp);
482                 printtimez(fp, &(ci->author->when));
483                 fputs("</updated>\n", fp);
484         }
485         if (ci->summary) {
486                 fputs("<title type=\"text\">", fp);
487                 xmlencode(fp, ci->summary, strlen(ci->summary));
488                 fputs("</title>\n", fp);
489         }
490
491         fputs("<content type=\"text\">", fp);
492         fprintf(fp, "commit %s\n", ci->oid);
493         if (ci->parentoid[0])
494                 fprintf(fp, "parent %s\n", ci->parentoid);
495
496 #if 0
497         if ((count = (int)git_commit_parentcount(commit)) > 1) {
498                 fprintf(fp, "Merge:");
499                 for (i = 0; i < count; i++) {
500                         git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
501                         fprintf(fp, " %s", buf);
502                 }
503                 fputc('\n', fp);
504         }
505 #endif
506
507         if (ci->author) {
508                 fprintf(fp, "Author: ");
509                 xmlencode(fp, ci->author->name, strlen(ci->author->name));
510                 fprintf(fp, " &lt;");
511                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
512                 fprintf(fp, "&gt;\nDate:   ");
513                 printtime(fp, &(ci->author->when));
514         }
515         fputc('\n', fp);
516
517         if (ci->msg)
518                 xmlencode(fp, ci->msg, strlen(ci->msg));
519         fputs("\n</content>\n", fp);
520         if (ci->author) {
521                 fputs("<author><name>", fp);
522                 xmlencode(fp, ci->author->name, strlen(ci->author->name));
523                 fputs("</name>\n<email>", fp);
524                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
525                 fputs("</email>\n</author>\n", fp);
526         }
527         fputs("</entry>\n", fp);
528 }
529
530 int
531 writeatom(FILE *fp)
532 {
533         struct commitinfo *ci;
534         git_revwalk *w = NULL;
535         git_oid id;
536         size_t i, m = 100; /* max */
537
538         fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", fp);
539         fputs("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
540         xmlencode(fp, name, strlen(name));
541         fputs(", branch master</title>\n<subtitle>", fp);
542
543         xmlencode(fp, description, strlen(description));
544         fputs("</subtitle>\n", fp);
545
546         git_revwalk_new(&w, repo);
547         git_revwalk_push_head(w);
548         git_revwalk_sorting(w, GIT_SORT_TIME);
549         git_revwalk_simplify_first_parent(w);
550
551         for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
552                 if (!(ci = commitinfo_getbyoid(&id)))
553                         break;
554                 printcommitatom(fp, ci);
555                 commitinfo_free(ci);
556         }
557         git_revwalk_free(w);
558
559         fputs("</feed>", fp);
560
561         return 0;
562 }
563
564 int
565 writeblob(git_object *obj, const char *filename, git_off_t filesize)
566 {
567         char fpath[PATH_MAX];
568         char tmp[PATH_MAX] = "";
569         char *p;
570         FILE *fp;
571
572         snprintf(fpath, sizeof(fpath), "file/%s.html", filename);
573         if (mkdirp(dirname(fpath)))
574                 return 1;
575
576         p = fpath;
577         while (*p) {
578                 if (*p == '/')
579                         strlcat(tmp, "../", sizeof(tmp));
580                 p++;
581         }
582         relpath = tmp;
583
584         fp = efopen(fpath, "w");
585         writeheader(fp);
586         fputs("<p> ", fp);
587         xmlencode(fp, filename, strlen(filename));
588         fprintf(fp, " (%" PRIu32 "b)", filesize);
589         fputs("</p><hr/>", fp);
590
591         if (git_blob_is_binary((git_blob *)obj)) {
592                 fprintf(fp, "<p>Binary file</p>\n");
593         } else {
594                 writeblobhtml(fp, (git_blob *)obj);
595                 if (ferror(fp))
596                         err(1, "fwrite");
597         }
598         writefooter(fp);
599         fclose(fp);
600
601         relpath = "";
602
603         return 0;
604 }
605
606 int
607 writefilestree(FILE *fp, git_tree *tree, const char *path)
608 {
609         const git_tree_entry *entry = NULL;
610         const char *filename;
611         char filepath[PATH_MAX];
612         git_object *obj = NULL;
613         git_off_t filesize;
614         size_t count, i;
615         int ret;
616
617         count = git_tree_entrycount(tree);
618         for (i = 0; i < count; i++) {
619                 if (!(entry = git_tree_entry_byindex(tree, i)))
620                         return -1;
621
622                 filename = git_tree_entry_name(entry);
623                 if (git_tree_entry_to_object(&obj, repo, entry))
624                         return -1;
625                 switch (git_object_type(obj)) {
626                 case GIT_OBJ_BLOB:
627                         break;
628                 case GIT_OBJ_TREE:
629                         ret = writefilestree(fp, (git_tree *)obj, filename);
630                         git_object_free(obj);
631                         if (ret)
632                                 return ret;
633                         continue;
634                 default:
635                         git_object_free(obj);
636                         continue;
637                 }
638                 if (path[0]) {
639                         snprintf(filepath, sizeof(filepath), "%s/%s", path, filename);
640                         filename = filepath;
641                 }
642
643                 filesize = git_blob_rawsize((git_blob *)obj);
644
645                 fputs("<tr><td>", fp);
646                 /* TODO: fancy print, like: "-rw-r--r--" */
647                 fprintf(fp, "%u", git_tree_entry_filemode_raw(entry));
648                 fprintf(fp, "</td><td><a href=\"%sfile/", relpath);
649                 xmlencode(fp, filename, strlen(filename));
650                 fputs(".html\">", fp);
651                 xmlencode(fp, filename, strlen(filename));
652                 fputs("</a></td><td class=\"num\">", fp);
653                 fprintf(fp, "%" PRIu32, filesize);
654                 fputs("</td></tr>\n", fp);
655
656                 writeblob(obj, filename, filesize);
657         }
658
659         return 0;
660 }
661
662 int
663 writefiles(FILE *fp)
664 {
665         const git_oid *id;
666         git_tree *tree = NULL;
667         git_object *obj = NULL;
668         git_commit *commit = NULL;
669
670         fputs("<table id=\"files\"><thead>\n"
671               "<tr><td>Mode</td><td>Name</td><td>Size</td></tr>\n"
672               "</thead><tbody>\n", fp);
673
674         if (git_revparse_single(&obj, repo, "HEAD"))
675                 return -1;
676         id = git_object_id(obj);
677         if (git_commit_lookup(&commit, repo, id))
678                 return -1;
679         if (git_commit_tree(&tree, commit)) {
680                 git_commit_free(commit);
681                 return -1;
682         }
683         git_commit_free(commit);
684
685         writefilestree(fp, tree, "");
686
687         git_commit_free(commit);
688         git_tree_free(tree);
689
690         fputs("</tbody></table>", fp);
691
692         return 0;
693 }
694
695 int
696 main(int argc, char *argv[])
697 {
698         git_object *obj = NULL;
699         const git_error *e = NULL;
700         FILE *fp, *fpread;
701         char path[PATH_MAX], *p;
702         int status;
703
704         if (argc != 2) {
705                 fprintf(stderr, "%s <repodir>\n", argv[0]);
706                 return 1;
707         }
708         repodir = argv[1];
709
710         git_libgit2_init();
711
712         if ((status = git_repository_open_ext(&repo, repodir,
713                 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) < 0) {
714                 e = giterr_last();
715                 fprintf(stderr, "error %d/%d: %s\n", status, e->klass, e->message);
716                 return status;
717         }
718
719         /* use directory name as name */
720         p = xbasename(repodir);
721         snprintf(name, sizeof(name), "%s", p);
722         free(p);
723
724         /* read description or .git/description */
725         snprintf(path, sizeof(path), "%s%s%s",
726                 repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "description");
727         if (!(fpread = fopen(path, "r"))) {
728                 snprintf(path, sizeof(path), "%s%s%s",
729                         repodir, repodir[strlen(repodir)] == '/' ? "" : "/", ".git/description");
730                 fpread = fopen(path, "r");
731         }
732         if (fpread) {
733                 if (!fgets(description, sizeof(description), fpread))
734                         description[0] = '\0';
735                 fclose(fpread);
736         }
737
738         /* check LICENSE */
739         haslicense = !git_revparse_single(&obj, repo, "HEAD:LICENSE");
740         git_object_free(obj);
741         /* check README */
742         hasreadme = !git_revparse_single(&obj, repo, "HEAD:README");
743         git_object_free(obj);
744
745         fp = efopen("log.html", "w");
746         writeheader(fp);
747         writelog(fp);
748         writefooter(fp);
749         fclose(fp);
750
751         fp = efopen("files.html", "w");
752         writeheader(fp);
753         writefiles(fp);
754         writefooter(fp);
755         fclose(fp);
756
757         /* Atom feed */
758         fp = efopen("atom.xml", "w");
759         writeatom(fp);
760         fclose(fp);
761
762         /* cleanup */
763         git_repository_free(repo);
764         git_libgit2_shutdown();
765
766         return 0;
767 }