File Coverage

deps/libgit2/src/diff_stats.c
Criterion Covered Total %
statement 122 169 72.1
branch 72 124 58.0
condition n/a
subroutine n/a
pod n/a
total 194 293 66.2


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