add refs page (branches and tags) - stagit-gopher - A git gopher frontend. (mir… | |
git clone git://bitreich.org/stagit-gopher/ git://enlrupgkhuxnvlhsf6lc3fziv5h2h… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
LICENSE | |
--- | |
commit e10f79ad2afa61294c90c4e9ae361aa2b086cf9d | |
parent c226554b64a9529296b690d827966ccf139336bd | |
Author: Hiltjo Posthuma <[email protected]> | |
Date: Sun, 3 Jan 2016 21:06:03 +0100 | |
add refs page (branches and tags) | |
quite some code is added, this will be cleaned up in a following code iteration. | |
- make sure to free some more allocated git objects. | |
- use fputs() asmuch as possible instead of fprintf(). | |
- code cleanup | |
Diffstat: | |
M TODO | 1 + | |
M stagit.c | 282 +++++++++++++++++++++++++++--… | |
2 files changed, 251 insertions(+), 32 deletions(-) | |
--- | |
diff --git a/TODO b/TODO | |
@@ -4,6 +4,7 @@ performance: | |
layout: | |
- make top menu look nicer in links/lynx again. | |
+- show tags in log. | |
documentation: | |
- improve mandoc pages. | |
diff --git a/stagit.c b/stagit.c | |
@@ -264,7 +264,8 @@ writeheader(FILE *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=\"%sfiles.html\">Files</a> | ", relpath); | |
+ fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath); | |
if (hasreadme) | |
fprintf(fp, " | <a href=\"%sfile/README.html\">README</a>", re… | |
if (haslicense) | |
@@ -283,7 +284,7 @@ writefooter(FILE *fp) | |
void | |
writeblobhtml(FILE *fp, const git_blob *blob) | |
{ | |
- off_t i = 0; | |
+ off_t i; | |
size_t n = 1; | |
char *nfmt = "<a href=\"#l%d\" id=\"l%d\">%d</a>\n"; | |
const char *s = git_blob_rawcontent(blob); | |
@@ -293,12 +294,11 @@ writeblobhtml(FILE *fp, const git_blob *blob) | |
if (len) { | |
fprintf(fp, nfmt, n, n, n); | |
- while (i < len - 1) { | |
+ for (i = 0; i < len - 1; i++) { | |
if (s[i] == '\n') { | |
n++; | |
fprintf(fp, nfmt, n, n, n); | |
} | |
- i++; | |
} | |
} | |
@@ -319,7 +319,7 @@ printcommit(FILE *fp, struct commitinfo *ci) | |
#if 0 | |
if ((count = (int)git_commit_parentcount(commit)) > 1) { | |
- fprintf(fp, "<b>Merge:</b>"); | |
+ fputs("<b>Merge:</b>", fp); | |
for (i = 0; i < count; i++) { | |
git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); | |
fprintf(fp, " <a href=\"%scommit/%s.html\">%s</a>", | |
@@ -329,9 +329,9 @@ printcommit(FILE *fp, struct commitinfo *ci) | |
} | |
#endif | |
if (ci->author) { | |
- fprintf(fp, "<b>Author:</b> "); | |
+ fputs("<b>Author:</b> ", fp); | |
xmlencode(fp, ci->author->name, strlen(ci->author->name)); | |
- fprintf(fp, " <<a href=\"mailto:"); | |
+ fputs(" <<a href=\"mailto:", fp); | |
xmlencode(fp, ci->author->email, strlen(ci->author->email)); | |
fputs("\">", fp); | |
xmlencode(fp, ci->author->email, strlen(ci->author->email)); | |
@@ -377,7 +377,7 @@ printshowfile(struct commitinfo *ci) | |
if (!git_diff_stats_to_buf(&statsbuf, ci->stats, | |
GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_SHORT, 80)) { | |
if (statsbuf.ptr && statsbuf.ptr[0]) { | |
- fprintf(fp, "<b>Diffstat:</b>\n"); | |
+ fputs("<b>Diffstat:</b>\n", fp); | |
fputs(statsbuf.ptr, fp); | |
} | |
} | |
@@ -431,24 +431,30 @@ printshowfile(struct commitinfo *ci) | |
} | |
git_buf_free(&statsbuf); | |
- fputs( "</pre>\n", fp); | |
+ fputs("</pre>\n", fp); | |
writefooter(fp); | |
fclose(fp); | |
return; | |
} | |
-void | |
-writelog(FILE *fp) | |
+int | |
+writelog(FILE *fp, const char *branch) | |
{ | |
struct commitinfo *ci; | |
+ const git_oid *oid; | |
git_revwalk *w = NULL; | |
+ git_object *obj = NULL; | |
git_oid id; | |
size_t len; | |
mkdir("commit", 0755); | |
+ if (git_revparse_single(&obj, repo, branch)) | |
+ return -1; | |
+ oid = git_object_id(obj); | |
+ | |
git_revwalk_new(&w, repo); | |
- git_revwalk_push_head(w); | |
+ git_revwalk_push(w, oid); | |
git_revwalk_sorting(w, GIT_SORT_TIME); | |
git_revwalk_simplify_first_parent(w); | |
@@ -491,10 +497,14 @@ writelog(FILE *fp) | |
commitinfo_free(ci); | |
} | |
- fprintf(fp, "</tbody></table>"); | |
+ fputs("</tbody></table>", fp); | |
git_revwalk_free(w); | |
+ git_object_free(obj); | |
+ | |
relpath = ""; | |
+ | |
+ return 0; | |
} | |
void | |
@@ -521,7 +531,7 @@ printcommitatom(FILE *fp, struct commitinfo *ci) | |
#if 0 | |
if ((count = (int)git_commit_parentcount(commit)) > 1) { | |
- fprintf(fp, "Merge:"); | |
+ fputs("Merge:", fp); | |
for (i = 0; i < count; i++) { | |
git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); | |
fprintf(fp, " %s", buf); | |
@@ -531,11 +541,11 @@ printcommitatom(FILE *fp, struct commitinfo *ci) | |
#endif | |
if (ci->author) { | |
- fprintf(fp, "Author: "); | |
+ fputs("Author: ", fp); | |
xmlencode(fp, ci->author->name, strlen(ci->author->name)); | |
- fprintf(fp, " <"); | |
+ fputs(" <", fp); | |
xmlencode(fp, ci->author->email, strlen(ci->author->email)); | |
- fprintf(fp, ">\nDate: "); | |
+ fputs(">\nDate: ", fp); | |
printtime(fp, &(ci->author->when)); | |
} | |
fputc('\n', fp); | |
@@ -619,7 +629,7 @@ writeblob(git_object *obj, const char *filename, git_off_t … | |
fputs("</p><hr/>", fp); | |
if (git_blob_is_binary((git_blob *)obj)) { | |
- fprintf(fp, "<p>Binary file</p>\n"); | |
+ fputs("<p>Binary file</p>\n", fp); | |
} else { | |
writeblobhtml(fp, (git_blob *)obj); | |
if (ferror(fp)) | |
@@ -676,7 +686,7 @@ filemode(git_filemode_t m) | |
} | |
int | |
-writefilestree(FILE *fp, git_tree *tree, const char *path) | |
+writefilestree(FILE *fp, git_tree *tree, const char *branch, const char *path) | |
{ | |
const git_tree_entry *entry = NULL; | |
const char *filename; | |
@@ -690,15 +700,15 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) | |
for (i = 0; i < count; 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; | |
+ filename = git_tree_entry_name(entry); | |
switch (git_object_type(obj)) { | |
case GIT_OBJ_BLOB: | |
break; | |
case GIT_OBJ_TREE: | |
- ret = writefilestree(fp, (git_tree *)obj, filename); | |
+ ret = writefilestree(fp, (git_tree *)obj, branch, | |
+ filename); | |
git_object_free(obj); | |
if (ret) | |
return ret; | |
@@ -708,7 +718,8 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) | |
continue; | |
} | |
if (path[0]) { | |
- snprintf(filepath, sizeof(filepath), "%s/%s", path, fi… | |
+ snprintf(filepath, sizeof(filepath), "%s/%s", | |
+ path, filename); | |
filename = filepath; | |
} | |
@@ -731,42 +742,221 @@ writefilestree(FILE *fp, git_tree *tree, const char *pat… | |
} | |
int | |
-writefiles(FILE *fp) | |
+writefiles(FILE *fp, const char *branch) | |
{ | |
const git_oid *id; | |
git_tree *tree = NULL; | |
git_object *obj = NULL; | |
git_commit *commit = NULL; | |
+ int ret = -1; | |
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; | |
+ if (git_revparse_single(&obj, repo, branch)) | |
+ goto err; | |
id = git_object_id(obj); | |
if (git_commit_lookup(&commit, repo, id)) | |
- return -1; | |
+ goto err; | |
if (git_commit_tree(&tree, commit)) { | |
git_commit_free(commit); | |
- return -1; | |
+ goto err; | |
} | |
- git_commit_free(commit); | |
+ ret = writefilestree(fp, tree, branch, ""); | |
- writefilestree(fp, tree, ""); | |
+err: | |
+ fputs("</tbody></table>", fp); | |
+ git_object_free(obj); | |
git_commit_free(commit); | |
git_tree_free(tree); | |
+ return ret; | |
+} | |
+ | |
+int | |
+writebranches(FILE *fp) | |
+{ | |
+ struct commitinfo *ci; | |
+ git_branch_iterator *it = NULL; | |
+ git_branch_t branch; | |
+ git_reference *ref = NULL, *dref = NULL; | |
+ const git_oid *id = NULL; | |
+ const char *branchname = NULL; | |
+ size_t len; | |
+ int ret = -1; | |
+ | |
+ /* log for local branches */ | |
+ if (git_branch_iterator_new(&it, repo, GIT_BRANCH_LOCAL)) | |
+ return -1; | |
+ | |
+ fputs("<h2>Branches</h2><table id=\"branches\"><thead>\n<tr><td>Branch… | |
+ "<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_branch_next(&ref, &branch, it)) { | |
+ if (git_branch_name(&branchname, ref)) | |
+ continue; | |
+ | |
+ id = NULL; | |
+ switch (git_reference_type(ref)) { | |
+ case GIT_REF_SYMBOLIC: | |
+ if (git_reference_resolve(&dref, ref)) | |
+ goto err; | |
+ id = git_reference_target(dref); | |
+ break; | |
+ case GIT_REF_OID: | |
+ id = git_reference_target(ref); | |
+ break; | |
+ default: | |
+ continue; | |
+ } | |
+ if (!id) | |
+ goto err; | |
+ if (!(ci = commitinfo_getbyoid(id))) | |
+ break; | |
+ | |
+ relpath = ""; | |
+ | |
+ fputs("<tr><td>", fp); | |
+ xmlencode(fp, branchname, strlen(branchname)); | |
+ fputs("</td><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, … | |
+ if ((len = strlen(ci->summary)) > summarylen) { | |
+ xmlencode(fp, ci->summary, summarylen - 1); | |
+ fputs("…", fp); | |
+ } else { | |
+ xmlencode(fp, ci->summary, len); | |
+ } | |
+ fputs("</a>", fp); | |
+ } | |
+ fputs("</td><td>", fp); | |
+ if (ci->author) | |
+ xmlencode(fp, ci->author->name, strlen(ci->author->nam… | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "%zu", ci->filecount); | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "+%zu", ci->addcount); | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "-%zu", ci->delcount); | |
+ fputs("</td></tr>\n", fp); | |
+ | |
+ relpath = "../"; | |
+ | |
+ commitinfo_free(ci); | |
+ git_reference_free(ref); | |
+ git_reference_free(dref); | |
+ ref = NULL; | |
+ dref = NULL; | |
+ } | |
+ ret = 0; | |
+ | |
+err: | |
fputs("</tbody></table>", fp); | |
+ git_reference_free(ref); | |
+ git_reference_free(dref); | |
+ git_branch_iterator_free(it); | |
+ | |
+ return ret; | |
+} | |
+ | |
+int | |
+tagcompare(void *s1, void *s2) | |
+{ | |
+ return strcmp(*(char **)s1, *(char **)s2); | |
+} | |
+ | |
+int | |
+writetags(FILE *fp) | |
+{ | |
+ struct commitinfo *ci; | |
+ git_strarray tagnames; | |
+ git_object *obj = NULL; | |
+ const git_oid *id = NULL; | |
+ size_t i, len; | |
+ | |
+ fputs("<h2>Tags</h2><table id=\"branches\"><thead>\n<tr><td>Tag</td>" | |
+ "<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); | |
+ | |
+ /* summary page with branches and tags */ | |
+ memset(&tagnames, 0, sizeof(tagnames)); | |
+ git_tag_list(&tagnames, repo); | |
+ /* sort names */ | |
+ qsort(tagnames.strings, tagnames.count, sizeof(char *), | |
+ (int (*)(const void *, const void *))&tagcompare); | |
+ for (i = 0; i < tagnames.count; i++) { | |
+ if (git_revparse_single(&obj, repo, tagnames.strings[i])) | |
+ continue; | |
+ id = git_object_id(obj); | |
+ if (!(ci = commitinfo_getbyoid(id))) | |
+ break; | |
+ | |
+ relpath = ""; | |
+ | |
+ fputs("<tr><td>", fp); | |
+ xmlencode(fp, tagnames.strings[i], strlen(tagnames.strings[i])… | |
+ fputs("</td><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, … | |
+ if ((len = strlen(ci->summary)) > summarylen) { | |
+ xmlencode(fp, ci->summary, summarylen - 1); | |
+ fputs("…", fp); | |
+ } else { | |
+ xmlencode(fp, ci->summary, len); | |
+ } | |
+ fputs("</a>", fp); | |
+ } | |
+ fputs("</td><td>", fp); | |
+ if (ci->author) | |
+ xmlencode(fp, ci->author->name, strlen(ci->author->nam… | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "%zu", ci->filecount); | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "+%zu", ci->addcount); | |
+ fputs("</td><td class=\"num\">", fp); | |
+ fprintf(fp, "-%zu", ci->delcount); | |
+ fputs("</td></tr>\n", fp); | |
+ | |
+ relpath = "../"; | |
+ | |
+ commitinfo_free(ci); | |
+ git_object_free(obj); | |
+ } | |
+ fputs("</tbody></table>", fp); | |
+ git_strarray_free(&tagnames); | |
return 0; | |
} | |
int | |
+writerefs(FILE *fp) | |
+{ | |
+ int ret; | |
+ | |
+ if ((ret = writebranches(fp))) | |
+ return ret; | |
+ return writetags(fp); | |
+} | |
+ | |
+int | |
main(int argc, char *argv[]) | |
{ | |
git_object *obj = NULL; | |
+ git_branch_iterator *it = NULL; | |
+ git_branch_t branch; | |
+ git_reference *ref = NULL; | |
+ const char *branchname = NULL; | |
const git_error *e = NULL; | |
FILE *fp, *fpread; | |
char path[PATH_MAX], *p; | |
@@ -827,15 +1017,43 @@ main(int argc, char *argv[]) | |
hasreadme = !git_revparse_single(&obj, repo, "HEAD:README"); | |
git_object_free(obj); | |
+ /* log for HEAD */ | |
fp = efopen("log.html", "w"); | |
writeheader(fp); | |
- writelog(fp); | |
+ writelog(fp, "HEAD"); | |
writefooter(fp); | |
fclose(fp); | |
+ /* log for local branches */ | |
+ if (git_branch_iterator_new(&it, repo, GIT_BRANCH_LOCAL)) | |
+ err(1, "git_branch_iterator_new"); | |
+ | |
+ while (!git_branch_next(&ref, &branch, it)) { | |
+ if (git_branch_name(&branchname, ref)) | |
+ continue; | |
+ | |
+ snprintf(path, sizeof(path), "log-%s.html", branchname); | |
+ | |
+ fp = efopen(path, "w"); | |
+ writeheader(fp); | |
+ writelog(fp, branchname); | |
+ writefooter(fp); | |
+ fclose(fp); | |
+ } | |
+ git_reference_free(ref); | |
+ git_branch_iterator_free(it); | |
+ | |
+ /* files for HEAD */ | |
fp = efopen("files.html", "w"); | |
writeheader(fp); | |
- writefiles(fp); | |
+ writefiles(fp, "HEAD"); | |
+ writefooter(fp); | |
+ fclose(fp); | |
+ | |
+ /* summary page with branches and tags */ | |
+ fp = efopen("refs.html", "w"); | |
+ writeheader(fp); | |
+ writerefs(fp); | |
writefooter(fp); | |
fclose(fp); | |