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<center>\n", fp);
120 fprintf(fp, "<h1><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /> %s <span class=\"desc\">%s</span></h1>\n",
121 relpath, name, description);
122 fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
123 fprintf(fp, "<a href=\"%sfiles.html\">Files</a>", relpath);
125 fprintf(fp, " | <a href=\"%sreadme.html\">README</a>", relpath);
127 fprintf(fp, " | <a href=\"%slicense.html\">LICENSE</a>", relpath);
128 fputs("\n</center>\n<hr/>\n<pre>", fp);
134 writefooter(FILE *fp)
136 return !fputs("</pre>\n</body>\n</html>", fp);
140 efopen(const char *name, const char *flags)
144 if (!(fp = fopen(name, flags)))
150 /* Escape characters below as HTML 2.0 / XML 1.0. */
152 xmlencode(FILE *fp, const char *s, size_t len)
156 for (i = 0; *s && i < len; s++, i++) {
158 case '<': fputs("<", fp); break;
159 case '>': fputs(">", fp); break;
160 case '\'': fputs("'", fp); break;
161 case '&': fputs("&", fp); break;
162 case '"': fputs(""", fp); break;
163 default: fputc(*s, fp);
168 /* Some implementations of basename(3) return a pointer to a static
169 * internal buffer (OpenBSD). Others modify the contents of `path` (POSIX).
170 * This is a wrapper function that is compatible with both versions.
171 * The program will error out if basename(3) failed, this can only happen
172 * with the OpenBSD version. */
174 xbasename(const char *path)
178 if (!(p = strdup(path)))
180 if (!(b = basename(p)))
182 if (!(b = strdup(b)))
190 printtimeformat(FILE *fp, const git_time *intime, const char *fmt)
196 t = (time_t) intime->time + (intime->offset * 60);
198 strftime(out, sizeof(out), fmt, intm);
203 printtimez(FILE *fp, const git_time *intime)
205 printtimeformat(fp, intime, "%Y-%m-%dT%H:%M:%SZ");
209 printtime(FILE *fp, const git_time *intime)
211 printtimeformat(fp, intime, "%a %b %e %T %Y");
215 printtimeshort(FILE *fp, const git_time *intime)
217 printtimeformat(fp, intime, "%Y-%m-%d %H:%M");
221 writeblobhtml(FILE *fp, const git_blob *blob)
223 xmlencode(fp, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob));
227 printcommit(FILE *fp, struct commitinfo *ci)
229 /* TODO: show tag when commit has it */
230 fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
231 relpath, ci->oid, ci->oid);
233 if (ci->parentoid[0])
234 fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>\n",
235 relpath, ci->parentoid, ci->parentoid);
238 if ((count = (int)git_commit_parentcount(commit)) > 1) {
239 fprintf(fp, "<b>Merge:</b>");
240 for (i = 0; i < count; i++) {
241 git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
242 fprintf(fp, " <a href=\"%scommit/%s.html\">%s</a>",
249 fprintf(fp, "<b>Author:</b> ");
250 xmlencode(fp, ci->author->name, strlen(ci->author->name));
251 fprintf(fp, " <<a href=\"mailto:");
252 xmlencode(fp, ci->author->email, strlen(ci->author->email));
254 xmlencode(fp, ci->author->email, strlen(ci->author->email));
255 fputs("</a>>\n<b>Date:</b> ", fp);
256 printtime(fp, &(ci->author->when));
262 xmlencode(fp, ci->msg, strlen(ci->msg));
268 printshowfile(struct commitinfo *ci)
270 const git_diff_delta *delta;
271 const git_diff_hunk *hunk;
272 const git_diff_line *line;
275 size_t ndeltas, nhunks, nhunklines;
280 snprintf(path, sizeof(path), "commit/%s.html", ci->oid);
281 /* check if file exists if so skip it */
282 if (!access(path, F_OK))
285 fp = efopen(path, "w+b");
289 memset(&statsbuf, 0, sizeof(statsbuf));
293 if (!git_diff_stats_to_buf(&statsbuf, ci->stats,
294 GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_SHORT, 80)) {
295 if (statsbuf.ptr && statsbuf.ptr[0]) {
296 fprintf(fp, "<b>Diffstat:</b>\n");
297 fputs(statsbuf.ptr, fp);
304 ndeltas = git_diff_num_deltas(ci->diff);
305 for (i = 0; i < ndeltas; i++) {
306 if (git_patch_from_diff(&patch, ci->diff, i)) {
307 git_patch_free(patch);
308 break; /* TODO: handle error */
311 delta = git_patch_get_delta(patch);
312 fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s\">%s</a> b/<a href=\"%sfile/%s\">%s</a></b>\n",
313 relpath, delta->old_file.path, delta->old_file.path,
314 relpath, delta->new_file.path, delta->new_file.path);
316 /* check binary data */
317 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
318 fputs("Binary files differ\n", fp);
319 git_patch_free(patch);
323 nhunks = git_patch_num_hunks(patch);
324 for (j = 0; j < nhunks; j++) {
325 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
326 break; /* TODO: handle error ? */
328 fprintf(fp, "<span class=\"h\">%s</span>\n", hunk->header);
331 if (git_patch_get_line_in_hunk(&line, patch, j, k))
333 if (line->old_lineno == -1)
334 fprintf(fp, "<span class=\"i\"><a href=\"#h%zu-%zu\" id=\"h%zu-%zu\">+",
336 else if (line->new_lineno == -1)
337 fprintf(fp, "<span class=\"d\"><a href=\"#h%zu-%zu\" id=\"h%zu-%zu\">-",
341 xmlencode(fp, line->content, line->content_len);
342 if (line->old_lineno == -1 || line->new_lineno == -1)
343 fputs("</a></span>", fp);
346 git_patch_free(patch);
348 git_buf_free(&statsbuf);
358 struct commitinfo *ci;
359 git_revwalk *w = NULL;
364 mkdir("commit", 0755);
366 git_revwalk_new(&w, repo);
367 git_revwalk_push_head(w);
369 /* TODO: also make "expanded" log ? (with message body) */
370 fputs("<table><thead>\n<tr><td align=\"right\">Age</td><td>Commit message</td><td>Author</td>"
371 "<td align=\"right\">Files</td><td align=\"right\">+</td><td align=\"right\">-</td></tr>\n</thead><tbody>\n", fp);
372 while (!git_revwalk_next(&id, w)) {
375 if (!(ci = commitinfo_getbyoid(&id)))
378 fputs("<tr><td align=\"right\">", fp);
380 printtimeshort(fp, &(ci->author->when));
381 fputs("</td><td>", fp);
383 fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
384 if ((len = strlen(ci->summary)) > 79) {
385 xmlencode(fp, ci->summary, 76);
388 xmlencode(fp, ci->summary, len);
392 fputs("</td><td>", fp);
394 xmlencode(fp, ci->author->name, strlen(ci->author->name));
395 fputs("</td><td align=\"right\">", fp);
396 fprintf(fp, "%zu", ci->filecount);
397 fputs("</td><td align=\"right\">", fp);
398 fprintf(fp, "+%zu", ci->addcount);
399 fputs("</td><td align=\"right\">", fp);
400 fprintf(fp, "-%zu", ci->delcount);
401 fputs("</td></tr>\n", fp);
408 fprintf(fp, "</tbody></table>");
417 printcommitatom(FILE *fp, struct commitinfo *ci)
419 fputs("<entry>\n", fp);
421 fprintf(fp, "<id>%s</id>\n", ci->oid);
423 fputs("<updated>", fp);
424 printtimez(fp, &(ci->author->when));
425 fputs("</updated>\n", fp);
428 fputs("<title type=\"text\">", fp);
429 xmlencode(fp, ci->summary, strlen(ci->summary));
430 fputs("</title>\n", fp);
433 fputs("<content type=\"text\">", fp);
434 fprintf(fp, "commit %s\n", ci->oid);
435 if (ci->parentoid[0])
436 fprintf(fp, "parent %s\n", ci->parentoid);
439 if ((count = (int)git_commit_parentcount(commit)) > 1) {
440 fprintf(fp, "Merge:");
441 for (i = 0; i < count; i++) {
442 git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
443 fprintf(fp, " %s", buf);
450 fprintf(fp, "Author: ");
451 xmlencode(fp, ci->author->name, strlen(ci->author->name));
452 fprintf(fp, " <");
453 xmlencode(fp, ci->author->email, strlen(ci->author->email));
454 fprintf(fp, ">\nDate: ");
455 printtime(fp, &(ci->author->when));
460 xmlencode(fp, ci->msg, strlen(ci->msg));
461 fputs("\n</content>\n", fp);
463 fputs("<author><name>", fp);
464 xmlencode(fp, ci->author->name, strlen(ci->author->name));
465 fputs("</name>\n<email>", fp);
466 xmlencode(fp, ci->author->email, strlen(ci->author->email));
467 fputs("</email>\n</author>\n", fp);
469 fputs("</entry>\n", fp);
475 struct commitinfo *ci;
476 git_revwalk *w = NULL;
478 size_t i, m = 100; /* max */
480 fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", fp);
481 fputs("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
482 xmlencode(fp, name, strlen(name));
483 fputs(", branch master</title>\n<subtitle>", fp);
485 xmlencode(fp, description, strlen(description));
486 fputs("</subtitle>\n", fp);
488 git_revwalk_new(&w, repo);
489 git_revwalk_push_head(w);
491 for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
492 if (!(ci = commitinfo_getbyoid(&id)))
494 printcommitatom(fp, ci);
499 fputs("</feed>", fp);
507 const git_index_entry *entry;
511 fputs("<table><thead>\n"
512 "<tr><td>Mode</td><td>Name</td><td align=\"right\">Size</td></tr>\n"
513 "</thead><tbody>\n", fp);
515 git_repository_index(&index, repo);
516 count = git_index_entrycount(index);
518 for (i = 0; i < count; i++) {
519 entry = git_index_get_byindex(index, i);
520 fputs("<tr><td>", fp);
521 fprintf(fp, "%u", entry->mode); /* TODO: fancy print, like: "-rw-r--r--" */
522 fprintf(fp, "</td><td><a href=\"%sfile/", relpath);
523 xmlencode(fp, entry->path, strlen(entry->path));
525 xmlencode(fp, entry->path, strlen(entry->path));
526 fputs("</a></td><td align=\"right\">", fp);
527 fprintf(fp, "%" PRIu64, entry->file_size);
528 fputs("</td></tr>\n", fp);
531 fputs("</tbody></table>", fp);
537 main(int argc, char *argv[])
539 git_object *obj = NULL;
540 const git_error *e = NULL;
542 char path[PATH_MAX], *p;
546 fprintf(stderr, "%s <repodir>\n", argv[0]);
553 if ((status = git_repository_open(&repo, repodir)) < 0) {
555 fprintf(stderr, "error %d/%d: %s\n", status, e->klass, e->message);
559 /* use directory name as name */
560 p = xbasename(repodir);
561 snprintf(name, sizeof(name), "%s", p);
564 /* read description or .git/description */
565 snprintf(path, sizeof(path), "%s%s%s",
566 repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "description");
567 if (!(fpread = fopen(path, "r+b"))) {
568 snprintf(path, sizeof(path), "%s%s%s",
569 repodir, repodir[strlen(repodir)] == '/' ? "" : "/", ".git/description");
570 fpread = fopen(path, "r+b");
573 if (!fgets(description, sizeof(description), fpread))
574 description[0] = '\0';
579 haslicense = !git_revparse_single(&obj, repo, "HEAD:LICENSE");
581 hasreadme = !git_revparse_single(&obj, repo, "HEAD:README");
584 if (!git_revparse_single(&obj, repo, "HEAD:LICENSE")) {
585 fp = efopen("license.html", "w+b");
587 writeblobhtml(fp, (git_blob *)obj);
596 if (!git_revparse_single(&obj, repo, "HEAD:README")) {
597 fp = efopen("readme.html", "w+b");
599 writeblobhtml(fp, (git_blob *)obj);
606 fp = efopen("log.html", "w+b");
612 fp = efopen("files.html", "w+b");
619 fp = efopen("atom.xml", "w+b");
624 git_repository_free(repo);
625 git_libgit2_shutdown();