add stagit.c - stagit-gopher - A git gopher frontend. (mirror) | |
git clone git://bitreich.org/stagit-gopher/ git://enlrupgkhuxnvlhsf6lc3fziv5h2h… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
LICENSE | |
--- | |
commit ef3f1ef854936fc254da5375f69f58dad4e69ca6 | |
parent 81dd454368b40eabf62fede213626d45512150a2 | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Sat, 26 Dec 2015 21:07:47 +0100 | |
add stagit.c | |
Diffstat: | |
A stagit.c | 852 +++++++++++++++++++++++++++++… | |
1 file changed, 852 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/stagit.c b/stagit.c | |
@@ -0,0 +1,852 @@ | |
+#include <sys/stat.h> | |
+ | |
+#include <err.h> | |
+#include <errno.h> | |
+#include <inttypes.h> | |
+#include <libgen.h> | |
+#include <limits.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+#include <unistd.h> | |
+ | |
+#include <git2.h> | |
+ | |
+#include "compat.h" | |
+#include "config.h" | |
+ | |
+struct commitinfo { | |
+ const git_oid *id; | |
+ | |
+ char oid[GIT_OID_HEXSZ + 1]; | |
+ char parentoid[GIT_OID_HEXSZ + 1]; | |
+ | |
+ const git_signature *author; | |
+ const char *summary; | |
+ const char *msg; | |
+ | |
+ git_diff_stats *stats; | |
+ git_diff *diff; | |
+ git_commit *commit; | |
+ git_commit *parent; | |
+ git_tree *commit_tree; | |
+ git_tree *parent_tree; | |
+ | |
+ size_t addcount; | |
+ size_t delcount; | |
+ size_t filecount; | |
+}; | |
+ | |
+static git_repository *repo; | |
+ | |
+static const char *relpath = ""; | |
+static const char *repodir; | |
+ | |
+static char name[255]; | |
+static char description[255]; | |
+static char cloneurl[1024]; | |
+static int hasreadme, haslicense; | |
+ | |
+void | |
+commitinfo_free(struct commitinfo *ci) | |
+{ | |
+ if (!ci) | |
+ return; | |
+ | |
+ git_diff_stats_free(ci->stats); | |
+ git_diff_free(ci->diff); | |
+ git_tree_free(ci->commit_tree); | |
+ git_tree_free(ci->parent_tree); | |
+ git_commit_free(ci->commit); | |
+} | |
+ | |
+struct commitinfo * | |
+commitinfo_getbyoid(const git_oid *id) | |
+{ | |
+ struct commitinfo *ci; | |
+ git_diff_options opts; | |
+ int error; | |
+ | |
+ if (!(ci = calloc(1, sizeof(struct commitinfo)))) | |
+ err(1, "calloc"); | |
+ | |
+ ci->id = id; | |
+ if (git_commit_lookup(&(ci->commit), repo, id)) | |
+ goto err; | |
+ | |
+ git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit)); | |
+ git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_… | |
+ | |
+ ci->author = git_commit_author(ci->commit); | |
+ ci->summary = git_commit_summary(ci->commit); | |
+ ci->msg = git_commit_message(ci->commit); | |
+ | |
+ if ((error = git_commit_tree(&(ci->commit_tree), ci->commit))) | |
+ goto err; | |
+ if (!(error = git_commit_parent(&(ci->parent), ci->commit, 0))) { | |
+ if ((error = git_commit_tree(&(ci->parent_tree), ci->parent))) | |
+ goto err; | |
+ } else { | |
+ ci->parent = NULL; | |
+ ci->parent_tree = NULL; | |
+ } | |
+ | |
+ git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION); | |
+ opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; | |
+ if ((error = git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree,… | |
+ goto err; | |
+ if (git_diff_get_stats(&(ci->stats), ci->diff)) | |
+ goto err; | |
+ | |
+ ci->addcount = git_diff_stats_insertions(ci->stats); | |
+ ci->delcount = git_diff_stats_deletions(ci->stats); | |
+ ci->filecount = git_diff_stats_files_changed(ci->stats); | |
+ | |
+ return ci; | |
+ | |
+err: | |
+ commitinfo_free(ci); | |
+ free(ci); | |
+ | |
+ return NULL; | |
+} | |
+ | |
+FILE * | |
+efopen(const char *name, const char *flags) | |
+{ | |
+ FILE *fp; | |
+ | |
+ if (!(fp = fopen(name, flags))) | |
+ err(1, "fopen"); | |
+ | |
+ return fp; | |
+} | |
+ | |
+/* Escape characters below as HTML 2.0 / XML 1.0. */ | |
+void | |
+xmlencode(FILE *fp, const char *s, size_t len) | |
+{ | |
+ size_t i; | |
+ | |
+ for (i = 0; *s && i < len; s++, i++) { | |
+ switch(*s) { | |
+ case '<': fputs("<", fp); break; | |
+ case '>': fputs(">", fp); break; | |
+ case '\'': fputs("'", fp); break; | |
+ case '&': fputs("&", fp); break; | |
+ case '"': fputs(""", fp); break; | |
+ default: fputc(*s, fp); | |
+ } | |
+ } | |
+} | |
+ | |
+/* Some implementations of dirname(3) return a pointer to a static | |
+ * internal buffer (OpenBSD). Others modify the contents of `path` (POSIX). | |
+ * This is a wrapper function that is compatible with both versions. | |
+ * The program will error out if dirname(3) failed, this can only happen | |
+ * with the OpenBSD version. */ | |
+char * | |
+xdirname(const char *path) | |
+{ | |
+ char *p, *b; | |
+ | |
+ if (!(p = strdup(path))) | |
+ err(1, "strdup"); | |
+ if (!(b = dirname(p))) | |
+ err(1, "basename"); | |
+ if (!(b = strdup(b))) | |
+ err(1, "strdup"); | |
+ free(p); | |
+ | |
+ return b; | |
+} | |
+ | |
+/* Some implementations of basename(3) return a pointer to a static | |
+ * internal buffer (OpenBSD). Others modify the contents of `path` (POSIX). | |
+ * This is a wrapper function that is compatible with both versions. | |
+ * The program will error out if basename(3) failed, this can only happen | |
+ * with the OpenBSD version. */ | |
+char * | |
+xbasename(const char *path) | |
+{ | |
+ char *p, *b; | |
+ | |
+ if (!(p = strdup(path))) | |
+ err(1, "strdup"); | |
+ if (!(b = basename(p))) | |
+ err(1, "basename"); | |
+ if (!(b = strdup(b))) | |
+ err(1, "strdup"); | |
+ free(p); | |
+ | |
+ return b; | |
+} | |
+ | |
+int | |
+mkdirp(const char *path) | |
+{ | |
+ char tmp[PATH_MAX], *p; | |
+ | |
+ strlcpy(tmp, path, sizeof(tmp)); | |
+ for (p = tmp + (tmp[0] == '/'); *p; p++) { | |
+ if (*p != '/') | |
+ continue; | |
+ *p = '\0'; | |
+ if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EE… | |
+ return -1; | |
+ *p = '/'; | |
+ } | |
+ if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST) | |
+ return -1; | |
+ return 0; | |
+} | |
+ | |
+void | |
+printtimeformat(FILE *fp, const git_time *intime, const char *fmt) | |
+{ | |
+ struct tm *intm; | |
+ time_t t; | |
+ char out[32]; | |
+ | |
+ t = (time_t) intime->time + (intime->offset * 60); | |
+ intm = gmtime(&t); | |
+ strftime(out, sizeof(out), fmt, intm); | |
+ fputs(out, fp); | |
+} | |
+ | |
+void | |
+printtimez(FILE *fp, const git_time *intime) | |
+{ | |
+ printtimeformat(fp, intime, "%Y-%m-%dT%H:%M:%SZ"); | |
+} | |
+ | |
+void | |
+printtime(FILE *fp, const git_time *intime) | |
+{ | |
+ printtimeformat(fp, intime, "%a %b %e %T %Y"); | |
+} | |
+ | |
+void | |
+printtimeshort(FILE *fp, const git_time *intime) | |
+{ | |
+ printtimeformat(fp, intime, "%Y-%m-%d %H:%M"); | |
+} | |
+ | |
+int | |
+writeheader(FILE *fp) | |
+{ | |
+ fputs("<!DOCTYPE HTML>" | |
+ "<html dir=\"ltr\" lang=\"en\">\n<head>\n" | |
+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charse… | |
+ "<meta http-equiv=\"Content-Language\" content=\"en\" />\n<tit… | |
+ xmlencode(fp, name, strlen(name)); | |
+ if (description[0]) | |
+ fputs(" - ", fp); | |
+ xmlencode(fp, description, strlen(description)); | |
+ fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%s… | |
+ fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" tit… | |
+ name, relpath); | |
+ fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle… | |
+ fputs("</head>\n<body>\n<table><tr><td>", fp); | |
+ fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=… | |
+ relpath, relpath); | |
+ fputs("</td><td><h1>", fp); | |
+ xmlencode(fp, name, strlen(name)); | |
+ fputs("</h1><span class=\"desc\">", fp); | |
+ xmlencode(fp, description, strlen(description)); | |
+ fputs("</span></td></tr>", fp); | |
+ if (cloneurl[0]) { | |
+ fputs("<tr class=\"url\"><td></td><td>git clone <a href=\"", f… | |
+ xmlencode(fp, cloneurl, strlen(cloneurl)); | |
+ fputs("\">", fp); | |
+ xmlencode(fp, cloneurl, strlen(cloneurl)); | |
+ fputs("</a></td></tr>", fp); | |
+ } | |
+ fputs("<tr><td></td><td>\n", fp); | |
+ fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath); | |
+ fprintf(fp, "<a href=\"%sfiles.html\">Files</a>", relpath); | |
+ if (hasreadme) | |
+ fprintf(fp, " | <a href=\"%sfile/README.html\">README</a>", re… | |
+ if (haslicense) | |
+ fprintf(fp, " | <a href=\"%sfile/LICENSE.html\">LICENSE</a>", … | |
+ fputs("</td></tr></table>\n<hr/><div id=\"content\">\n", fp); | |
+ | |
+ return 0; | |
+} | |
+ | |
+int | |
+writefooter(FILE *fp) | |
+{ | |
+ return !fputs("</div></body>\n</html>", fp); | |
+} | |
+ | |
+void | |
+writeblobhtml(FILE *fp, const git_blob *blob) | |
+{ | |
+ off_t i = 0; | |
+ size_t n = 1; | |
+ char *nfmt = "<a href=\"#l%d\" id=\"l%d\">%d</a>\n"; | |
+ const char *s = git_blob_rawcontent(blob); | |
+ git_off_t len = git_blob_rawsize(blob); | |
+ | |
+ fputs("<table id=\"blob\"><tr><td class=\"num\"><pre>\n", fp); | |
+ | |
+ if (len) { | |
+ fprintf(fp, nfmt, n, n, n); | |
+ while (i < len - 1) { | |
+ if (s[i] == '\n') { | |
+ n++; | |
+ fprintf(fp, nfmt, n, n, n); | |
+ } | |
+ i++; | |
+ } | |
+ } | |
+ | |
+ fputs("</pre></td><td><pre>\n", fp); | |
+ xmlencode(fp, s, (size_t)len); | |
+ fputs("</pre></td></tr></table>\n", fp); | |
+} | |
+ | |
+void | |
+printcommit(FILE *fp, struct commitinfo *ci) | |
+{ | |
+ fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n", | |
+ relpath, ci->oid, ci->oid); | |
+ | |
+ if (ci->parentoid[0]) | |
+ fprintf(fp, "<b>parent</b> <a href=\"%scommit/%s.html\">%s</a>… | |
+ relpath, ci->parentoid, ci->parentoid); | |
+ | |
+#if 0 | |
+ if ((count = (int)git_commit_parentcount(commit)) > 1) { | |
+ fprintf(fp, "<b>Merge:</b>"); | |
+ for (i = 0; i < count; i++) { | |
+ git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); | |
+ fprintf(fp, " <a href=\"%scommit/%s.html\">%s</a>", | |
+ relpath, buf, buf); | |
+ } | |
+ fputc('\n', fp); | |
+ } | |
+#endif | |
+ if (ci->author) { | |
+ fprintf(fp, "<b>Author:</b> "); | |
+ xmlencode(fp, ci->author->name, strlen(ci->author->name)); | |
+ fprintf(fp, " <<a href=\"mailto:"); | |
+ xmlencode(fp, ci->author->email, strlen(ci->author->email)); | |
+ fputs("\">", fp); | |
+ xmlencode(fp, ci->author->email, strlen(ci->author->email)); | |
+ fputs("</a>>\n<b>Date:</b> ", fp); | |
+ printtime(fp, &(ci->author->when)); | |
+ fputc('\n', fp); | |
+ } | |
+ fputc('\n', fp); | |
+ | |
+ if (ci->msg) | |
+ xmlencode(fp, ci->msg, strlen(ci->msg)); | |
+ | |
+ fputc('\n', fp); | |
+} | |
+ | |
+void | |
+printshowfile(struct commitinfo *ci) | |
+{ | |
+ const git_diff_delta *delta; | |
+ const git_diff_hunk *hunk; | |
+ const git_diff_line *line; | |
+ git_patch *patch; | |
+ git_buf statsbuf; | |
+ size_t ndeltas, nhunks, nhunklines; | |
+ FILE *fp; | |
+ size_t i, j, k; | |
+ char path[PATH_MAX]; | |
+ | |
+ snprintf(path, sizeof(path), "commit/%s.html", ci->oid); | |
+ /* check if file exists if so skip it */ | |
+ if (!access(path, F_OK)) | |
+ return; | |
+ | |
+ fp = efopen(path, "w"); | |
+ writeheader(fp); | |
+ fputs("<pre>\n", fp); | |
+ printcommit(fp, ci); | |
+ | |
+ memset(&statsbuf, 0, sizeof(statsbuf)); | |
+ | |
+ /* diff stat */ | |
+ if (ci->stats) { | |
+ if (!git_diff_stats_to_buf(&statsbuf, ci->stats, | |
+ GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_SHORT, 80)) { | |
+ if (statsbuf.ptr && statsbuf.ptr[0]) { | |
+ fprintf(fp, "<b>Diffstat:</b>\n"); | |
+ fputs(statsbuf.ptr, fp); | |
+ } | |
+ } | |
+ } | |
+ | |
+ fputs("<hr/>", fp); | |
+ | |
+ ndeltas = git_diff_num_deltas(ci->diff); | |
+ for (i = 0; i < ndeltas; i++) { | |
+ if (git_patch_from_diff(&patch, ci->diff, i)) { | |
+ git_patch_free(patch); | |
+ break; | |
+ } | |
+ | |
+ delta = git_patch_get_delta(patch); | |
+ fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s.html\">%s</a>… | |
+ relpath, delta->old_file.path, delta->old_file.path, | |
+ relpath, delta->new_file.path, delta->new_file.path); | |
+ | |
+ /* check binary data */ | |
+ if (delta->flags & GIT_DIFF_FLAG_BINARY) { | |
+ fputs("Binary files differ\n", fp); | |
+ git_patch_free(patch); | |
+ continue; | |
+ } | |
+ | |
+ nhunks = git_patch_num_hunks(patch); | |
+ for (j = 0; j < nhunks; j++) { | |
+ if (git_patch_get_hunk(&hunk, &nhunklines, patch, j)) | |
+ break; | |
+ | |
+ fprintf(fp, "<span class=\"h\">%s</span>\n", hunk->hea… | |
+ | |
+ for (k = 0; ; k++) { | |
+ if (git_patch_get_line_in_hunk(&line, patch, j… | |
+ break; | |
+ if (line->old_lineno == -1) | |
+ fprintf(fp, "<a href=\"#h%zu-%zu\" id=… | |
+ j, k, j, k); | |
+ else if (line->new_lineno == -1) | |
+ fprintf(fp, "<a href=\"#h%zu-%zu\" id=… | |
+ j, k, j, k); | |
+ else | |
+ fputc(' ', fp); | |
+ xmlencode(fp, line->content, line->content_len… | |
+ if (line->old_lineno == -1 || line->new_lineno… | |
+ fputs("</a>", fp); | |
+ } | |
+ } | |
+ git_patch_free(patch); | |
+ } | |
+ git_buf_free(&statsbuf); | |
+ | |
+ fputs( "</pre>\n", fp); | |
+ writefooter(fp); | |
+ fclose(fp); | |
+ return; | |
+} | |
+ | |
+void | |
+writelog(FILE *fp) | |
+{ | |
+ struct commitinfo *ci; | |
+ git_revwalk *w = NULL; | |
+ git_oid id; | |
+ size_t len; | |
+ | |
+ mkdir("commit", 0755); | |
+ | |
+ git_revwalk_new(&w, repo); | |
+ git_revwalk_push_head(w); | |
+ git_revwalk_sorting(w, GIT_SORT_TIME); | |
+ git_revwalk_simplify_first_parent(w); | |
+ | |
+ fputs("<table id=\"log\"><thead>\n<tr><td>Age</td><td>Commit message</… | |
+ "<td>Author</td><td>Files</td><td class=\"num\">+</td>" | |
+ "<td class=\"num\">-</td></tr>\n</thead><tbody>\n", fp); | |
+ while (!git_revwalk_next(&id, w)) { | |
+ relpath = ""; | |
+ | |
+ if (!(ci = commitinfo_getbyoid(&id))) | |
+ break; | |
+ | |
+ fputs("<tr><td>", fp); | |
+ if (ci->author) | |
+ printtimeshort(fp, &(ci->author->when)); | |
+ fputs("</td><td>", fp); | |
+ if (ci->summary) { | |
+ fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, … | |
+ if ((len = strlen(ci->summary)) > summarylen) { | |
+ xmlencode(fp, ci->summary, summarylen - 1); | |
+ fputs("…", fp); | |
+ } else { | |
+ xmlencode(fp, ci->summary, len); | |
+ } | |
+ fputs("</a>", fp); | |
+ } | |
+ fputs("</td><td>", fp); | |
+ if (ci->author) | |
+ xmlencode(fp, ci->author->name, strlen(ci->author->nam… | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "%zu", ci->filecount); | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "+%zu", ci->addcount); | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "-%zu", ci->delcount); | |
+ fputs("</td></tr>\n", fp); | |
+ | |
+ relpath = "../"; | |
+ printshowfile(ci); | |
+ | |
+ commitinfo_free(ci); | |
+ } | |
+ fprintf(fp, "</tbody></table>"); | |
+ | |
+ git_revwalk_free(w); | |
+ relpath = ""; | |
+} | |
+ | |
+void | |
+printcommitatom(FILE *fp, struct commitinfo *ci) | |
+{ | |
+ fputs("<entry>\n", fp); | |
+ | |
+ fprintf(fp, "<id>%s</id>\n", ci->oid); | |
+ if (ci->author) { | |
+ fputs("<updated>", fp); | |
+ printtimez(fp, &(ci->author->when)); | |
+ fputs("</updated>\n", fp); | |
+ } | |
+ if (ci->summary) { | |
+ fputs("<title type=\"text\">", fp); | |
+ xmlencode(fp, ci->summary, strlen(ci->summary)); | |
+ fputs("</title>\n", fp); | |
+ } | |
+ | |
+ fputs("<content type=\"text\">", fp); | |
+ fprintf(fp, "commit %s\n", ci->oid); | |
+ if (ci->parentoid[0]) | |
+ fprintf(fp, "parent %s\n", ci->parentoid); | |
+ | |
+#if 0 | |
+ if ((count = (int)git_commit_parentcount(commit)) > 1) { | |
+ fprintf(fp, "Merge:"); | |
+ for (i = 0; i < count; i++) { | |
+ git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); | |
+ fprintf(fp, " %s", buf); | |
+ } | |
+ fputc('\n', fp); | |
+ } | |
+#endif | |
+ | |
+ if (ci->author) { | |
+ fprintf(fp, "Author: "); | |
+ xmlencode(fp, ci->author->name, strlen(ci->author->name)); | |
+ fprintf(fp, " <"); | |
+ xmlencode(fp, ci->author->email, strlen(ci->author->email)); | |
+ fprintf(fp, ">\nDate: "); | |
+ printtime(fp, &(ci->author->when)); | |
+ } | |
+ fputc('\n', fp); | |
+ | |
+ if (ci->msg) | |
+ xmlencode(fp, ci->msg, strlen(ci->msg)); | |
+ fputs("\n</content>\n", fp); | |
+ if (ci->author) { | |
+ fputs("<author><name>", fp); | |
+ xmlencode(fp, ci->author->name, strlen(ci->author->name)); | |
+ fputs("</name>\n<email>", fp); | |
+ xmlencode(fp, ci->author->email, strlen(ci->author->email)); | |
+ fputs("</email>\n</author>\n", fp); | |
+ } | |
+ fputs("</entry>\n", fp); | |
+} | |
+ | |
+int | |
+writeatom(FILE *fp) | |
+{ | |
+ struct commitinfo *ci; | |
+ git_revwalk *w = NULL; | |
+ git_oid id; | |
+ size_t i, m = 100; /* max */ | |
+ | |
+ fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | |
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp); | |
+ xmlencode(fp, name, strlen(name)); | |
+ fputs(", branch master</title>\n<subtitle>", fp); | |
+ | |
+ xmlencode(fp, description, strlen(description)); | |
+ fputs("</subtitle>\n", fp); | |
+ | |
+ git_revwalk_new(&w, repo); | |
+ git_revwalk_push_head(w); | |
+ git_revwalk_sorting(w, GIT_SORT_TIME); | |
+ git_revwalk_simplify_first_parent(w); | |
+ | |
+ for (i = 0; i < m && !git_revwalk_next(&id, w); i++) { | |
+ if (!(ci = commitinfo_getbyoid(&id))) | |
+ break; | |
+ printcommitatom(fp, ci); | |
+ commitinfo_free(ci); | |
+ } | |
+ git_revwalk_free(w); | |
+ | |
+ fputs("</feed>", fp); | |
+ | |
+ return 0; | |
+} | |
+ | |
+int | |
+writeblob(git_object *obj, const char *filename, git_off_t filesize) | |
+{ | |
+ char fpath[PATH_MAX]; | |
+ char tmp[PATH_MAX] = ""; | |
+ char *d, *p; | |
+ FILE *fp; | |
+ | |
+ snprintf(fpath, sizeof(fpath), "file/%s.html", filename); | |
+ d = xdirname(fpath); | |
+ if (mkdirp(d)) { | |
+ free(d); | |
+ return 1; | |
+ } | |
+ free(d); | |
+ | |
+ p = fpath; | |
+ while (*p) { | |
+ if (*p == '/') | |
+ strlcat(tmp, "../", sizeof(tmp)); | |
+ p++; | |
+ } | |
+ relpath = tmp; | |
+ | |
+ fp = efopen(fpath, "w"); | |
+ writeheader(fp); | |
+ fputs("<p> ", fp); | |
+ xmlencode(fp, filename, strlen(filename)); | |
+ fprintf(fp, " (%" PRIu32 "b)", filesize); | |
+ fputs("</p><hr/>", fp); | |
+ | |
+ if (git_blob_is_binary((git_blob *)obj)) { | |
+ fprintf(fp, "<p>Binary file</p>\n"); | |
+ } else { | |
+ writeblobhtml(fp, (git_blob *)obj); | |
+ if (ferror(fp)) | |
+ err(1, "fwrite"); | |
+ } | |
+ writefooter(fp); | |
+ fclose(fp); | |
+ | |
+ relpath = ""; | |
+ | |
+ return 0; | |
+} | |
+ | |
+const char * | |
+filemode(git_filemode_t m) | |
+{ | |
+ static char mode[11]; | |
+ | |
+ memset(mode, '-', sizeof(mode) - 1); | |
+ mode[10] = '\0'; | |
+ | |
+ if (S_ISREG(m)) | |
+ mode[0] = '-'; | |
+ else if (S_ISBLK(m)) | |
+ mode[0] = 'b'; | |
+ else if (S_ISCHR(m)) | |
+ mode[0] = 'c'; | |
+ else if (S_ISDIR(m)) | |
+ mode[0] = 'd'; | |
+ else if (S_ISFIFO(m)) | |
+ mode[0] = 'p'; | |
+ else if (S_ISLNK(m)) | |
+ mode[0] = 'l'; | |
+ else if (S_ISSOCK(m)) | |
+ mode[0] = 's'; | |
+ else | |
+ mode[0] = '?'; | |
+ | |
+ if (m & S_IRUSR) mode[1] = 'r'; | |
+ if (m & S_IWUSR) mode[2] = 'w'; | |
+ if (m & S_IXUSR) mode[3] = 'x'; | |
+ if (m & S_IRGRP) mode[4] = 'r'; | |
+ if (m & S_IWGRP) mode[5] = 'w'; | |
+ if (m & S_IXGRP) mode[6] = 'x'; | |
+ if (m & S_IROTH) mode[7] = 'r'; | |
+ if (m & S_IWOTH) mode[8] = 'w'; | |
+ if (m & S_IXOTH) mode[9] = 'x'; | |
+ | |
+ if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S'; | |
+ if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S'; | |
+ if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T'; | |
+ | |
+ return mode; | |
+} | |
+ | |
+int | |
+writefilestree(FILE *fp, git_tree *tree, const char *path) | |
+{ | |
+ const git_tree_entry *entry = NULL; | |
+ const char *filename; | |
+ char filepath[PATH_MAX]; | |
+ git_object *obj = NULL; | |
+ git_off_t filesize; | |
+ size_t count, i; | |
+ int ret; | |
+ | |
+ count = git_tree_entrycount(tree); | |
+ for (i = 0; i < count; i++) { | |
+ if (!(entry = git_tree_entry_byindex(tree, i))) | |
+ return -1; | |
+ | |
+ filename = git_tree_entry_name(entry); | |
+ if (git_tree_entry_to_object(&obj, repo, entry)) | |
+ return -1; | |
+ switch (git_object_type(obj)) { | |
+ case GIT_OBJ_BLOB: | |
+ break; | |
+ case GIT_OBJ_TREE: | |
+ ret = writefilestree(fp, (git_tree *)obj, filename); | |
+ git_object_free(obj); | |
+ if (ret) | |
+ return ret; | |
+ continue; | |
+ default: | |
+ git_object_free(obj); | |
+ continue; | |
+ } | |
+ if (path[0]) { | |
+ snprintf(filepath, sizeof(filepath), "%s/%s", path, fi… | |
+ filename = filepath; | |
+ } | |
+ | |
+ filesize = git_blob_rawsize((git_blob *)obj); | |
+ | |
+ fputs("<tr><td>", fp); | |
+ fprintf(fp, "%s", filemode(git_tree_entry_filemode(entry))); | |
+ fprintf(fp, "</td><td><a href=\"%sfile/", relpath); | |
+ xmlencode(fp, filename, strlen(filename)); | |
+ fputs(".html\">", fp); | |
+ xmlencode(fp, filename, strlen(filename)); | |
+ fputs("</a></td><td class=\"num\">", fp); | |
+ fprintf(fp, "%" PRIu32, filesize); | |
+ fputs("</td></tr>\n", fp); | |
+ | |
+ writeblob(obj, filename, filesize); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+int | |
+writefiles(FILE *fp) | |
+{ | |
+ const git_oid *id; | |
+ git_tree *tree = NULL; | |
+ git_object *obj = NULL; | |
+ git_commit *commit = NULL; | |
+ | |
+ fputs("<table id=\"files\"><thead>\n<tr>" | |
+ "<td>Mode</td><td>Name</td><td class=\"num\">Size</td>" | |
+ "</tr>\n</thead><tbody>\n", fp); | |
+ | |
+ if (git_revparse_single(&obj, repo, "HEAD")) | |
+ return -1; | |
+ id = git_object_id(obj); | |
+ if (git_commit_lookup(&commit, repo, id)) | |
+ return -1; | |
+ if (git_commit_tree(&tree, commit)) { | |
+ git_commit_free(commit); | |
+ return -1; | |
+ } | |
+ git_commit_free(commit); | |
+ | |
+ writefilestree(fp, tree, ""); | |
+ | |
+ git_commit_free(commit); | |
+ git_tree_free(tree); | |
+ | |
+ fputs("</tbody></table>", fp); | |
+ | |
+ return 0; | |
+} | |
+ | |
+int | |
+main(int argc, char *argv[]) | |
+{ | |
+ git_object *obj = NULL; | |
+ const git_error *e = NULL; | |
+ FILE *fp, *fpread; | |
+ char path[PATH_MAX], *p; | |
+ int status; | |
+ | |
+ if (argc != 2) { | |
+ fprintf(stderr, "%s <repodir>\n", argv[0]); | |
+ return 1; | |
+ } | |
+ repodir = argv[1]; | |
+ | |
+ git_libgit2_init(); | |
+ | |
+ if ((status = git_repository_open_ext(&repo, repodir, | |
+ GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) < 0) { | |
+ e = giterr_last(); | |
+ fprintf(stderr, "error %d/%d: %s\n", status, e->klass, e->mess… | |
+ return status; | |
+ } | |
+ | |
+ /* use directory name as name */ | |
+ p = xbasename(repodir); | |
+ snprintf(name, sizeof(name), "%s", p); | |
+ free(p); | |
+ | |
+ /* read description or .git/description */ | |
+ snprintf(path, sizeof(path), "%s%s%s", | |
+ repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "descript… | |
+ if (!(fpread = fopen(path, "r"))) { | |
+ snprintf(path, sizeof(path), "%s%s%s", | |
+ repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "… | |
+ fpread = fopen(path, "r"); | |
+ } | |
+ if (fpread) { | |
+ if (!fgets(description, sizeof(description), fpread)) | |
+ description[0] = '\0'; | |
+ fclose(fpread); | |
+ } | |
+ | |
+ /* read url or .git/url */ | |
+ snprintf(path, sizeof(path), "%s%s%s", | |
+ repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "url"); | |
+ if (!(fpread = fopen(path, "r"))) { | |
+ snprintf(path, sizeof(path), "%s%s%s", | |
+ repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "… | |
+ fpread = fopen(path, "r"); | |
+ } | |
+ if (fpread) { | |
+ if (!fgets(cloneurl, sizeof(cloneurl), fpread)) | |
+ cloneurl[0] = '\0'; | |
+ fclose(fpread); | |
+ } | |
+ | |
+ /* check LICENSE */ | |
+ haslicense = !git_revparse_single(&obj, repo, "HEAD:LICENSE"); | |
+ git_object_free(obj); | |
+ /* check README */ | |
+ hasreadme = !git_revparse_single(&obj, repo, "HEAD:README"); | |
+ git_object_free(obj); | |
+ | |
+ fp = efopen("log.html", "w"); | |
+ writeheader(fp); | |
+ writelog(fp); | |
+ writefooter(fp); | |
+ fclose(fp); | |
+ | |
+ fp = efopen("files.html", "w"); | |
+ writeheader(fp); | |
+ writefiles(fp); | |
+ writefooter(fp); | |
+ fclose(fp); | |
+ | |
+ /* Atom feed */ | |
+ fp = efopen("atom.xml", "w"); | |
+ writeatom(fp); | |
+ fclose(fp); | |
+ | |
+ /* cleanup */ | |
+ git_repository_free(repo); | |
+ git_libgit2_shutdown(); | |
+ | |
+ return 0; | |
+} |