optimization: read stats once and remember it
for an initial run and new commits this speeds stagit up a bit: on an initial run of sbase goes from about 4 seconds to 2.8 on my machine. now we can't use statsbuf, so create the stats string ourselves, while at it color the + and - using a style (can be disabled for the color-haters out there ;)).M · stagit.c +147, -39
1@@ -15,6 +15,13 @@
2 #include "compat.h"
3 #include "config.h"
4
5+struct deltainfo {
6+ git_patch *patch;
7+
8+ size_t addcount;
9+ size_t delcount;
10+};
11+
12 struct commitinfo {
13 const git_oid *id;
14
15@@ -25,16 +32,18 @@ struct commitinfo {
16 const char *summary;
17 const char *msg;
18
19- git_diff_stats *stats;
20- git_diff *diff;
21- git_commit *commit;
22- git_commit *parent;
23- git_tree *commit_tree;
24- git_tree *parent_tree;
25+ git_diff *diff;
26+ git_commit *commit;
27+ git_commit *parent;
28+ git_tree *commit_tree;
29+ git_tree *parent_tree;
30
31 size_t addcount;
32 size_t delcount;
33 size_t filecount;
34+
35+ struct deltainfo **deltas;
36+ size_t ndeltas;
37 };
38
39 static git_repository *repo;
40@@ -48,13 +57,96 @@ static char description[255];
41 static char cloneurl[1024];
42 static int haslicense, hasreadme, hassubmodules;
43
44+void
45+deltainfo_free(struct deltainfo *di)
46+{
47+ if (!di)
48+ return;
49+ git_patch_free(di->patch);
50+ di->patch = NULL;
51+ free(di);
52+}
53+
54+int
55+commitinfo_getstats(struct commitinfo *ci)
56+{
57+ struct deltainfo *di;
58+ const git_diff_delta *delta;
59+ const git_diff_hunk *hunk;
60+ const git_diff_line *line;
61+ git_patch *patch = NULL;
62+ size_t ndeltas, nhunks, nhunklines;
63+ size_t i, j, k;
64+
65+ ndeltas = git_diff_num_deltas(ci->diff);
66+ if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
67+ err(1, "calloc");
68+
69+ for (i = 0; i < ndeltas; i++) {
70+ if (!(di = calloc(1, sizeof(struct deltainfo))))
71+ err(1, "calloc");
72+ if (git_patch_from_diff(&patch, ci->diff, i)) {
73+ git_patch_free(patch);
74+ goto err;
75+ }
76+ di->patch = patch;
77+ ci->deltas[i] = di;
78+
79+ delta = git_patch_get_delta(patch);
80+
81+ /* check binary data */
82+ if (delta->flags & GIT_DIFF_FLAG_BINARY)
83+ continue;
84+
85+ nhunks = git_patch_num_hunks(patch);
86+ for (j = 0; j < nhunks; j++) {
87+ if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
88+ break;
89+ for (k = 0; ; k++) {
90+ if (git_patch_get_line_in_hunk(&line, patch, j, k))
91+ break;
92+ if (line->old_lineno == -1) {
93+ di->addcount++;
94+ ci->addcount++;
95+ } else if (line->new_lineno == -1) {
96+ di->delcount++;
97+ ci->delcount++;
98+ }
99+ }
100+ }
101+ }
102+ ci->ndeltas = i;
103+ ci->filecount = i;
104+
105+ return 0;
106+
107+err:
108+ if (ci->deltas)
109+ for (i = 0; i < ci->ndeltas; i++)
110+ deltainfo_free(ci->deltas[i]);
111+ free(ci->deltas);
112+ ci->deltas = NULL;
113+ ci->ndeltas = 0;
114+ ci->addcount = 0;
115+ ci->delcount = 0;
116+ ci->filecount = 0;
117+
118+ return -1;
119+}
120+
121 void
122 commitinfo_free(struct commitinfo *ci)
123 {
124+ size_t i;
125+
126 if (!ci)
127 return;
128
129- git_diff_stats_free(ci->stats);
130+ if (ci->deltas)
131+ for (i = 0; i < ci->ndeltas; i++)
132+ deltainfo_free(ci->deltas[i]);
133+ free(ci->deltas);
134+ ci->deltas = NULL;
135 git_diff_free(ci->diff);
136 git_tree_free(ci->commit_tree);
137 git_tree_free(ci->parent_tree);
138@@ -66,6 +158,7 @@ commitinfo_getbyoid(const git_oid *id)
139 {
140 struct commitinfo *ci;
141 git_diff_options opts;
142+ const git_oid *oid;
143 int error;
144
145 if (!(ci = calloc(1, sizeof(struct commitinfo))))
146@@ -82,26 +175,24 @@ commitinfo_getbyoid(const git_oid *id)
147 ci->summary = git_commit_summary(ci->commit);
148 ci->msg = git_commit_message(ci->commit);
149
150- if ((error = git_commit_tree(&(ci->commit_tree), ci->commit)))
151+ oid = git_commit_tree_id(ci->commit);
152+ if ((error = git_tree_lookup(&(ci->commit_tree), repo, oid)))
153 goto err;
154 if (!(error = git_commit_parent(&(ci->parent), ci->commit, 0))) {
155- if ((error = git_commit_tree(&(ci->parent_tree), ci->parent)))
156- goto err;
157- } else {
158- ci->parent = NULL;
159- ci->parent_tree = NULL;
160+ oid = git_commit_tree_id(ci->parent);
161+ if ((error = git_tree_lookup(&(ci->parent_tree), repo, oid))) {
162+ ci->parent = NULL;
163+ ci->parent_tree = NULL;
164+ }
165 }
166
167 git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
168 opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
169 if ((error = git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts)))
170 goto err;
171- if (git_diff_get_stats(&(ci->stats), ci->diff))
172- goto err;
173
174- ci->addcount = git_diff_stats_insertions(ci->stats);
175- ci->delcount = git_diff_stats_deletions(ci->stats);
176- ci->filecount = git_diff_stats_files_changed(ci->stats);
177+ if (commitinfo_getstats(ci) == -1)
178+ goto err;
179
180 return ci;
181
182@@ -332,33 +423,55 @@ printshowfile(FILE *fp, struct commitinfo *ci)
183 const git_diff_hunk *hunk;
184 const git_diff_line *line;
185 git_patch *patch;
186- git_buf statsbuf;
187- size_t ndeltas, nhunks, nhunklines;
188+ size_t nhunks, nhunklines, changed, add, del, total;
189 size_t i, j, k;
190+ char linestr[80];
191
192 printcommit(fp, ci);
193
194- memset(&statsbuf, 0, sizeof(statsbuf));
195+ if (!ci->deltas)
196+ return;
197
198 /* diff stat */
199- if (ci->stats &&
200- !git_diff_stats_to_buf(&statsbuf, ci->stats,
201- GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_SHORT, 80)) {
202- if (statsbuf.ptr && statsbuf.ptr[0]) {
203- fputs("<b>Diffstat:</b>\n", fp);
204- xmlencode(fp, statsbuf.ptr, strlen(statsbuf.ptr));
205+ fputs("<b>Diffstat:</b>\n<table>", fp);
206+ for (i = 0; i < ci->ndeltas; i++) {
207+ delta = git_patch_get_delta(ci->deltas[i]->patch);
208+ fputs("<tr><td>", fp);
209+ xmlencode(fp, delta->old_file.path, strlen(delta->old_file.path));
210+ if (strcmp(delta->old_file.path, delta->new_file.path)) {
211+ fputs(" -> ", fp);
212+ xmlencode(fp, delta->new_file.path, strlen(delta->new_file.path));
213+ }
214+
215+ add = ci->deltas[i]->addcount;
216+ del = ci->deltas[i]->delcount;
217+ changed = add + del;
218+ total = sizeof(linestr) - 2;
219+ if (changed > total) {
220+ if (add)
221+ add = ((float)total / changed * add) + 1;
222+ if (del)
223+ del = ((float)total / changed * del) + 1;
224 }
225+ memset(&linestr, '+', add);
226+ memset(&linestr[add], '-', del);
227+
228+ fprintf(fp, "</td><td> | %zu <span class=\"i\">",
229+ ci->deltas[i]->addcount + ci->deltas[i]->delcount);
230+ fwrite(&linestr, 1, add, fp);
231+ fputs("</span><span class=\"d\">", fp);
232+ fwrite(&linestr[add], 1, del, fp);
233+ fputs("</span></td></tr>\n", fp);
234 }
235+ fprintf(fp, "</table>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
236+ ci->filecount, ci->filecount == 1 ? "" : "s",
237+ ci->addcount, ci->addcount == 1 ? "" : "s",
238+ ci->delcount, ci->delcount == 1 ? "" : "s");
239
240 fputs("<hr/>", fp);
241
242- ndeltas = git_diff_num_deltas(ci->diff);
243- for (i = 0; i < ndeltas; i++) {
244- if (git_patch_from_diff(&patch, ci->diff, i)) {
245- git_patch_free(patch);
246- break;
247- }
248-
249+ for (i = 0; i < ci->ndeltas; i++) {
250+ patch = ci->deltas[i]->patch;
251 delta = git_patch_get_delta(patch);
252 fprintf(fp, "<b>diff --git a/<a href=\"%sfile/%s.html\">%s</a> b/<a href=\"%sfile/%s.html\">%s</a></b>\n",
253 relpath, delta->old_file.path, delta->old_file.path,
254@@ -367,7 +480,6 @@ printshowfile(FILE *fp, struct commitinfo *ci)
255 /* check binary data */
256 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
257 fputs("Binary files differ\n", fp);
258- git_patch_free(patch);
259 continue;
260 }
261
262@@ -396,11 +508,7 @@ printshowfile(FILE *fp, struct commitinfo *ci)
263 fputs("</a>", fp);
264 }
265 }
266- git_patch_free(patch);
267 }
268- git_buf_free(&statsbuf);
269-
270- return;
271 }
272
273 int
M · style.css
+2, -0 1@@ -79,10 +79,12 @@ pre a.h {
2 color: #00a;
3 }
4
5+span.i,
6 pre a.i {
7 color: #070;
8 }
9
10+span.d,
11 pre a.d {
12 color: #e00;
13 }