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 cleanupM · 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, " <<a href=\"mailto:");
51+ fputs(" <<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, " <");
132+ fputs(" <", fp);
133 xmlencode(fp, ci->author->email, strlen(ci->author->email));
134- fprintf(fp, ">\nDate: ");
135+ fputs(">\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