stagit

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

Hiltjo Posthuma contact@arjunchoudhary.com

commit: c609a66 parent: f67dc51
2 files changed, 251 insertions(+), 32 deletions(-)
MTODO+1-0
Mstagit.c+250-32
M · TODO +1, -0
1@@ -4,6 +4,7 @@ performance:
2 
3 layout:
4 - make top menu look nicer in links/lynx again.
5+- show tags in log.
6 
7 documentation:
8 - improve mandoc pages.
M · stagit.c +250, -32
  1@@ -264,7 +264,8 @@ writeheader(FILE *fp)
  2 	}
  3 	fputs("<tr><td></td><td>\n", fp);
  4 	fprintf(fp, "<a href=\"%slog.html\">Log</a> | ", relpath);
  5-	fprintf(fp, "<a href=\"%sfiles.html\">Files</a>", relpath);
  6+	fprintf(fp, "<a href=\"%sfiles.html\">Files</a> | ", relpath);
  7+	fprintf(fp, "<a href=\"%srefs.html\">Refs</a>", relpath);
  8 	if (hasreadme)
  9 		fprintf(fp, " | <a href=\"%sfile/README.html\">README</a>", relpath);
 10 	if (haslicense)
 11@@ -283,7 +284,7 @@ writefooter(FILE *fp)
 12 void
 13 writeblobhtml(FILE *fp, const git_blob *blob)
 14 {
 15-	off_t i = 0;
 16+	off_t i;
 17 	size_t n = 1;
 18 	char *nfmt = "<a href=\"#l%d\" id=\"l%d\">%d</a>\n";
 19 	const char *s = git_blob_rawcontent(blob);
 20@@ -293,12 +294,11 @@ writeblobhtml(FILE *fp, const git_blob *blob)
 21 
 22 	if (len) {
 23 		fprintf(fp, nfmt, n, n, n);
 24-		while (i < len - 1) {
 25+		for (i = 0; i < len - 1; i++) {
 26 			if (s[i] == '\n') {
 27 				n++;
 28 				fprintf(fp, nfmt, n, n, n);
 29 			}
 30-			i++;
 31 		}
 32 	}
 33 
 34@@ -319,7 +319,7 @@ printcommit(FILE *fp, struct commitinfo *ci)
 35 
 36 #if 0
 37 	if ((count = (int)git_commit_parentcount(commit)) > 1) {
 38-		fprintf(fp, "<b>Merge:</b>");
 39+		fputs("<b>Merge:</b>", fp);
 40 		for (i = 0; i < count; i++) {
 41 			git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
 42 			fprintf(fp, " <a href=\"%scommit/%s.html\">%s</a>",
 43@@ -329,9 +329,9 @@ printcommit(FILE *fp, struct commitinfo *ci)
 44 	}
 45 #endif
 46 	if (ci->author) {
 47-		fprintf(fp, "<b>Author:</b> ");
 48+		fputs("<b>Author:</b> ", fp);
 49 		xmlencode(fp, ci->author->name, strlen(ci->author->name));
 50-		fprintf(fp, " &lt;<a href=\"mailto:");
 51+		fputs(" &lt;<a href=\"mailto:", fp);
 52 		xmlencode(fp, ci->author->email, strlen(ci->author->email));
 53 		fputs("\">", fp);
 54 		xmlencode(fp, ci->author->email, strlen(ci->author->email));
 55@@ -377,7 +377,7 @@ printshowfile(struct commitinfo *ci)
 56 		if (!git_diff_stats_to_buf(&statsbuf, ci->stats,
 57 		    GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_SHORT, 80)) {
 58 			if (statsbuf.ptr && statsbuf.ptr[0]) {
 59-				fprintf(fp, "<b>Diffstat:</b>\n");
 60+				fputs("<b>Diffstat:</b>\n", fp);
 61 				fputs(statsbuf.ptr, fp);
 62 			}
 63 		}
 64@@ -431,24 +431,30 @@ printshowfile(struct commitinfo *ci)
 65 	}
 66 	git_buf_free(&statsbuf);
 67 
 68-	fputs( "</pre>\n", fp);
 69+	fputs("</pre>\n", fp);
 70 	writefooter(fp);
 71 	fclose(fp);
 72 	return;
 73 }
 74 
 75-void
 76-writelog(FILE *fp)
 77+int
 78+writelog(FILE *fp, const char *branch)
 79 {
 80 	struct commitinfo *ci;
 81+	const git_oid *oid;
 82 	git_revwalk *w = NULL;
 83+	git_object *obj = NULL;
 84 	git_oid id;
 85 	size_t len;
 86 
 87 	mkdir("commit", 0755);
 88 
 89+	if (git_revparse_single(&obj, repo, branch))
 90+		return -1;
 91+	oid = git_object_id(obj);
 92+
 93 	git_revwalk_new(&w, repo);
 94-	git_revwalk_push_head(w);
 95+	git_revwalk_push(w, oid);
 96 	git_revwalk_sorting(w, GIT_SORT_TIME);
 97 	git_revwalk_simplify_first_parent(w);
 98 
 99@@ -491,10 +497,14 @@ writelog(FILE *fp)
100 
101 		commitinfo_free(ci);
102 	}
103-	fprintf(fp, "</tbody></table>");
104+	fputs("</tbody></table>", fp);
105 
106 	git_revwalk_free(w);
107+	git_object_free(obj);
108+
109 	relpath = "";
110+
111+	return 0;
112 }
113 
114 void
115@@ -521,7 +531,7 @@ printcommitatom(FILE *fp, struct commitinfo *ci)
116 
117 #if 0
118 	if ((count = (int)git_commit_parentcount(commit)) > 1) {
119-		fprintf(fp, "Merge:");
120+		fputs("Merge:", fp);
121 		for (i = 0; i < count; i++) {
122 			git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
123 			fprintf(fp, " %s", buf);
124@@ -531,11 +541,11 @@ printcommitatom(FILE *fp, struct commitinfo *ci)
125 #endif
126 
127 	if (ci->author) {
128-		fprintf(fp, "Author: ");
129+		fputs("Author: ", fp);
130 		xmlencode(fp, ci->author->name, strlen(ci->author->name));
131-		fprintf(fp, " &lt;");
132+		fputs(" &lt;", fp);
133 		xmlencode(fp, ci->author->email, strlen(ci->author->email));
134-		fprintf(fp, "&gt;\nDate:   ");
135+		fputs("&gt;\nDate:   ", fp);
136 		printtime(fp, &(ci->author->when));
137 	}
138 	fputc('\n', fp);
139@@ -619,7 +629,7 @@ writeblob(git_object *obj, const char *filename, git_off_t filesize)
140 	fputs("</p><hr/>", fp);
141 
142 	if (git_blob_is_binary((git_blob *)obj)) {
143-		fprintf(fp, "<p>Binary file</p>\n");
144+		fputs("<p>Binary file</p>\n", fp);
145 	} else {
146 		writeblobhtml(fp, (git_blob *)obj);
147 		if (ferror(fp))
148@@ -676,7 +686,7 @@ filemode(git_filemode_t m)
149 }
150 
151 int
152-writefilestree(FILE *fp, git_tree *tree, const char *path)
153+writefilestree(FILE *fp, git_tree *tree, const char *branch, const char *path)
154 {
155 	const git_tree_entry *entry = NULL;
156 	const char *filename;
157@@ -690,15 +700,15 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
158 	for (i = 0; i < count; i++) {
159 		if (!(entry = git_tree_entry_byindex(tree, i)))
160 			return -1;
161-
162-		filename = git_tree_entry_name(entry);
163 		if (git_tree_entry_to_object(&obj, repo, entry))
164 			return -1;
165+		filename = git_tree_entry_name(entry);
166 		switch (git_object_type(obj)) {
167 		case GIT_OBJ_BLOB:
168 			break;
169 		case GIT_OBJ_TREE:
170-			ret = writefilestree(fp, (git_tree *)obj, filename);
171+			ret = writefilestree(fp, (git_tree *)obj, branch,
172+			                     filename);
173 			git_object_free(obj);
174 			if (ret)
175 				return ret;
176@@ -708,7 +718,8 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
177 			continue;
178 		}
179 		if (path[0]) {
180-			snprintf(filepath, sizeof(filepath), "%s/%s", path, filename);
181+			snprintf(filepath, sizeof(filepath), "%s/%s",
182+			         path, filename);
183 			filename = filepath;
184 		}
185 
186@@ -731,42 +742,221 @@ writefilestree(FILE *fp, git_tree *tree, const char *path)
187 }
188 
189 int
190-writefiles(FILE *fp)
191+writefiles(FILE *fp, const char *branch)
192 {
193 	const git_oid *id;
194 	git_tree *tree = NULL;
195 	git_object *obj = NULL;
196 	git_commit *commit = NULL;
197+	int ret = -1;
198 
199 	fputs("<table id=\"files\"><thead>\n<tr>"
200 	      "<td>Mode</td><td>Name</td><td class=\"num\">Size</td>"
201 	      "</tr>\n</thead><tbody>\n", fp);
202 
203-	if (git_revparse_single(&obj, repo, "HEAD"))
204-		return -1;
205+	if (git_revparse_single(&obj, repo, branch))
206+		goto err;
207 	id = git_object_id(obj);
208 	if (git_commit_lookup(&commit, repo, id))
209-		return -1;
210+		goto err;
211 	if (git_commit_tree(&tree, commit)) {
212 		git_commit_free(commit);
213-		return -1;
214+		goto err;
215 	}
216-	git_commit_free(commit);
217+	ret = writefilestree(fp, tree, branch, "");
218 
219-	writefilestree(fp, tree, "");
220+err:
221+	fputs("</tbody></table>", fp);
222 
223+	git_object_free(obj);
224 	git_commit_free(commit);
225 	git_tree_free(tree);
226 
227+	return ret;
228+}
229+
230+int
231+writebranches(FILE *fp)
232+{
233+	struct commitinfo *ci;
234+	git_branch_iterator *it = NULL;
235+	git_branch_t branch;
236+	git_reference *ref = NULL, *dref = NULL;
237+	const git_oid *id = NULL;
238+	const char *branchname = NULL;
239+	size_t len;
240+	int ret = -1;
241+
242+	/* log for local branches */
243+	if (git_branch_iterator_new(&it, repo, GIT_BRANCH_LOCAL))
244+		return -1;
245+
246+	fputs("<h2>Branches</h2><table id=\"branches\"><thead>\n<tr><td>Branch</td><td>Age</td>"
247+		  "<td>Commit message</td>"
248+		  "<td>Author</td><td>Files</td><td class=\"num\">+</td>"
249+		  "<td class=\"num\">-</td></tr>\n</thead><tbody>\n", fp);
250+
251+	while (!git_branch_next(&ref, &branch, it)) {
252+		if (git_branch_name(&branchname, ref))
253+			continue;
254+
255+		id = NULL;
256+		switch (git_reference_type(ref)) {
257+		case GIT_REF_SYMBOLIC:
258+			if (git_reference_resolve(&dref, ref))
259+				goto err;
260+			id = git_reference_target(dref);
261+			break;
262+		case GIT_REF_OID:
263+			id = git_reference_target(ref);
264+			break;
265+		default:
266+			continue;
267+		}
268+		if (!id)
269+			goto err;
270+		if (!(ci = commitinfo_getbyoid(id)))
271+			break;
272+
273+		relpath = "";
274+
275+		fputs("<tr><td>", fp);
276+		xmlencode(fp, branchname, strlen(branchname));
277+		fputs("</td><td>", fp);
278+		if (ci->author)
279+			printtimeshort(fp, &(ci->author->when));
280+		fputs("</td><td>", fp);
281+		if (ci->summary) {
282+			fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
283+			if ((len = strlen(ci->summary)) > summarylen) {
284+				xmlencode(fp, ci->summary, summarylen - 1);
285+				fputs("…", fp);
286+			} else {
287+				xmlencode(fp, ci->summary, len);
288+			}
289+			fputs("</a>", fp);
290+		}
291+		fputs("</td><td>", fp);
292+		if (ci->author)
293+			xmlencode(fp, ci->author->name, strlen(ci->author->name));
294+		fputs("</td><td class=\"num\">", fp);
295+		fprintf(fp, "%zu", ci->filecount);
296+		fputs("</td><td class=\"num\">", fp);
297+		fprintf(fp, "+%zu", ci->addcount);
298+		fputs("</td><td class=\"num\">", fp);
299+		fprintf(fp, "-%zu", ci->delcount);
300+		fputs("</td></tr>\n", fp);
301+
302+		relpath = "../";
303+
304+		commitinfo_free(ci);
305+		git_reference_free(ref);
306+		git_reference_free(dref);
307+		ref = NULL;
308+		dref = NULL;
309+	}
310+	ret = 0;
311+
312+err:
313 	fputs("</tbody></table>", fp);
314+	git_reference_free(ref);
315+	git_reference_free(dref);
316+	git_branch_iterator_free(it);
317+
318+	return ret;
319+}
320+
321+int
322+tagcompare(void *s1, void *s2)
323+{
324+	return strcmp(*(char **)s1, *(char **)s2);
325+}
326+
327+int
328+writetags(FILE *fp)
329+{
330+	struct commitinfo *ci;
331+	git_strarray tagnames;
332+	git_object *obj = NULL;
333+	const git_oid *id = NULL;
334+	size_t i, len;
335+
336+	fputs("<h2>Tags</h2><table id=\"branches\"><thead>\n<tr><td>Tag</td>"
337+	      "<td>Age</td><td>Commit message</td>"
338+	      "<td>Author</td><td>Files</td><td class=\"num\">+</td>"
339+	      "<td class=\"num\">-</td></tr>\n</thead><tbody>\n", fp);
340+
341+	/* summary page with branches and tags */
342+	memset(&tagnames, 0, sizeof(tagnames));
343+	git_tag_list(&tagnames, repo);
344+	/* sort names */
345+	qsort(tagnames.strings, tagnames.count, sizeof(char *),
346+	      (int (*)(const void *, const void *))&tagcompare);
347+	for (i = 0; i < tagnames.count; i++) {
348+		if (git_revparse_single(&obj, repo, tagnames.strings[i]))
349+			continue;
350+		id = git_object_id(obj);
351+		if (!(ci = commitinfo_getbyoid(id)))
352+			break;
353+
354+		relpath = "";
355+
356+		fputs("<tr><td>", fp);
357+		xmlencode(fp, tagnames.strings[i], strlen(tagnames.strings[i]));
358+		fputs("</td><td>", fp);
359+		if (ci->author)
360+			printtimeshort(fp, &(ci->author->when));
361+		fputs("</td><td>", fp);
362+		if (ci->summary) {
363+			fprintf(fp, "<a href=\"%scommit/%s.html\">", relpath, ci->oid);
364+			if ((len = strlen(ci->summary)) > summarylen) {
365+				xmlencode(fp, ci->summary, summarylen - 1);
366+				fputs("…", fp);
367+			} else {
368+				xmlencode(fp, ci->summary, len);
369+			}
370+			fputs("</a>", fp);
371+		}
372+		fputs("</td><td>", fp);
373+		if (ci->author)
374+			xmlencode(fp, ci->author->name, strlen(ci->author->name));
375+		fputs("</td><td class=\"num\">", fp);
376+		fprintf(fp, "%zu", ci->filecount);
377+		fputs("</td><td class=\"num\">", fp);
378+		fprintf(fp, "+%zu", ci->addcount);
379+		fputs("</td><td class=\"num\">", fp);
380+		fprintf(fp, "-%zu", ci->delcount);
381+		fputs("</td></tr>\n", fp);
382+
383+		relpath = "../";
384+
385+		commitinfo_free(ci);
386+		git_object_free(obj);
387+	}
388+	fputs("</tbody></table>", fp);
389+	git_strarray_free(&tagnames);
390 
391 	return 0;
392 }
393 
394+int
395+writerefs(FILE *fp)
396+{
397+	int ret;
398+
399+	if ((ret = writebranches(fp)))
400+		return ret;
401+	return writetags(fp);
402+}
403+
404 int
405 main(int argc, char *argv[])
406 {
407 	git_object *obj = NULL;
408+	git_branch_iterator *it = NULL;
409+	git_branch_t branch;
410+	git_reference *ref = NULL;
411+	const char *branchname = NULL;
412 	const git_error *e = NULL;
413 	FILE *fp, *fpread;
414 	char path[PATH_MAX], *p;
415@@ -827,15 +1017,43 @@ main(int argc, char *argv[])
416 	hasreadme = !git_revparse_single(&obj, repo, "HEAD:README");
417 	git_object_free(obj);
418 
419+	/* log for HEAD */
420 	fp = efopen("log.html", "w");
421 	writeheader(fp);
422-	writelog(fp);
423+	writelog(fp, "HEAD");
424 	writefooter(fp);
425 	fclose(fp);
426 
427+	/* log for local branches */
428+	if (git_branch_iterator_new(&it, repo, GIT_BRANCH_LOCAL))
429+		err(1, "git_branch_iterator_new");
430+
431+	while (!git_branch_next(&ref, &branch, it)) {
432+		if (git_branch_name(&branchname, ref))
433+			continue;
434+
435+		snprintf(path, sizeof(path), "log-%s.html", branchname);
436+
437+		fp = efopen(path, "w");
438+		writeheader(fp);
439+		writelog(fp, branchname);
440+		writefooter(fp);
441+		fclose(fp);
442+	}
443+	git_reference_free(ref);
444+	git_branch_iterator_free(it);
445+
446+	/* files for HEAD */
447 	fp = efopen("files.html", "w");
448 	writeheader(fp);
449-	writefiles(fp);
450+	writefiles(fp, "HEAD");
451+	writefooter(fp);
452+	fclose(fp);
453+
454+	/* summary page with branches and tags */
455+	fp = efopen("refs.html", "w");
456+	writeheader(fp);
457+	writerefs(fp);
458 	writefooter(fp);
459 	fclose(fp);
460