File Coverage

deps/libgit2/src/libgit2/diff_stats.c
Criterion Covered Total %
statement 126 174 72.4
branch 78 136 57.3
condition n/a
subroutine n/a
pod n/a
total 204 310 65.8


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