stagit-responsive

My mobile friendly fork of stagit
Log | Files | Refs | README | LICENSE

commit e10f79ad2afa61294c90c4e9ae361aa2b086cf9d
parent c226554b64a9529296b690d827966ccf139336bd
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
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:
MTODO | 1+
Mstagit.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>", 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 = "<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, " &lt;<a href=\"mailto:");
+		fputs(" &lt;<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, " &lt;");
+		fputs(" &lt;", fp);
 		xmlencode(fp, ci->author->email, strlen(ci->author->email));
-		fprintf(fp, "&gt;\nDate:   ");
+		fputs("&gt;\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("</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, 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("<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><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);
+
+	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, ci->oid);
+			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->name));
+		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, ci->oid);
+			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->name));
+		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);