]> git.armaanb.net Git - stagit.git/blobdiff - urmoms.c
nvm, dont strip .git from bare repos
[stagit.git] / urmoms.c
index 2c5593ba23542cc431b870fe095c7eea28999040..9391de6f937143477da2fccc0986973f35094e45 100644 (file)
--- a/urmoms.c
+++ b/urmoms.c
@@ -1,6 +1,7 @@
 #include <sys/stat.h>
 
 #include <err.h>
+#include <errno.h>
 #include <inttypes.h>
 #include <libgen.h>
 #include <limits.h>
@@ -9,7 +10,10 @@
 #include <string.h>
 #include <unistd.h>
 
-#include "git2.h"
+#include <git2.h>
+
+#include "compat.h"
+#include "config.h"
 
 struct commitinfo {
        const git_oid *id;
@@ -40,6 +44,7 @@ static const char *repodir;
 
 static char name[255];
 static char description[255];
+static char cloneurl[1024];
 static int hasreadme, haslicense;
 
 void
@@ -50,6 +55,8 @@ commitinfo_free(struct commitinfo *ci)
 
        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);
 }
 
@@ -57,6 +64,7 @@ struct commitinfo *
 commitinfo_getbyoid(const git_oid *id)
 {
        struct commitinfo *ci;
+       git_diff_options opts;
        int error;
 
        if (!(ci = calloc(1, sizeof(struct commitinfo))))
@@ -66,7 +74,6 @@ commitinfo_getbyoid(const git_oid *id)
        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));
 
@@ -75,7 +82,7 @@ commitinfo_getbyoid(const git_oid *id)
        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;
@@ -84,7 +91,9 @@ commitinfo_getbyoid(const git_oid *id)
                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;
@@ -93,8 +102,6 @@ commitinfo_getbyoid(const git_oid *id)
        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:
@@ -104,38 +111,6 @@ 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<center>\n", fp);
-       fprintf(fp, "<h1><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /> %s <span class=\"desc\">%s</span></h1>\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=\"%sreadme.html\">README</a>", relpath);
-       if (haslicense)
-               fprintf(fp, " | <a href=\"%slicense.html\">LICENSE</a>", relpath);
-       fputs("\n</center>\n<hr/>\n<pre>", fp);
-
-       return 0;
-}
-
-int
-writefooter(FILE *fp)
-{
-       return !fputs("</pre>\n</body>\n</html>", fp);
-}
-
 FILE *
 efopen(const char *name, const char *flags)
 {
@@ -165,6 +140,27 @@ xmlencode(FILE *fp, const char *s, size_t len)
        }
 }
 
+/* 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.
@@ -186,6 +182,25 @@ xbasename(const char *path)
        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 != EEXIST)
+                       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)
 {
@@ -217,16 +232,84 @@ 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; 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)
 {
-       xmlencode(fp, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(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)
 {
-       /* 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);
 
@@ -282,8 +365,9 @@ printshowfile(struct commitinfo *ci)
        if (!access(path, F_OK))
                return;
 
-       fp = efopen(path, "w+b");
+       fp = efopen(path, "w");
        writeheader(fp);
+       fputs("<pre>\n", fp);
        printcommit(fp, ci);
 
        memset(&statsbuf, 0, sizeof(statsbuf));
@@ -305,11 +389,11 @@ printshowfile(struct commitinfo *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);
-               fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s\">%s</a> b/<a href=\"%sfile/%s\">%s</a></b>\n",
+               fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s.html\">%s</a> b/<a href=\"%sfile/%s.html\">%s</a></b>\n",
                        relpath, delta->old_file.path, delta->old_file.path,
                        relpath, delta->new_file.path, delta->new_file.path);
 
@@ -323,7 +407,7 @@ printshowfile(struct commitinfo *ci)
                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);
 
@@ -331,59 +415,61 @@ printshowfile(struct commitinfo *ci)
                                if (git_patch_get_line_in_hunk(&line, patch, j, k))
                                        break;
                                if (line->old_lineno == -1)
-                                       fprintf(fp, "<span class=\"i\"><a href=\"#h%zu-%zu\" id=\"h%zu-%zu\">+",
+                                       fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"i\">+",
                                                j, k, j, k);
                                else if (line->new_lineno == -1)
-                                       fprintf(fp, "<span class=\"d\"><a href=\"#h%zu-%zu\" id=\"h%zu-%zu\">-",
+                                       fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"d\">-",
                                                j, k, j, k);
                                else
                                        fputc(' ', fp);
                                xmlencode(fp, line->content, line->content_len);
                                if (line->old_lineno == -1 || line->new_lineno == -1)
-                                       fputs("</a></span>", fp);
+                                       fputs("</a>", fp);
                        }
                }
                git_patch_free(patch);
        }
        git_buf_free(&statsbuf);
 
+       fputs( "</pre>\n", fp);
        writefooter(fp);
        fclose(fp);
        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_new(&w, repo);
        git_revwalk_push_head(w);
+       git_revwalk_sorting(w, GIT_SORT_TIME);
+       git_revwalk_simplify_first_parent(w);
 
-       /* TODO: also make "expanded" log ? (with message body) */
-       fputs("<table><thead>\n<tr><td>Commit message</td><td>Author</td><td align=\"right\">Age</td>"
-             "<td align=\"right\">Files</td><td align=\"right\">+</td><td align=\"right\">-</td></tr>\n</thead><tbody>\n", fp);
+       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);
        while (!git_revwalk_next(&id, w)) {
                relpath = "";
 
                if (!(ci = commitinfo_getbyoid(&id)))
                        break;
 
-               fputs("<tr><td align=\"right\">", fp);
+               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, ci->oid);
-                       if ((len = strlen(ci->summary)) > 79) {
-                               xmlencode(fp, ci->summary, 76);
-                               fputs("...", fp);
+                       if ((len = strlen(ci->summary)) > summarylen) {
+                               xmlencode(fp, ci->summary, summarylen - 1);
+                               fputs("", fp);
                        } else {
                                xmlencode(fp, ci->summary, len);
                        }
@@ -392,11 +478,11 @@ writelog(FILE *fp)
                fputs("</td><td>", fp);
                if (ci->author)
                        xmlencode(fp, ci->author->name, strlen(ci->author->name));
-               fputs("</td><td align=\"right\">", fp);
+               fputs("</td><td class=\"num\">", fp);
                fprintf(fp, "%zu", ci->filecount);
-               fputs("</td><td align=\"right\">", fp);
+               fputs("</td><td class=\"num\">", fp);
                fprintf(fp, "+%zu", ci->addcount);
-               fputs("</td><td align=\"right\">", fp);
+               fputs("</td><td class=\"num\">", fp);
                fprintf(fp, "-%zu", ci->delcount);
                fputs("</td></tr>\n", fp);
 
@@ -409,8 +495,6 @@ writelog(FILE *fp)
 
        git_revwalk_free(w);
        relpath = "";
-
-       return ret;
 }
 
 void
@@ -477,8 +561,8 @@ writeatom(FILE *fp)
        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);
 
@@ -487,6 +571,8 @@ writeatom(FILE *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)))
@@ -502,32 +588,176 @@ writeatom(FILE *fp)
 }
 
 int
-writefiles(FILE *fp)
+writeblob(git_object *obj, const char *filename, git_off_t filesize)
 {
-       const git_index_entry *entry;
-       git_index *index;
-       size_t count, i;
+       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);
 
-       fputs("<table><thead>\n"
-             "<tr><td>Mode</td><td>Name</td><td align=\"right\">Size</td></tr>\n"
-             "</thead><tbody>\n", fp);
+       p = fpath;
+       while (*p) {
+               if (*p == '/')
+                       strlcat(tmp, "../", sizeof(tmp));
+               p++;
+       }
+       relpath = tmp;
 
-       git_repository_index(&index, repo);
-       count = git_index_entrycount(index);
+       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++) {
-               entry = git_index_get_byindex(index, 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, filename);
+                       filename = filepath;
+               }
+
+               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));
-               fputs("\">", fp);
-               xmlencode(fp, entry->path, strlen(entry->path));
-               fputs("</a></td><td align=\"right\">", fp);
-               fprintf(fp, "%" PRIu64, entry->file_size);
+               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;
@@ -550,7 +780,8 @@ main(int argc, char *argv[])
 
        git_libgit2_init();
 
-       if ((status = git_repository_open(&repo, repodir)) < 0) {
+       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->message);
                return status;
@@ -564,10 +795,10 @@ main(int argc, char *argv[])
        /* 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))
@@ -575,48 +806,41 @@ main(int argc, char *argv[])
                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);
        /* check README */
        hasreadme = !git_revparse_single(&obj, repo, "HEAD:README");
+       git_object_free(obj);
 
-       /* read LICENSE */
-       if (!git_revparse_single(&obj, repo, "HEAD:LICENSE")) {
-               fp = efopen("license.html", "w+b");
-               writeheader(fp);
-               writeblobhtml(fp, (git_blob *)obj);
-               if (ferror(fp))
-                       err(1, "fwrite");
-               writefooter(fp);
-
-               fclose(fp);
-       }
-
-       /* read README */
-       if (!git_revparse_single(&obj, repo, "HEAD:README")) {
-               fp = efopen("readme.html", "w+b");
-               writeheader(fp);
-               writeblobhtml(fp, (git_blob *)obj);
-               if (ferror(fp))
-                       err(1, "fwrite");
-               writefooter(fp);
-               fclose(fp);
-       }
-
-       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);