Line Flow Count Block(s) Source
1 - /*
2 - * Copyright (C) the libgit2 contributors. All rights reserved.
3 - *
4 - * This file is part of libgit2, distributed under the GNU GPL v2 with
5 - * a Linking Exception. For full terms see the included COPYING file.
6 - */
7 -
8 - #include "common.h"
9 -
10 - #include "vector.h"
11 - #include "diff.h"
12 - #include "patch_generate.h"
13 -
14 - #define DIFF_RENAME_FILE_SEPARATOR " => "
15 - #define STATS_FULL_MIN_SCALE 7
16 -
17 - typedef struct {
18 - size_t insertions;
19 - size_t deletions;
20 - } diff_file_stats;
21 -
22 - struct git_diff_stats {
23 - git_diff *diff;
24 - diff_file_stats *filestats;
25 -
26 - size_t files_changed;
27 - size_t insertions;
28 - size_t deletions;
29 - size_t renames;
30 -
31 - size_t max_name;
32 - size_t max_filestat;
33 - int max_digits;
34 - };
35 -
36 32 2 static int digits_for_value(size_t val)
37 - {
38 32 2 int count = 1;
39 32 2 size_t placevalue = 10;
40 -
41 34 2,4 while (val >= placevalue) {
42 2 3 ++count;
43 2 3 placevalue *= 10;
44 - }
45 -
46 32 5 return count;
47 - }
48 -
49 44 2 static int diff_file_stats_full_to_buf(
50 - git_buf *out,
51 - const git_diff_delta *delta,
52 - const diff_file_stats *filestat,
53 - const git_diff_stats *stats,
54 - size_t width)
55 - {
56 44 2 const char *old_path = NULL, *new_path = NULL;
57 - size_t padding;
58 - git_object_size_t old_size, new_size;
59 -
60 44 2 old_path = delta->old_file.path;
61 44 2 new_path = delta->new_file.path;
62 44 2 old_size = delta->old_file.size;
63 44 2 new_size = delta->new_file.size;
64 -
65 44 2 if (strcmp(old_path, new_path) != 0) {
66 - size_t common_dirlen;
67 - int error;
68 -
69 6 3 padding = stats->max_name - strlen(old_path) - strlen(new_path);
70 -
71 6 3-5 if ((common_dirlen = git_path_common_dirlen(old_path, new_path)) &&
72 - common_dirlen <= INT_MAX) {
73 1 6 error = git_buf_printf(out, " %.*s{%s"DIFF_RENAME_FILE_SEPARATOR"%s}",
74 - (int) common_dirlen, old_path,
75 - old_path + common_dirlen,
76 - new_path + common_dirlen);
77 - } else {
78 5 7 error = git_buf_printf(out, " %s" DIFF_RENAME_FILE_SEPARATOR "%s",
79 - old_path, new_path);
80 - }
81 -
82 6 8 if (error < 0)
83 6 9,10 goto on_error;
84 - } else {
85 38 11,12 if (git_buf_printf(out, " %s", old_path) < 0)
86 ##### 13 goto on_error;
87 -
88 38 14 padding = stats->max_name - strlen(old_path);
89 -
90 38 14 if (stats->renames > 0)
91 1 15 padding += strlen(DIFF_RENAME_FILE_SEPARATOR);
92 - }
93 -
94 44 16,17,19 if (git_buf_putcn(out, ' ', padding) < 0 ||
95 44 18 git_buf_puts(out, " | ") < 0)
96 - goto on_error;
97 -
98 44 20 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
99 3 21,22 if (git_buf_printf(out,
100 - "Bin %" PRId64 " -> %" PRId64 " bytes", old_size, new_size) < 0)
101 ##### 23 goto on_error;
102 - }
103 - else {
104 41 24,25 if (git_buf_printf(out,
105 - "%*" PRIuZ, stats->max_digits,
106 41 24 filestat->insertions + filestat->deletions) < 0)
107 ##### 26 goto on_error;
108 -
109 41 27,28 if (filestat->insertions || filestat->deletions) {
110 34 29,30 if (git_buf_putc(out, ' ') < 0)
111 ##### 31 goto on_error;
112 -
113 34 32 if (!width) {
114 34 33,34,36 if (git_buf_putcn(out, '+', filestat->insertions) < 0 ||
115 34 35 git_buf_putcn(out, '-', filestat->deletions) < 0)
116 - goto on_error;
117 - } else {
118 ##### 37 size_t total = filestat->insertions + filestat->deletions;
119 ##### 37,37 size_t full = (total * width + stats->max_filestat / 2) /
120 ##### 37 stats->max_filestat;
121 ##### 37 size_t plus = full * filestat->insertions / total;
122 ##### 37 size_t minus = full - plus;
123 -
124 ##### 37,38,40 if (git_buf_putcn(out, '+', max(plus, 1)) < 0 ||
125 ##### 39 git_buf_putcn(out, '-', max(minus, 1)) < 0)
126 - goto on_error;
127 - }
128 - }
129 - }
130 -
131 44 41 git_buf_putc(out, '\n');
132 -
133 - on_error:
134 44 42 return (git_buf_oom(out) ? -1 : 0);
135 - }
136 -
137 3 2 static int diff_file_stats_number_to_buf(
138 - git_buf *out,
139 - const git_diff_delta *delta,
140 - const diff_file_stats *filestats)
141 - {
142 - int error;
143 3 2 const char *path = delta->new_file.path;
144 -
145 3 2 if (delta->flags & GIT_DIFF_FLAG_BINARY)
146 1 3 error = git_buf_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path);
147 - else
148 2 4 error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n",
149 - filestats->insertions, filestats->deletions, path);
150 -
151 3 5 return error;
152 - }
153 -
154 21 2 static int diff_file_stats_summary_to_buf(
155 - git_buf *out,
156 - const git_diff_delta *delta)
157 - {
158 21 2 if (delta->old_file.mode != delta->new_file.mode) {
159 9 3 if (delta->old_file.mode == 0) {
160 4 4,4 git_buf_printf(out, " create mode %06o %s\n",
161 4 4 delta->new_file.mode, delta->new_file.path);
162 - }
163 5 5 else if (delta->new_file.mode == 0) {
164 2 6,6 git_buf_printf(out, " delete mode %06o %s\n",
165 2 6 delta->old_file.mode, delta->old_file.path);
166 - }
167 - else {
168 3 7,7,7 git_buf_printf(out, " mode change %06o => %06o %s\n",
169 3 7,7 delta->old_file.mode, delta->new_file.mode, delta->new_file.path);
170 - }
171 - }
172 -
173 21 8 return 0;
174 - }
175 -
176 32 2 int git_diff_get_stats(
177 - git_diff_stats **out,
178 - git_diff *diff)
179 - {
180 - size_t i, deltas;
181 32 2 size_t total_insertions = 0, total_deletions = 0;
182 32 2 git_diff_stats *stats = NULL;
183 32 2 int error = 0;
184 -
185 32 2-4 assert(out && diff);
186 -
187 32 5 stats = git__calloc(1, sizeof(git_diff_stats));
188 32 6,7 GIT_ERROR_CHECK_ALLOC(stats);
189 -
190 32 8 deltas = git_diff_num_deltas(diff);
191 -
192 32 9 stats->filestats = git__calloc(deltas, sizeof(diff_file_stats));
193 32 10 if (!stats->filestats) {
194 ##### 11 git__free(stats);
195 ##### 12 return -1;
196 - }
197 -
198 32 13 stats->diff = diff;
199 32 13 GIT_REFCOUNT_INC(diff);
200 -
201 81 14,26-28 for (i = 0; i < deltas && !error; ++i) {
202 49 15 git_patch *patch = NULL;
203 49 15 size_t add = 0, remove = 0, namelen;
204 - const git_diff_delta *delta;
205 -
206 49 15,16 if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
207 ##### 17 break;
208 -
209 - /* keep a count of renames because it will affect formatting */
210 49 18 delta = patch->delta;
211 -
212 - /* TODO ugh */
213 49 18 namelen = strlen(delta->new_file.path);
214 49 18 if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
215 6 19 namelen += strlen(delta->old_file.path);
216 6 19 stats->renames++;
217 - }
218 -
219 - /* and, of course, count the line stats */
220 49 20 error = git_patch_line_stats(NULL, &add, &remove, patch);
221 -
222 49 21 git_patch_free(patch);
223 -
224 49 22 stats->filestats[i].insertions = add;
225 49 22 stats->filestats[i].deletions = remove;
226 -
227 49 22 total_insertions += add;
228 49 22 total_deletions += remove;
229 -
230 49 22 if (stats->max_name < namelen)
231 37 23 stats->max_name = namelen;
232 49 24 if (stats->max_filestat < add + remove)
233 30 25 stats->max_filestat = add + remove;
234 - }
235 -
236 32 29 stats->files_changed = deltas;
237 32 29 stats->insertions = total_insertions;
238 32 29 stats->deletions = total_deletions;
239 32 29 stats->max_digits = digits_for_value(stats->max_filestat + 1);
240 -
241 32 30 if (error < 0) {
242 ##### 31 git_diff_stats_free(stats);
243 ##### 32 stats = NULL;
244 - }
245 -
246 32 33 *out = stats;
247 32 33 return error;
248 - }
249 -
250 13 2 size_t git_diff_stats_files_changed(
251 - const git_diff_stats *stats)
252 - {
253 13 2,3 assert(stats);
254 -
255 13 4 return stats->files_changed;
256 - }
257 -
258 13 2 size_t git_diff_stats_insertions(
259 - const git_diff_stats *stats)
260 - {
261 13 2,3 assert(stats);
262 -
263 13 4 return stats->insertions;
264 - }
265 -
266 13 2 size_t git_diff_stats_deletions(
267 - const git_diff_stats *stats)
268 - {
269 13 2,3 assert(stats);
270 -
271 13 4 return stats->deletions;
272 - }
273 -
274 33 2 int git_diff_stats_to_buf(
275 - git_buf *out,
276 - const git_diff_stats *stats,
277 - git_diff_stats_format_t format,
278 - size_t width)
279 - {
280 33 2 int error = 0;
281 - size_t i;
282 - const git_diff_delta *delta;
283 -
284 33 2-4 assert(out && stats);
285 -
286 33 5 if (format & GIT_DIFF_STATS_NUMBER) {
287 5 6,13,14 for (i = 0; i < stats->files_changed; ++i) {
288 3 7,8 if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
289 ##### 9 continue;
290 -
291 3 10 error = diff_file_stats_number_to_buf(
292 3 10 out, delta, &stats->filestats[i]);
293 3 11 if (error < 0)
294 ##### 12 return error;
295 - }
296 - }
297 -
298 33 15 if (format & GIT_DIFF_STATS_FULL) {
299 28 16 if (width > 0) {
300 1 17 if (width > stats->max_name + stats->max_digits + 5)
301 1 18 width -= (stats->max_name + stats->max_digits + 5);
302 1 19 if (width < STATS_FULL_MIN_SCALE)
303 ##### 20 width = STATS_FULL_MIN_SCALE;
304 - }
305 28 21 if (width > stats->max_filestat)
306 1 22 width = 0;
307 -
308 72 23,30,31 for (i = 0; i < stats->files_changed; ++i) {
309 44 24,25 if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
310 ##### 26 continue;
311 -
312 44 27 error = diff_file_stats_full_to_buf(
313 44 27 out, delta, &stats->filestats[i], stats, width);
314 44 28 if (error < 0)
315 ##### 29 return error;
316 - }
317 - }
318 -
319 33 32,33 if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) {
320 31 34-37 git_buf_printf(
321 - out, " %" PRIuZ " file%s changed",
322 31 34 stats->files_changed, stats->files_changed != 1 ? "s" : "");
323 -
324 31 38,39 if (stats->insertions || stats->deletions == 0)
325 30 40-43 git_buf_printf(
326 - out, ", %" PRIuZ " insertion%s(+)",
327 30 40 stats->insertions, stats->insertions != 1 ? "s" : "");
328 -
329 31 44,45 if (stats->deletions || stats->insertions == 0)
330 27 46-49 git_buf_printf(
331 - out, ", %" PRIuZ " deletion%s(-)",
332 27 46 stats->deletions, stats->deletions != 1 ? "s" : "");
333 -
334 31 50 git_buf_putc(out, '\n');
335 -
336 31 51,52 if (git_buf_oom(out))
337 ##### 53 return -1;
338 - }
339 -
340 33 54 if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) {
341 38 55,62,63 for (i = 0; i < stats->files_changed; ++i) {
342 21 56,57 if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
343 ##### 58 continue;
344 -
345 21 59 error = diff_file_stats_summary_to_buf(out, delta);
346 21 60 if (error < 0)
347 ##### 61 return error;
348 - }
349 - }
350 -
351 33 64 return error;
352 - }
353 -
354 32 2 void git_diff_stats_free(git_diff_stats *stats)
355 - {
356 32 2 if (stats == NULL)
357 32 3,7 return;
358 -
359 32 4 git_diff_free(stats->diff); /* bumped refcount in constructor */
360 32 5 git__free(stats->filestats);
361 32 6 git__free(stats);
362 - }