stagit

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 ;)).

Hiltjo Posthuma contact@arjunchoudhary.com

commit: 04d80a0 parent: 7b39128
2 files changed, 149 insertions(+), 39 deletions(-)
Mstagit.c+147-39
Mstyle.css+2-0
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(" -&gt; ", 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 }