17 char oid[GIT_OID_HEXSZ + 1];
18 char parentoid[GIT_OID_HEXSZ + 1];
20 const git_signature *author;
24 git_diff_stats *stats;
28 git_tree *commit_tree;
29 git_tree *parent_tree;
36 static git_repository *repo;
38 static const char *relpath = "";
39 static const char *repodir;
41 static char name[255];
42 static char description[255];
43 static int hasreadme, haslicense;
46 commitinfo_free(struct commitinfo *ci)
51 git_diff_stats_free(ci->stats);
52 git_diff_free(ci->diff);
53 git_commit_free(ci->commit);
57 commitinfo_getbyoid(const git_oid *id)
59 struct commitinfo *ci;
62 if (!(ci = calloc(1, sizeof(struct commitinfo))))
66 if (git_commit_lookup(&(ci->commit), repo, id))
69 /* TODO: show tags when commit has it */
70 git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
71 git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
73 ci->author = git_commit_author(ci->commit);
74 ci->summary = git_commit_summary(ci->commit);
75 ci->msg = git_commit_message(ci->commit);
77 if ((error = git_commit_tree(&(ci->commit_tree), ci->commit)))
78 goto err; /* TODO: handle error */
79 if (!(error = git_commit_parent(&(ci->parent), ci->commit, 0))) {
80 if ((error = git_commit_tree(&(ci->parent_tree), ci->parent)))
84 ci->parent_tree = NULL;
87 if ((error = git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, NULL)))
89 if (git_diff_get_stats(&(ci->stats), ci->diff))
92 ci->addcount = git_diff_stats_insertions(ci->stats);
93 ci->delcount = git_diff_stats_deletions(ci->stats);
94 ci->filecount = git_diff_stats_files_changed(ci->stats);
96 /* TODO: show tag when commit has it */
108 writeheader(FILE *fp)
110 fputs("<!DOCTYPE HTML>"
111 "<html dir=\"ltr\" lang=\"en\">\n<head>\n"
112 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
113 "<meta http-equiv=\"Content-Language\" content=\"en\" />\n", fp);
114 fprintf(fp, "<title>%s%s%s</title>\n", name, description[0] ? " - " : "", description);
115 fprintf(fp, "<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
116 fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n",
118 fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
119 fputs("</head>\n<body>\n\n", fp);
120 fprintf(fp, "<table><tr><td><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></td>"
121 "<td><h1>%s</h1><span class=\"desc\">%s</span></td></tr><tr><td></td><td>\n",
122 relpath, name, description);
123 fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
124 fprintf(fp, "<a href=\"%sfiles.html\">Files</a>", relpath);
126 fprintf(fp, " | <a href=\"%sreadme.html\">README</a>", relpath);
128 fprintf(fp, " | <a href=\"%slicense.html\">LICENSE</a>", relpath);
129 fputs("</td></tr></table>\n<hr/><div id=\"content\">\n", fp);
135 writefooter(FILE *fp)
137 return !fputs("</div></body>\n</html>", fp);
141 efopen(const char *name, const char *flags)
145 if (!(fp = fopen(name, flags)))
151 /* Escape characters below as HTML 2.0 / XML 1.0. */
153 xmlencode(FILE *fp, const char *s, size_t len)
157 for (i = 0; *s && i < len; s++, i++) {
159 case '<': fputs("<", fp); break;
160 case '>': fputs(">", fp); break;
161 case '\'': fputs("'", fp); break;
162 case '&': fputs("&", fp); break;
163 case '"': fputs(""", fp); break;
164 default: fputc(*s, fp);
169 /* Some implementations of basename(3) return a pointer to a static
170 * internal buffer (OpenBSD). Others modify the contents of `path` (POSIX).
171 * This is a wrapper function that is compatible with both versions.
172 * The program will error out if basename(3) failed, this can only happen
173 * with the OpenBSD version. */
175 xbasename(const char *path)
179 if (!(p = strdup(path)))
181 if (!(b = basename(p)))
183 if (!(b = strdup(b)))
191 printtimeformat(FILE *fp, const git_time *intime, const char *fmt)
197 t = (time_t) intime->time + (intime->offset * 60);
199 strftime(out, sizeof(out), fmt, intm);
204 printtimez(FILE *fp, const git_time *intime)
206 printtimeformat(fp, intime, "%Y-%m-%dT%H:%M:%SZ");
210 printtime(FILE *fp, const git_time *intime)
212 printtimeformat(fp, intime, "%a %b %e %T %Y");
216 printtimeshort(FILE *fp, const git_time *intime)
218 printtimeformat(fp, intime, "%Y-%m-%d %H:%M");
222 writeblobhtml(FILE *fp, const git_blob *blob)
224 xmlencode(fp, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob));
228 printcommit(FILE *fp, struct commitinfo *ci)
230 /* TODO: show tag when commit has it */
231 fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
232 relpath, ci->oid, ci->oid);
234 if (ci->parentoid[0])
235 fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n",
236 relpath, ci->parentoid, ci->parentoid);
239 if ((count = (int)git_commit_parentcount(commit)) > 1) {
240 fprintf(fp, "<b>Merge:</b>");
241 for (i = 0; i < count; i++) {
242 git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
243 fprintf(fp, " <a href=\"%scommit/%s.html\">%s</a>",
250 fprintf(fp, "<b>Author:</b> ");
251 xmlencode(fp, ci->author->name, strlen(ci->author->name));
252 fprintf(fp, " <<a href=\"mailto:");
253 xmlencode(fp, ci->author->email, strlen(ci->author->email));
255 xmlencode(fp, ci->author->email, strlen(ci->author->email));
256 fputs("</a>>\n<b>Date:</b> ", fp);
257 printtime(fp, &(ci->author->when));
263 xmlencode(fp, ci->msg, strlen(ci->msg));
269 printshowfile(struct commitinfo *ci)
271 const git_diff_delta *delta;
272 const git_diff_hunk *hunk;
273 const git_diff_line *line;
276 size_t ndeltas, nhunks, nhunklines;
281 snprintf(path, sizeof(path), "commit/%s.html", ci->oid);
282 /* check if file exists if so skip it */
283 if (!access(path, F_OK))
286 fp = efopen(path, "w+b");
288 fputs("<pre>\n", fp);
291 memset(&statsbuf, 0, sizeof(statsbuf));
295 if (!git_diff_stats_to_buf(&statsbuf, ci->stats,
296 GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_SHORT, 80)) {
297 if (statsbuf.ptr && statsbuf.ptr[0]) {
298 fprintf(fp, "<b>Diffstat:</b>\n");
299 fputs(statsbuf.ptr, fp);
306 ndeltas = git_diff_num_deltas(ci->diff);
307 for (i = 0; i < ndeltas; i++) {
308 if (git_patch_from_diff(&patch, ci->diff, i)) {
309 git_patch_free(patch);
310 break; /* TODO: handle error */
313 delta = git_patch_get_delta(patch);
314 fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s\">%s</a> b/<a href=\"%sfile/%s\">%s</a></b>\n",
315 relpath, delta->old_file.path, delta->old_file.path,
316 relpath, delta->new_file.path, delta->new_file.path);
318 /* check binary data */
319 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
320 fputs("Binary files differ\n", fp);
321 git_patch_free(patch);
325 nhunks = git_patch_num_hunks(patch);
326 for (j = 0; j < nhunks; j++) {
327 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
328 break; /* TODO: handle error ? */
330 fprintf(fp, "<span class=\"h\">%s</span>\n", hunk->header);
333 if (git_patch_get_line_in_hunk(&line, patch, j, k))
335 if (line->old_lineno == -1)
336 fprintf(fp, "<span class=\"i\"><a href=\"#h%zu-%zu\" id=\"h%zu-%zu\">+",
338 else if (line->new_lineno == -1)
339 fprintf(fp, "<span class=\"d\"><a href=\"#h%zu-%zu\" id=\"h%zu-%zu\">-",
343 xmlencode(fp, line->content, line->content_len);
344 if (line->old_lineno == -1 || line->new_lineno == -1)
345 fputs("</a></span>", fp);
348 git_patch_free(patch);
350 git_buf_free(&statsbuf);
352 fputs( "</pre>\n", fp);
361 struct commitinfo *ci;
362 git_revwalk *w = NULL;
367 mkdir("commit", 0755);
369 git_revwalk_new(&w, repo);
370 git_revwalk_push_head(w);
372 /* TODO: also make "expanded" log ? (with message body) */
373 fputs("<table><thead>\n<tr><td>Age</td><td>Commit message</td><td>Author</td>"
374 "<td>Files</td><td>+</td><td>-</td></tr>\n</thead><tbody>\n", fp);
375 while (!git_revwalk_next(&id, w)) {
378 if (!(ci = commitinfo_getbyoid(&id)))
381 fputs("<tr><td>", fp);
383 printtimeshort(fp, &(ci->author->when));
384 fputs("</td><td>", fp);
386 fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
387 if ((len = strlen(ci->summary)) > 79) {
388 xmlencode(fp, ci->summary, 76);
391 xmlencode(fp, ci->summary, len);
395 fputs("</td><td>", fp);
397 xmlencode(fp, ci->author->name, strlen(ci->author->name));
398 fputs("</td><td>", fp);
399 fprintf(fp, "%zu", ci->filecount);
400 fputs("</td><td>", fp);
401 fprintf(fp, "+%zu", ci->addcount);
402 fputs("</td><td>", fp);
403 fprintf(fp, "-%zu", ci->delcount);
404 fputs("</td></tr>\n", fp);
411 fprintf(fp, "</tbody></table>");
420 printcommitatom(FILE *fp, struct commitinfo *ci)
422 fputs("<entry>\n", fp);
424 fprintf(fp, "<id>%s</id>\n", ci->oid);
426 fputs("<updated>", fp);
427 printtimez(fp, &(ci->author->when));
428 fputs("</updated>\n", fp);
431 fputs("<title type=\"text\">", fp);
432 xmlencode(fp, ci->summary, strlen(ci->summary));
433 fputs("</title>\n", fp);
436 fputs("<content type=\"text\">", fp);
437 fprintf(fp, "commit %s\n", ci->oid);
438 if (ci->parentoid[0])
439 fprintf(fp, "parent %s\n", ci->parentoid);
442 if ((count = (int)git_commit_parentcount(commit)) > 1) {
443 fprintf(fp, "Merge:");
444 for (i = 0; i < count; i++) {
445 git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
446 fprintf(fp, " %s", buf);
453 fprintf(fp, "Author: ");
454 xmlencode(fp, ci->author->name, strlen(ci->author->name));
455 fprintf(fp, " <");
456 xmlencode(fp, ci->author->email, strlen(ci->author->email));
457 fprintf(fp, ">\nDate: ");
458 printtime(fp, &(ci->author->when));
463 xmlencode(fp, ci->msg, strlen(ci->msg));
464 fputs("\n</content>\n", fp);
466 fputs("<author><name>", fp);
467 xmlencode(fp, ci->author->name, strlen(ci->author->name));
468 fputs("</name>\n<email>", fp);
469 xmlencode(fp, ci->author->email, strlen(ci->author->email));
470 fputs("</email>\n</author>\n", fp);
472 fputs("</entry>\n", fp);
478 struct commitinfo *ci;
479 git_revwalk *w = NULL;
481 size_t i, m = 100; /* max */
483 fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", fp);
484 fputs("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
485 xmlencode(fp, name, strlen(name));
486 fputs(", branch master</title>\n<subtitle>", fp);
488 xmlencode(fp, description, strlen(description));
489 fputs("</subtitle>\n", fp);
491 git_revwalk_new(&w, repo);
492 git_revwalk_push_head(w);
494 for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
495 if (!(ci = commitinfo_getbyoid(&id)))
497 printcommitatom(fp, ci);
502 fputs("</feed>", fp);
510 const git_index_entry *entry;
514 fputs("<table><thead>\n"
515 "<tr><td>Mode</td><td>Name</td><td>Size</td></tr>\n"
516 "</thead><tbody>\n", fp);
518 git_repository_index(&index, repo);
519 count = git_index_entrycount(index);
521 for (i = 0; i < count; i++) {
522 entry = git_index_get_byindex(index, i);
523 fputs("<tr><td>", fp);
524 fprintf(fp, "%u", entry->mode); /* TODO: fancy print, like: "-rw-r--r--" */
525 fprintf(fp, "</td><td><a href=\"%sfile/", relpath);
526 xmlencode(fp, entry->path, strlen(entry->path));
528 xmlencode(fp, entry->path, strlen(entry->path));
529 fputs("</a></td><td>", fp);
530 fprintf(fp, "%" PRIu64, entry->file_size);
531 fputs("</td></tr>\n", fp);
534 fputs("</tbody></table>", fp);
540 main(int argc, char *argv[])
542 git_object *obj_license = NULL, *obj_readme = NULL;
543 const git_error *e = NULL;
545 char path[PATH_MAX], *p;
549 fprintf(stderr, "%s <repodir>\n", argv[0]);
556 if ((status = git_repository_open(&repo, repodir)) < 0) {
558 fprintf(stderr, "error %d/%d: %s\n", status, e->klass, e->message);
562 /* use directory name as name */
563 p = xbasename(repodir);
564 snprintf(name, sizeof(name), "%s", p);
567 /* read description or .git/description */
568 snprintf(path, sizeof(path), "%s%s%s",
569 repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "description");
570 if (!(fpread = fopen(path, "r+b"))) {
571 snprintf(path, sizeof(path), "%s%s%s",
572 repodir, repodir[strlen(repodir)] == '/' ? "" : "/", ".git/description");
573 fpread = fopen(path, "r+b");
576 if (!fgets(description, sizeof(description), fpread))
577 description[0] = '\0';
582 haslicense = !git_revparse_single(&obj_license, repo, "HEAD:LICENSE");
584 hasreadme = !git_revparse_single(&obj_readme, repo, "HEAD:README");
588 fp = efopen("license.html", "w+b");
590 fputs("<pre>\n", fp);
591 writeblobhtml(fp, (git_blob *)obj_license);
592 git_object_free(obj_license);
595 fputs("</pre>\n", fp);
603 fp = efopen("readme.html", "w+b");
605 fputs("<pre>\n", fp);
606 writeblobhtml(fp, (git_blob *)obj_readme);
607 git_object_free(obj_readme);
610 fputs("</pre>\n", fp);
615 fp = efopen("log.html", "w+b");
621 fp = efopen("files.html", "w+b");
628 fp = efopen("atom.xml", "w+b");
633 git_repository_free(repo);
634 git_libgit2_shutdown();