From e10f79ad2afa61294c90c4e9ae361aa2b086cf9d Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma Date: Sun, 3 Jan 2016 21:06:03 +0100 Subject: [PATCH] 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 --- TODO | 1 + stagit.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 251 insertions(+), 32 deletions(-) diff --git a/TODO b/TODO index 85b71a9..4f590e9 100644 --- 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 index 9391de6..9d8efad 100644 --- a/stagit.c +++ b/stagit.c @@ -264,7 +264,8 @@ writeheader(FILE *fp) } fputs("\n", fp); fprintf(fp, "Log | ", relpath); - fprintf(fp, "Files", relpath); + fprintf(fp, "Files | ", relpath); + fprintf(fp, "Refs", relpath); if (hasreadme) fprintf(fp, " | README", relpath); 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 = "%d\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, "Merge:"); + fputs("Merge:", fp); for (i = 0; i < count; i++) { git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); fprintf(fp, " %s", @@ -329,9 +329,9 @@ printcommit(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, " <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, "Diffstat:\n"); + fputs("Diffstat:\n", fp); fputs(statsbuf.ptr, fp); } } @@ -431,24 +431,30 @@ printshowfile(struct commitinfo *ci) } git_buf_free(&statsbuf); - fputs( "\n", fp); + fputs("\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, ""); + fputs("", 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 filesize) fputs("


", fp); if (git_blob_is_binary((git_blob *)obj)) { - fprintf(fp, "

Binary file

\n"); + fputs("

Binary file

\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, filename); + snprintf(filepath, sizeof(filepath), "%s/%s", + path, filename); filename = filepath; } @@ -731,42 +742,221 @@ writefilestree(FILE *fp, git_tree *tree, const char *path) } 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("\n" "" "\n\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("
ModeNameSize
", 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("

Branches

\n" + "" + "" + "\n\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("\n", fp); + + relpath = "../"; + + commitinfo_free(ci); + git_reference_free(ref); + git_reference_free(dref); + ref = NULL; + dref = NULL; + } + ret = 0; + +err: fputs("
BranchAgeCommit messageAuthorFiles+-
", fp); + xmlencode(fp, branchname, strlen(branchname)); + fputs("", fp); + if (ci->author) + printtimeshort(fp, &(ci->author->when)); + fputs("", fp); + if (ci->summary) { + fprintf(fp, "", relpath, ci->oid); + if ((len = strlen(ci->summary)) > summarylen) { + xmlencode(fp, ci->summary, summarylen - 1); + fputs("…", fp); + } else { + xmlencode(fp, ci->summary, len); + } + fputs("", fp); + } + fputs("", fp); + if (ci->author) + xmlencode(fp, ci->author->name, strlen(ci->author->name)); + fputs("", fp); + fprintf(fp, "%zu", ci->filecount); + fputs("", fp); + fprintf(fp, "+%zu", ci->addcount); + fputs("", fp); + fprintf(fp, "-%zu", ci->delcount); + fputs("
", 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("

Tags

\n" + "" + "" + "\n\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("\n", fp); + + relpath = "../"; + + commitinfo_free(ci); + git_object_free(obj); + } + fputs("
TagAgeCommit messageAuthorFiles+-
", fp); + xmlencode(fp, tagnames.strings[i], strlen(tagnames.strings[i])); + fputs("", fp); + if (ci->author) + printtimeshort(fp, &(ci->author->when)); + fputs("", fp); + if (ci->summary) { + fprintf(fp, "", relpath, ci->oid); + if ((len = strlen(ci->summary)) > summarylen) { + xmlencode(fp, ci->summary, summarylen - 1); + fputs("…", fp); + } else { + xmlencode(fp, ci->summary, len); + } + fputs("", fp); + } + fputs("", fp); + if (ci->author) + xmlencode(fp, ci->author->name, strlen(ci->author->name)); + fputs("", fp); + fprintf(fp, "%zu", ci->filecount); + fputs("", fp); + fprintf(fp, "+%zu", ci->addcount); + fputs("", fp); + fprintf(fp, "-%zu", ci->delcount); + fputs("
", 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);