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