#include <string.h>
#include <unistd.h>
+#include <git2.h>
+
+#include "compat.h"
#include "config.h"
-#include "git2.h"
struct commitinfo {
const git_oid *id;
static char name[255];
static char description[255];
+static char cloneurl[1024];
static int hasreadme, haslicense;
void
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);
}
commitinfo_getbyoid(const git_oid *id)
{
struct commitinfo *ci;
+ git_diff_options opts;
int error;
if (!(ci = calloc(1, sizeof(struct commitinfo))))
if (git_commit_lookup(&(ci->commit), repo, id))
goto err;
- /* TODO: show tags when commit has it */
git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
ci->msg = git_commit_message(ci->commit);
if ((error = git_commit_tree(&(ci->commit_tree), ci->commit)))
- goto err; /* TODO: handle error */
+ goto err;
if (!(error = git_commit_parent(&(ci->parent), ci->commit, 0))) {
if ((error = git_commit_tree(&(ci->parent_tree), ci->parent)))
goto err;
ci->parent_tree = NULL;
}
- if ((error = git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_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, ci->commit_tree, &opts)))
goto err;
if (git_diff_get_stats(&(ci->stats), ci->diff))
goto err;
ci->delcount = git_diff_stats_deletions(ci->stats);
ci->filecount = git_diff_stats_files_changed(ci->stats);
- /* TODO: show tag when commit has it */
-
return ci;
err:
return NULL;
}
-int
-writeheader(FILE *fp)
-{
- fputs("<!DOCTYPE HTML>"
- "<html dir=\"ltr\" lang=\"en\">\n<head>\n"
- "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
- "<meta http-equiv=\"Content-Language\" content=\"en\" />\n", fp);
- fprintf(fp, "<title>%s%s%s</title>\n", name, description[0] ? " - " : "", description);
- fprintf(fp, "<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
- fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n",
- name, relpath);
- fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
- fputs("</head>\n<body>\n\n", fp);
- fprintf(fp, "<table><tr><td><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></td>"
- "<td><h1>%s</h1><span class=\"desc\">%s</span></td></tr><tr><td></td><td>\n",
- relpath, name, description);
- 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>", relpath);
- if (haslicense)
- fprintf(fp, " | <a href=\"%sfile/LICENSE.html\">LICENSE</a>", relpath);
- fputs("</td></tr></table>\n<hr/><div id=\"content\">\n", fp);
-
- return 0;
-}
-
-int
-writefooter(FILE *fp)
-{
- return !fputs("</div></body>\n</html>", fp);
-}
-
FILE *
efopen(const char *name, const char *flags)
{
}
}
+/* 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.
{
char tmp[PATH_MAX], *p;
- strlcpy(tmp, path, sizeof(tmp)); /* TODO: bring in libutil? */
+ strlcpy(tmp, path, sizeof(tmp));
for (p = tmp + (tmp[0] == '/'); *p; p++) {
if (*p != '/')
continue;
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; charset=UTF-8\" />\n"
+ "<meta http-equiv=\"Content-Language\" content=\"en\" />\n<title>", fp);
+ 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=\"%sfavicon.png\" />\n", relpath);
+ fprintf(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"%s Atom Feed\" href=\"%satom.xml\" />\n",
+ name, relpath);
+ fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
+ fputs("</head>\n<body>\n<table><tr><td>", fp);
+ fprintf(fp, "<a href=\"../%s\"><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></a>",
+ 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=\"", fp);
+ 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>", relpath);
+ if (haslicense)
+ fprintf(fp, " | <a href=\"%sfile/LICENSE.html\">LICENSE</a>", relpath);
+ 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)
{
void
printcommit(FILE *fp, struct commitinfo *ci)
{
- /* TODO: show tag when commit has it */
fprintf(fp, "<b>commit</b> <a href=\"%scommit/%s.html\">%s</a>\n",
relpath, ci->oid, ci->oid);
if (!access(path, F_OK))
return;
- fp = efopen(path, "w+b");
+ fp = efopen(path, "w");
writeheader(fp);
fputs("<pre>\n", fp);
printcommit(fp, ci);
for (i = 0; i < ndeltas; i++) {
if (git_patch_from_diff(&patch, ci->diff, i)) {
git_patch_free(patch);
- break; /* TODO: handle error */
+ break;
}
delta = git_patch_get_delta(patch);
nhunks = git_patch_num_hunks(patch);
for (j = 0; j < nhunks; j++) {
if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
- break; /* TODO: handle error ? */
+ break;
fprintf(fp, "<span class=\"h\">%s</span>\n", hunk->header);
return;
}
-int
+void
writelog(FILE *fp)
{
struct commitinfo *ci;
git_revwalk *w = NULL;
git_oid id;
size_t len;
- int ret = 0;
mkdir("commit", 0755);
git_revwalk_sorting(w, GIT_SORT_TIME);
git_revwalk_simplify_first_parent(w);
- /* TODO: also make "expanded" log ? (with message body) */
fputs("<table id=\"log\"><thead>\n<tr><td>Age</td><td>Commit message</td>"
"<td>Author</td><td>Files</td><td class=\"num\">+</td>"
"<td class=\"num\">-</td></tr>\n</thead><tbody>\n", fp);
git_revwalk_free(w);
relpath = "";
-
- return ret;
}
void
git_oid id;
size_t i, m = 100; /* max */
- fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", fp);
- fputs("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
+ 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);
}
int
-writeblob(const git_index_entry *entry)
+writeblob(git_object *obj, const char *filename, git_off_t filesize)
{
char fpath[PATH_MAX];
- char ref[PATH_MAX];
char tmp[PATH_MAX] = "";
- char *p;
- git_object *obj = NULL;
+ char *d, *p;
FILE *fp;
- snprintf(fpath, sizeof(fpath), "file/%s.html", entry->path);
- snprintf(ref, sizeof(ref), "HEAD:%s", entry->path);
-
- if (git_revparse_single(&obj, repo, ref))
- return 1;
-
- if (mkdirp(dirname(fpath)))
+ snprintf(fpath, sizeof(fpath), "file/%s.html", filename);
+ d = xdirname(fpath);
+ if (mkdirp(d)) {
+ free(d);
return 1;
+ }
+ free(d);
p = fpath;
while (*p) {
}
relpath = tmp;
- fp = efopen(fpath, "w+b");
+ fp = efopen(fpath, "w");
writeheader(fp);
- fprintf(fp, "<p>%s (%" PRIu32 "b)</p><hr/>", entry->path, entry->file_size);
+ 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 {
if (ferror(fp))
err(1, "fwrite");
}
- git_object_free(obj);
writefooter(fp);
fclose(fp);
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
-writefiles(FILE *fp)
+writefilestree(FILE *fp, git_tree *tree, const char *path)
{
- const git_index_entry *entry;
- git_index *index;
+ 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;
- fputs("<table id=\"files\"><thead>\n"
- "<tr><td>Mode</td><td>Name</td><td>Size</td></tr>\n"
- "</thead><tbody>\n", fp);
+ count = git_tree_entrycount(tree);
+ for (i = 0; i < count; i++) {
+ if (!(entry = git_tree_entry_byindex(tree, i)))
+ return -1;
- git_repository_index(&index, repo);
- count = git_index_entrycount(index);
+ 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, filename);
+ filename = filepath;
+ }
- for (i = 0; i < count; i++) {
- entry = git_index_get_byindex(index, i);
+ filesize = git_blob_rawsize((git_blob *)obj);
fputs("<tr><td>", fp);
- fprintf(fp, "%u", entry->mode); /* TODO: fancy print, like: "-rw-r--r--" */
+ fprintf(fp, "%s", filemode(git_tree_entry_filemode(entry)));
fprintf(fp, "</td><td><a href=\"%sfile/", relpath);
- xmlencode(fp, entry->path, strlen(entry->path));
+ xmlencode(fp, filename, strlen(filename));
fputs(".html\">", fp);
- xmlencode(fp, entry->path, strlen(entry->path));
+ xmlencode(fp, filename, strlen(filename));
fputs("</a></td><td class=\"num\">", fp);
- fprintf(fp, "%" PRIu32, entry->file_size);
+ fprintf(fp, "%" PRIu32, filesize);
fputs("</td></tr>\n", fp);
- writeblob(entry);
+ 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;
/* read description or .git/description */
snprintf(path, sizeof(path), "%s%s%s",
repodir, repodir[strlen(repodir)] == '/' ? "" : "/", "description");
- if (!(fpread = fopen(path, "r+b"))) {
+ if (!(fpread = fopen(path, "r"))) {
snprintf(path, sizeof(path), "%s%s%s",
repodir, repodir[strlen(repodir)] == '/' ? "" : "/", ".git/description");
- fpread = fopen(path, "r+b");
+ fpread = fopen(path, "r");
}
if (fpread) {
if (!fgets(description, sizeof(description), fpread))
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)] == '/' ? "" : "/", ".git/url");
+ 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);
hasreadme = !git_revparse_single(&obj, repo, "HEAD:README");
git_object_free(obj);
- fp = efopen("log.html", "w+b");
+ fp = efopen("log.html", "w");
writeheader(fp);
writelog(fp);
writefooter(fp);
fclose(fp);
- fp = efopen("files.html", "w+b");
+ fp = efopen("files.html", "w");
writeheader(fp);
writefiles(fp);
writefooter(fp);
fclose(fp);
/* Atom feed */
- fp = efopen("atom.xml", "w+b");
+ fp = efopen("atom.xml", "w");
writeatom(fp);
fclose(fp);