#include <sys/stat.h>
+#include <sys/types.h>
#include <err.h>
#include <errno.h>
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 = "";
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;
if (!di)
return;
git_patch_free(di->patch);
- di->patch = NULL;
+ memset(di, 0, sizeof(*di));
free(di);
}
commitinfo_getstats(struct commitinfo *ci)
{
struct deltainfo *di;
+ git_diff_options opts;
const git_diff_delta *delta;
const git_diff_hunk *hunk;
const git_diff_line *line;
size_t ndeltas, nhunks, nhunklines;
size_t i, j, k;
+ if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
+ goto err;
+ if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
+ if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
+ ci->parent = NULL;
+ ci->parent_tree = NULL;
+ }
+ }
+
+ git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
+ 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;
+
ndeltas = git_diff_num_deltas(ci->diff);
if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
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;
return 0;
err:
+ git_diff_free(ci->diff);
+ ci->diff = NULL;
+ git_tree_free(ci->commit_tree);
+ ci->commit_tree = NULL;
+ git_tree_free(ci->parent_tree);
+ ci->parent_tree = NULL;
+ git_commit_free(ci->parent);
+ ci->parent = NULL;
+
if (ci->deltas)
for (i = 0; i < ci->ndeltas; i++)
deltainfo_free(ci->deltas[i]);
if (ci->deltas)
for (i = 0; i < ci->ndeltas; i++)
deltainfo_free(ci->deltas[i]);
+
free(ci->deltas);
- ci->deltas = NULL;
git_diff_free(ci->diff);
git_tree_free(ci->commit_tree);
git_tree_free(ci->parent_tree);
git_commit_free(ci->commit);
git_commit_free(ci->parent);
+ memset(ci, 0, sizeof(*ci));
free(ci);
}
commitinfo_getbyoid(const git_oid *id)
{
struct commitinfo *ci;
- git_diff_options opts;
if (!(ci = calloc(1, sizeof(struct commitinfo))))
err(1, "calloc");
ci->summary = git_commit_summary(ci->commit);
ci->msg = git_commit_message(ci->commit);
- if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
- goto err;
- if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
- if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
- ci->parent = NULL;
- ci->parent_tree = NULL;
- }
- }
-
- git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
- 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;
err:
FILE *fp;
if (!(fp = fopen(name, flags)))
- err(1, "fopen");
+ err(1, "fopen: '%s'", name);
return fp;
}
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);
writeblobhtml(FILE *fp, const git_blob *blob)
{
size_t n = 0, i, prev;
- const char *nfmt = "<a href=\"#l%d\" class=\"line\" id=\"l%d\">%6d</a> ";
+ 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);
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);
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;
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);
writefooter(fpfile);
fclose(fpfile);
}
+err:
commitinfo_free(ci);
}
git_revwalk_free(w);
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);
void
usage(char *argv0)
{
- fprintf(stderr, "%s [-c cachefile] repodir\n", argv0);
+ fprintf(stderr, "%s [-c cachefile] [-l commits] repodir\n", argv0);
exit(1);
}
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)
- err(1, "pledge");
-
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-') {
if (repodir)
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 (!repodir)
git_libgit2_init();
+ if (cachefile) {
+ if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
+ err(1, "pledge");
+ } else {
+ if (pledge("stdio rpath wpath cpath", NULL) == -1)
+ err(1, "pledge");
+ }
+
if (git_repository_open_ext(&repo, repodir,
GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
e = giterr_last();
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++;
/* 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>"
"<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))
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);
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);