]> git.armaanb.net Git - stagit.git/blobdiff - stagit.c
stagit: add -l option: limit the amount of commits for the log.html file
[stagit.git] / stagit.c
index 4e081dde21275b8884f561b02959cb5c6e7e11e1..81fdd1d15faef622e0b25d94225136c3b4de33dd 100644 (file)
--- a/stagit.c
+++ b/stagit.c
@@ -1,4 +1,5 @@
 #include <sys/stat.h>
+#include <sys/types.h>
 
 #include <err.h>
 #include <errno.h>
@@ -46,11 +47,6 @@ struct commitinfo {
        size_t ndeltas;
 };
 
-/* summary length (bytes) in the log */
-static const unsigned summarylen = 70;
-/* display line count or file size in file tree index */
-static const int showlinecount = 1;
-
 static git_repository *repo;
 
 static const char *relpath = "";
@@ -61,6 +57,7 @@ static char *strippedname = "";
 static char description[255];
 static char cloneurl[1024];
 static int haslicense, hasreadme, hassubmodules;
+static long long nlogcommits = -1; /* < 0 indicates not used */
 
 /* cache */
 static git_oid lastoid;
@@ -110,13 +107,10 @@ commitinfo_getstats(struct commitinfo *ci)
                err(1, "calloc");
 
        for (i = 0; i < ndeltas; i++) {
+               if (git_patch_from_diff(&patch, ci->diff, i))
+                       goto err;
                if (!(di = calloc(1, sizeof(struct deltainfo))))
                        err(1, "calloc");
-               if (git_patch_from_diff(&patch, ci->diff, i)) {
-                       git_patch_free(patch);
-                       free(di);
-                       goto err;
-               }
                di->patch = patch;
                ci->deltas[i] = di;
 
@@ -216,8 +210,6 @@ commitinfo_getbyoid(const git_oid *id)
        opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
        if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts))
                goto err;
-       if (commitinfo_getstats(ci) == -1)
-               goto err;
 
        return ci;
 
@@ -233,7 +225,7 @@ efopen(const char *name, const char *flags)
        FILE *fp;
 
        if (!(fp = fopen(name, flags)))
-               err(1, "fopen");
+               err(1, "fopen: '%s'", name);
 
        return fp;
 }
@@ -300,7 +292,7 @@ printtime(FILE *fp, const git_time *intime)
        t = (time_t)intime->time + (intime->offset * 60);
        if (!(intm = gmtime(&t)))
                return;
-       strftime(out, sizeof(out), "%a %b %e %H:%M:%S", intm);
+       strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
        if (intime->offset < 0)
                fprintf(fp, "%s -%02d%02d", out,
                            -(intime->offset) / 60, -(intime->offset) % 60);
@@ -378,28 +370,31 @@ writefooter(FILE *fp)
 int
 writeblobhtml(FILE *fp, const git_blob *blob)
 {
-       off_t i;
-       size_t n = 0;
-       const char *nfmt = "<a href=\"#l%d\" id=\"l%d\">%d</a>\n";
+       size_t n = 0, i, prev;
+       const char *nfmt = "<a href=\"#l%d\" class=\"line\" id=\"l%d\">%7d</a> ";
        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);
+       fputs("<pre id=\"blob\">\n", fp);
 
-       if (len) {
-               n++;
-               fprintf(fp, nfmt, n, n, n);
-               for (i = 0; i < len - 1; i++) {
-                       if (s[i] == '\n') {
-                               n++;
-                               fprintf(fp, nfmt, n, n, n);
-                       }
+       if (len > 0) {
+               for (i = 0, prev = 0; i < (size_t)len; i++) {
+                       if (s[i] != '\n')
+                               continue;
+                       n++;
+                       fprintf(fp, nfmt, n, n, n);
+                       xmlencode(fp, &s[prev], i - prev + 1);
+                       prev = i + 1;
+               }
+               /* trailing data */
+               if ((len - prev) > 0) {
+                       n++;
+                       fprintf(fp, nfmt, n, n, n);
+                       xmlencode(fp, &s[prev], len - prev);
                }
        }
 
-       fputs("</pre></td><td><pre>\n", fp);
-       xmlencode(fp, s, (size_t)len);
-       fputs("</pre></td></tr></table>\n", fp);
+       fputs("</pre>\n", fp);
 
        return n;
 }
@@ -486,7 +481,7 @@ printshowfile(FILE *fp, struct commitinfo *ci)
                fwrite(&linestr[add], 1, del, fp);
                fputs("</span></td></tr>\n", fp);
        }
-       fprintf(fp, "</table>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
+       fprintf(fp, "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
                ci->filecount, ci->filecount == 1 ? "" : "s",
                ci->addcount,  ci->addcount  == 1 ? "" : "s",
                ci->delcount,  ci->delcount  == 1 ? "" : "s");
@@ -537,20 +532,13 @@ printshowfile(FILE *fp, struct commitinfo *ci)
 void
 writelogline(FILE *fp, struct commitinfo *ci)
 {
-       size_t len;
-
        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)) > summarylen) {
-                       xmlencode(fp, ci->summary, summarylen - 1);
-                       fputs("…", fp);
-               } else {
-                       xmlencode(fp, ci->summary, len);
-               }
+               xmlencode(fp, ci->summary, strlen(ci->summary));
                fputs("</a>", fp);
        }
        fputs("</td><td>", fp);
@@ -571,7 +559,7 @@ writelog(FILE *fp, const git_oid *oid)
        struct commitinfo *ci;
        git_revwalk *w = NULL;
        git_oid id;
-       char path[PATH_MAX];
+       char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
        FILE *fpfile;
        int r;
 
@@ -585,21 +573,37 @@ writelog(FILE *fp, const git_oid *oid)
 
                if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
                        break;
+
+               git_oid_tostr(oidstr, sizeof(oidstr), &id);
+               r = snprintf(path, sizeof(path), "commit/%s.html", oidstr);
+               if (r == -1 || (size_t)r >= sizeof(path))
+                       errx(1, "path truncated: 'commit/%s.html'", oidstr);
+               r = access(path, F_OK);
+
+               /* optimization: if there are no log lines to write and
+                  the commit file already exists: skip the diffstat */
+               if (!nlogcommits && !r)
+                       continue;
+
                if (!(ci = commitinfo_getbyoid(&id)))
                        break;
+               /* diffstat: for stagit HTML required for the log.html line */
+               if (commitinfo_getstats(ci) == -1)
+                       goto err;
+
+               if (nlogcommits < 0) {
+                       writelogline(fp, ci);
+               } else if (nlogcommits > 0) {
+                       writelogline(fp, ci);
+                       nlogcommits--;
+               }
 
-               writelogline(fp, ci);
                if (cachefile)
                        writelogline(wcachefp, ci);
 
-               relpath = "../";
-
-               r = snprintf(path, sizeof(path), "commit/%s.html", ci->oid);
-               if (r == -1 || (size_t)r >= sizeof(path))
-                       errx(1, "path truncated: 'commit/%s.html'", ci->oid);
-
                /* check if file exists if so skip it */
-               if (access(path, F_OK)) {
+               if (r) {
+                       relpath = "../";
                        fpfile = efopen(path, "w");
                        writeheader(fpfile, ci->summary);
                        fputs("<pre>", fpfile);
@@ -608,6 +612,7 @@ writelog(FILE *fp, const git_oid *oid)
                        writefooter(fpfile);
                        fclose(fpfile);
                }
+err:
                commitinfo_free(ci);
        }
        git_revwalk_free(w);
@@ -836,7 +841,7 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
                        fprintf(fp, "</td><td><a href=\"%s%s\">", relpath, filepath);
                        xmlencode(fp, entrypath, strlen(entrypath));
                        fputs("</a></td><td class=\"num\" align=\"right\">", fp);
-                       if (showlinecount && lc > 0)
+                       if (lc > 0)
                                fprintf(fp, "%dL", lc);
                        else
                                fprintf(fp, "%juB", (uintmax_t)filesize);
@@ -995,7 +1000,7 @@ err:
 void
 usage(char *argv0)
 {
-       fprintf(stderr, "%s [-c cachefile] repodir\n", argv0);
+       fprintf(stderr, "%s [-c cachefile] [-l commits] repodir\n", argv0);
        exit(1);
 }
 
@@ -1005,13 +1010,14 @@ main(int argc, char *argv[])
        git_object *obj = NULL;
        const git_oid *head = NULL;
        const git_error *e = NULL;
+       mode_t mask;
        FILE *fp, *fpread;
        char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
        char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
        size_t n;
        int i, fd;
 
-       if (pledge("stdio rpath wpath cpath", NULL) == -1)
+       if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
                err(1, "pledge");
 
        for (i = 1; i < argc; i++) {
@@ -1020,11 +1026,24 @@ main(int argc, char *argv[])
                                usage(argv[0]);
                        repodir = argv[i];
                } else if (argv[i][1] == 'c') {
-                       if (i + 1 >= argc)
+                       if (nlogcommits > 0 || i + 1 >= argc)
                                usage(argv[0]);
                        cachefile = argv[++i];
+               } else if (argv[i][1] == 'l') {
+                       if (cachefile || i + 1 >= argc)
+                               usage(argv[0]);
+                       errno = 0;
+                       nlogcommits = strtoll(argv[++i], &p, 10);
+                       if (argv[i][0] == '\0' || *p != '\0' ||
+                           nlogcommits <= 0)
+                               usage(argv[0]);
+                       if (errno == ERANGE && (nlogcommits == LLONG_MAX ||
+                           nlogcommits == LLONG_MIN))
+                               usage(argv[0]);
                }
        }
+       if (!cachefile && pledge("stdio rpath wpath cpath", NULL) == -1)
+               err(1, "pledge");
        if (!repodir)
                usage(argv[0]);
 
@@ -1045,10 +1064,6 @@ main(int argc, char *argv[])
                head = git_object_id(obj);
        git_object_free(obj);
 
-       /* don't cache if there is no HEAD */
-       if (!head)
-               cachefile = NULL;
-
        /* use directory name as name */
        if ((name = strrchr(repodirabs, '/')))
                name++;
@@ -1104,7 +1119,7 @@ main(int argc, char *argv[])
        /* log for HEAD */
        fp = efopen("log.html", "w");
        relpath = "";
-       mkdir("commit", 0755);
+       mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
        writeheader(fp, "Log");
        fputs("<table id=\"log\"><thead>\n<tr><td><b>Date</b></td>"
              "<td><b>Commit message</b></td>"
@@ -1112,7 +1127,7 @@ main(int argc, char *argv[])
              "<td class=\"num\" align=\"right\"><b>+</b></td>"
              "<td class=\"num\" align=\"right\"><b>-</b></td></tr>\n</thead><tbody>\n", fp);
 
-       if (cachefile) {
+       if (cachefile && head) {
                /* read from cache file (does not need to exist) */
                if ((rcachefp = fopen(cachefile, "r"))) {
                        if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
@@ -1125,7 +1140,7 @@ main(int argc, char *argv[])
                if ((fd = mkstemp(tmppath)) == -1)
                        err(1, "mkstemp");
                if (!(wcachefp = fdopen(fd, "w")))
-                       err(1, "fdopen");
+                       err(1, "fdopen: '%s'", tmppath);
                /* write last commit id (HEAD) */
                git_oid_tostr(buf, sizeof(buf), head);
                fprintf(wcachefp, "%s\n", buf);
@@ -1175,8 +1190,14 @@ main(int argc, char *argv[])
        fclose(fp);
 
        /* rename new cache file on success */
-       if (cachefile && rename(tmppath, cachefile))
-               err(1, "rename: '%s' to '%s'", tmppath, cachefile);
+       if (cachefile && head) {
+               if (rename(tmppath, cachefile))
+                       err(1, "rename: '%s' to '%s'", tmppath, cachefile);
+               umask((mask = umask(0)));
+               if (chmod(cachefile,
+                   (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
+                       err(1, "chmod: '%s'", cachefile);
+       }
 
        /* cleanup */
        git_repository_free(repo);