X-Git-Url: https://git.armaanb.net/?a=blobdiff_plain;f=stagit.c;h=9a8ccd0245b1e55af35fad1dd86f3477ec779e22;hb=503398bd5f325447f7d801306222e5fc142cacae;hp=86c39200c2cf50340e098b5f125e6d1d42250734;hpb=187daac42007c87e6af9317a20446e3b81907f63;p=stagit.git diff --git a/stagit.c b/stagit.c index 86c3920..9a8ccd0 100644 --- a/stagit.c +++ b/stagit.c @@ -3,12 +3,13 @@ #include #include -#include #include #include +#include #include #include #include +#include #include #include @@ -56,12 +57,16 @@ static char *name = ""; static char *strippedname = ""; static char description[255]; static char cloneurl[1024]; -static int haslicense, hasreadme, hassubmodules; +static char *submodules; +static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:COPYING" }; +static char *license; +static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" }; +static char *readme; static long long nlogcommits = -1; /* < 0 indicates not used */ /* cache */ static git_oid lastoid; -static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + nul byte */ +static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */ static FILE *rcachefp, *wcachefp; static const char *cachefile; @@ -72,7 +77,7 @@ joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) r = snprintf(buf, bufsiz, "%s%s%s", path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); - if (r == -1 || (size_t)r >= bufsiz) + if (r < 0 || (size_t)r >= bufsiz) errx(1, "path truncated: '%s%s%s'", path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); } @@ -92,6 +97,7 @@ commitinfo_getstats(struct commitinfo *ci) { struct deltainfo *di; git_diff_options opts; + git_diff_find_options fopts; const git_diff_delta *delta; const git_diff_hunk *hunk; const git_diff_line *line; @@ -109,10 +115,20 @@ commitinfo_getstats(struct commitinfo *ci) } git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION); - opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH | + GIT_DIFF_IGNORE_SUBMODULES | + GIT_DIFF_INCLUDE_TYPECHANGE; if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts)) goto err; + if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION)) + goto err; + /* find renames and copies, exact matches (no heuristic) for renames. */ + fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES | + GIT_DIFF_FIND_EXACT_MATCH_ONLY; + if (git_diff_find_similar(ci->diff, &fopts)) + goto err; + ndeltas = git_diff_num_deltas(ci->diff); if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *)))) err(1, "calloc"); @@ -120,6 +136,7 @@ commitinfo_getstats(struct commitinfo *ci) 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"); di->patch = patch; @@ -327,6 +344,7 @@ writeheader(FILE *fp, const char *title) fputs("\n" "\n\n" "\n" + "\n" "", fp); xmlencode(fp, title, strlen(title)); if (title[0] && strippedname[0]) @@ -348,22 +366,25 @@ writeheader(FILE *fp, const char *title) 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); + fputs("<tr class=\"url\"><td></td><td><span class=\"clone\">git clone <a href=\"", fp); xmlencode(fp, cloneurl, strlen(cloneurl)); fputs("\">", fp); xmlencode(fp, cloneurl, strlen(cloneurl)); - fputs("</a></td></tr>", fp); + fputs("</a></span></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); fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath); - if (hassubmodules) - fprintf(fp, " | <a href=\"%sfile/.gitmodules.html\">Submodules</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); + if (submodules) + fprintf(fp, " | <a href=\"%sfile/%s.html\">Submodules</a>", + relpath, submodules); + if (readme) + fprintf(fp, " | <a href=\"%sfile/%s.html\">README</a>", + relpath, readme); + if (license) + fprintf(fp, " | <a href=\"%sfile/%s.html\">LICENSE</a>", + relpath, license); fputs("</td></tr></table>\n<hr/>\n<div id=\"content\">\n", fp); } @@ -374,35 +395,51 @@ writefooter(FILE *fp) } int -writeblobhtml(FILE *fp, const git_blob *blob) +syntax_highlight(const char *filename, FILE *fp, const char *s, size_t len) { - size_t n = 0, i, prev; - const char *nfmt = "<a href=\"#l%d\" class=\"line\" id=\"l%d\">%7d</a> "; + // Copy STDOUT + int stdout_copy = dup(1); + // Redirect STDOUT + dup2(fileno(fp), 1); + + // Ruby script for syntax highlighting. + FILE *child = popen("./highlight", "w"); + if (child == NULL) { + printf("child is null: %s", strerror(errno)); + exit(1); + } + // Give filename through STDIN: + fprintf(child, "%s\n", filename); + // Give code to highlight through STDIN: + int lc; + size_t i; + for (i = 0; *s && i < len; s++, i++) { + if (*s == '\n') lc++; + fprintf(child, "%c", *s); + } + + pclose(child); + // Give back STDOUT. + dup2(stdout_copy, 1); + return lc; +} + +int +writeblobhtml(const char *filename, FILE *fp, const git_blob *blob) +{ + int lc = 0; const char *s = git_blob_rawcontent(blob); git_off_t len = git_blob_rawsize(blob); - fputs("<pre id=\"blob\">\n", fp); + fputs("<div id=\"blob\">\n", fp); 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); - } + lc = syntax_highlight(filename, fp, s, len); } - fputs("</pre>\n", fp); + fputs("</div>\n", fp); - return n; + return lc; } void @@ -442,6 +479,7 @@ printshowfile(FILE *fp, struct commitinfo *ci) git_patch *patch; size_t nhunks, nhunklines, changed, add, del, total, i, j, k; char linestr[80]; + int c; printcommit(fp, ci); @@ -460,7 +498,22 @@ printshowfile(FILE *fp, struct commitinfo *ci) fputs("<b>Diffstat:</b>\n<table>", fp); for (i = 0; i < ci->ndeltas; i++) { delta = git_patch_get_delta(ci->deltas[i]->patch); - fprintf(fp, "<tr><td><a href=\"#h%zu\">", i); + + switch (delta->status) { + case GIT_DELTA_ADDED: c = 'A'; break; + case GIT_DELTA_COPIED: c = 'C'; break; + case GIT_DELTA_DELETED: c = 'D'; break; + case GIT_DELTA_MODIFIED: c = 'M'; break; + case GIT_DELTA_RENAMED: c = 'R'; break; + case GIT_DELTA_TYPECHANGE: c = 'T'; break; + default: c = ' '; break; + } + if (c == ' ') + fprintf(fp, "<tr><td>%c", c); + else + fprintf(fp, "<tr><td class=\"%c\">%c", c, c); + + fprintf(fp, "</td><td><a href=\"#h%zu\">", i); xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); if (strcmp(delta->old_file.path, delta->new_file.path)) { fputs(" -> ", fp); @@ -497,9 +550,15 @@ printshowfile(FILE *fp, struct commitinfo *ci) for (i = 0; i < ci->ndeltas; i++) { patch = ci->deltas[i]->patch; delta = git_patch_get_delta(patch); - fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/%s.html\">%s</a> b/<a href=\"%sfile/%s.html\">%s</a></b>\n", - i, relpath, delta->old_file.path, delta->old_file.path, - relpath, delta->new_file.path, delta->new_file.path); + fprintf(fp, "<b>diff --git a/<a id=\"h%zu\" href=\"%sfile/", i, relpath); + xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); + fputs(".html\">", fp); + xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path)); + fprintf(fp, "</a> b/<a href=\"%sfile/", relpath); + xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path)); + fprintf(fp, ".html\">"); + xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path)); + fprintf(fp, "</a></b>\n"); /* check binary data */ if (delta->flags & GIT_DIFF_FLAG_BINARY) { @@ -571,7 +630,6 @@ writelog(FILE *fp, const git_oid *oid) git_revwalk_new(&w, repo); git_revwalk_push(w, oid); - git_revwalk_sorting(w, GIT_SORT_TIME); git_revwalk_simplify_first_parent(w); while (!git_revwalk_next(&id, w)) { @@ -582,7 +640,7 @@ writelog(FILE *fp, const git_oid *oid) git_oid_tostr(oidstr, sizeof(oidstr), &id); r = snprintf(path, sizeof(path), "commit/%s.html", oidstr); - if (r == -1 || (size_t)r >= sizeof(path)) + if (r < 0 || (size_t)r >= sizeof(path)) errx(1, "path truncated: 'commit/%s.html'", oidstr); r = access(path, F_OK); @@ -602,6 +660,10 @@ writelog(FILE *fp, const git_oid *oid) } else if (nlogcommits > 0) { writelogline(fp, ci); nlogcommits--; + if (!nlogcommits && ci->parentoid[0]) + fputs("<tr><td></td><td colspan=\"5\">" + "More commits remaining [...]</td>" + "</tr>\n", fp); } if (cachefile) @@ -649,11 +711,11 @@ printcommitatom(FILE *fp, struct commitinfo *ci) xmlencode(fp, ci->summary, strlen(ci->summary)); fputs("\n", fp); } - fprintf(fp, "", + fprintf(fp, "\n", ci->oid); if (ci->author) { - fputs("", fp); + fputs("\n", fp); xmlencode(fp, ci->author->name, strlen(ci->author->name)); fputs("\n", fp); xmlencode(fp, ci->author->email, strlen(ci->author->email)); @@ -697,7 +759,6 @@ 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++) { @@ -744,7 +805,7 @@ writeblob(git_object *obj, const char *fpath, const char *filename, git_off_t fi if (git_blob_is_binary((git_blob *)obj)) { fputs("

Binary file.

\n", fp); } else { - lc = writeblobhtml(fp, (git_blob *)obj); + lc = writeblobhtml(fpath, fp, (git_blob *)obj); if (ferror(fp)) err(1, "fwrite"); } @@ -819,7 +880,7 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) r = snprintf(filepath, sizeof(filepath), "file/%s.html", entrypath); - if (r == -1 || (size_t)r >= sizeof(filepath)) + if (r < 0 || (size_t)r >= sizeof(filepath)) errx(1, "path truncated: 'file/%s.html'", entrypath); if (!git_tree_entry_to_object(&obj, repo, entry)) { @@ -844,7 +905,9 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) fputs("", fp); fputs(filemode(git_tree_entry_filemode(entry)), fp); - fprintf(fp, "", relpath, filepath); + fprintf(fp, "", fp); xmlencode(fp, entrypath, strlen(entrypath)); fputs("", fp); if (lc > 0) @@ -1015,7 +1078,6 @@ 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; @@ -1038,10 +1100,7 @@ main(int argc, char *argv[]) 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)) + nlogcommits <= 0 || errno) usage(argv[0]); } } @@ -1054,6 +1113,13 @@ main(int argc, char *argv[]) git_libgit2_init(); #ifdef __OpenBSD__ + if (unveil(repodir, "r") == -1) + err(1, "unveil: %s", repodir); + if (unveil(".", "rwc") == -1) + err(1, "unveil: ."); + if (cachefile && unveil(cachefile, "rwc") == -1) + err(1, "unveil: %s", cachefile); + if (cachefile) { if (pledge("stdio rpath wpath cpath fattr", NULL) == -1) err(1, "pledge"); @@ -1065,8 +1131,7 @@ main(int argc, char *argv[]) if (git_repository_open_ext(&repo, repodir, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) { - e = giterr_last(); - fprintf(stderr, "%s: %s\n", argv[0], e->message); + fprintf(stderr, "%s: cannot open repository\n", argv[0]); return 1; } @@ -1114,17 +1179,24 @@ main(int argc, char *argv[]) } /* check LICENSE */ - haslicense = (!git_revparse_single(&obj, repo, "HEAD:LICENSE") && - git_object_type(obj) == GIT_OBJ_BLOB); - git_object_free(obj); + for (i = 0; i < sizeof(licensefiles) / sizeof(*licensefiles) && !license; i++) { + if (!git_revparse_single(&obj, repo, licensefiles[i]) && + git_object_type(obj) == GIT_OBJ_BLOB) + license = licensefiles[i] + strlen("HEAD:"); + git_object_free(obj); + } /* check README */ - hasreadme = (!git_revparse_single(&obj, repo, "HEAD:README") && - git_object_type(obj) == GIT_OBJ_BLOB); - git_object_free(obj); + for (i = 0; i < sizeof(readmefiles) / sizeof(*readmefiles) && !readme; i++) { + if (!git_revparse_single(&obj, repo, readmefiles[i]) && + git_object_type(obj) == GIT_OBJ_BLOB) + readme = readmefiles[i] + strlen("HEAD:"); + git_object_free(obj); + } - hassubmodules = (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") && - git_object_type(obj) == GIT_OBJ_BLOB); + if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") && + git_object_type(obj) == GIT_OBJ_BLOB) + submodules = ".gitmodules"; git_object_free(obj); /* log for HEAD */ @@ -1133,7 +1205,7 @@ main(int argc, char *argv[]) mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO); writeheader(fp, "Log"); fputs("\n" - "" + "" "" "" "\n\n", fp);
DateCommit messageCommitAuthorFiles+-