2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
8 #include "diff_stats.h"
14 #include "patch_generate.h"
16 #define DIFF_RENAME_FILE_SEPARATOR " => "
17 #define STATS_FULL_MIN_SCALE 7
24 struct git_diff_stats
{
26 diff_file_stats
*filestats
;
38 static int digits_for_value(size_t val
)
41 size_t placevalue
= 10;
43 while (val
>= placevalue
) {
51 static int diff_file_stats_full_to_buf(
53 const git_diff_delta
*delta
,
54 const diff_file_stats
*filestat
,
55 const git_diff_stats
*stats
,
58 const char *old_path
= NULL
, *new_path
= NULL
, *adddel_path
= NULL
;
60 git_object_size_t old_size
, new_size
;
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
;
67 if (old_path
&& new_path
&& strcmp(old_path
, new_path
) != 0) {
71 padding
= stats
->max_name
- strlen(old_path
) - strlen(new_path
);
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
);
80 error
= git_str_printf(out
, " %s" DIFF_RENAME_FILE_SEPARATOR
"%s",
87 adddel_path
= new_path
? new_path
: old_path
;
88 if (git_str_printf(out
, " %s", adddel_path
) < 0)
91 padding
= stats
->max_name
- strlen(adddel_path
);
93 if (stats
->renames
> 0)
94 padding
+= strlen(DIFF_RENAME_FILE_SEPARATOR
);
97 if (git_str_putcn(out
, ' ', padding
) < 0 ||
98 git_str_puts(out
, " | ") < 0)
101 if (delta
->flags
& GIT_DIFF_FLAG_BINARY
) {
102 if (git_str_printf(out
,
103 "Bin %" PRId64
" -> %" PRId64
" bytes", old_size
, new_size
) < 0)
107 if (git_str_printf(out
,
108 "%*" PRIuZ
, stats
->max_digits
,
109 filestat
->insertions
+ filestat
->deletions
) < 0)
112 if (filestat
->insertions
|| filestat
->deletions
) {
113 if (git_str_putc(out
, ' ') < 0)
117 if (git_str_putcn(out
, '+', filestat
->insertions
) < 0 ||
118 git_str_putcn(out
, '-', filestat
->deletions
) < 0)
121 size_t total
= filestat
->insertions
+ filestat
->deletions
;
122 size_t full
= (total
* width
+ stats
->max_filestat
/ 2) /
124 size_t plus
= full
* filestat
->insertions
/ total
;
125 size_t minus
= full
- plus
;
127 if (git_str_putcn(out
, '+', max(plus
, 1)) < 0 ||
128 git_str_putcn(out
, '-', max(minus
, 1)) < 0)
134 git_str_putc(out
, '\n');
137 return (git_str_oom(out
) ? -1 : 0);
140 static int diff_file_stats_number_to_buf(
142 const git_diff_delta
*delta
,
143 const diff_file_stats
*filestats
)
146 const char *path
= delta
->new_file
.path
;
148 if (delta
->flags
& GIT_DIFF_FLAG_BINARY
)
149 error
= git_str_printf(out
, "%-8c" "%-8c" "%s\n", '-', '-', path
);
151 error
= git_str_printf(out
, "%-8" PRIuZ
"%-8" PRIuZ
"%s\n",
152 filestats
->insertions
, filestats
->deletions
, path
);
157 static int diff_file_stats_summary_to_buf(
159 const git_diff_delta
*delta
)
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
);
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
);
171 git_str_printf(out
, " mode change %06o => %06o %s\n",
172 delta
->old_file
.mode
, delta
->new_file
.mode
, delta
->new_file
.path
);
179 int git_diff_get_stats(
180 git_diff_stats
**out
,
184 size_t total_insertions
= 0, total_deletions
= 0;
185 git_diff_stats
*stats
= NULL
;
189 GIT_ASSERT_ARG(diff
);
191 stats
= git__calloc(1, sizeof(git_diff_stats
));
192 GIT_ERROR_CHECK_ALLOC(stats
);
194 deltas
= git_diff_num_deltas(diff
);
196 stats
->filestats
= git__calloc(deltas
, sizeof(diff_file_stats
));
197 if (!stats
->filestats
) {
203 GIT_REFCOUNT_INC(diff
);
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
;
210 if ((error
= git_patch_from_diff(&patch
, diff
, i
)) < 0)
213 /* keep a count of renames because it will affect formatting */
214 delta
= patch
->delta
;
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
);
223 /* and, of course, count the line stats */
224 error
= git_patch_line_stats(NULL
, &add
, &remove
, patch
);
226 git_patch_free(patch
);
228 stats
->filestats
[i
].insertions
= add
;
229 stats
->filestats
[i
].deletions
= remove
;
231 total_insertions
+= add
;
232 total_deletions
+= remove
;
234 if (stats
->max_name
< namelen
)
235 stats
->max_name
= namelen
;
236 if (stats
->max_filestat
< add
+ remove
)
237 stats
->max_filestat
= add
+ remove
;
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);
246 git_diff_stats_free(stats
);
254 size_t git_diff_stats_files_changed(
255 const git_diff_stats
*stats
)
257 GIT_ASSERT_ARG(stats
);
259 return stats
->files_changed
;
262 size_t git_diff_stats_insertions(
263 const git_diff_stats
*stats
)
265 GIT_ASSERT_ARG(stats
);
267 return stats
->insertions
;
270 size_t git_diff_stats_deletions(
271 const git_diff_stats
*stats
)
273 GIT_ASSERT_ARG(stats
);
275 return stats
->deletions
;
278 int git_diff_stats_to_buf(
280 const git_diff_stats
*stats
,
281 git_diff_stats_format_t format
,
284 GIT_BUF_WRAP_PRIVATE(out
, git_diff__stats_to_buf
, stats
, format
, width
);
287 int git_diff__stats_to_buf(
289 const git_diff_stats
*stats
,
290 git_diff_stats_format_t format
,
295 const git_diff_delta
*delta
;
298 GIT_ASSERT_ARG(stats
);
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
)
305 error
= diff_file_stats_number_to_buf(
306 out
, delta
, &stats
->filestats
[i
]);
312 if (format
& GIT_DIFF_STATS_FULL
) {
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
;
319 if (width
> stats
->max_filestat
)
322 for (i
= 0; i
< stats
->files_changed
; ++i
) {
323 if ((delta
= git_diff_get_delta(stats
->diff
, i
)) == NULL
)
326 error
= diff_file_stats_full_to_buf(
327 out
, delta
, &stats
->filestats
[i
], stats
, width
);
333 if (format
& GIT_DIFF_STATS_FULL
|| format
& GIT_DIFF_STATS_SHORT
) {
335 out
, " %" PRIuZ
" file%s changed",
336 stats
->files_changed
, stats
->files_changed
!= 1 ? "s" : "");
338 if (stats
->insertions
|| stats
->deletions
== 0)
340 out
, ", %" PRIuZ
" insertion%s(+)",
341 stats
->insertions
, stats
->insertions
!= 1 ? "s" : "");
343 if (stats
->deletions
|| stats
->insertions
== 0)
345 out
, ", %" PRIuZ
" deletion%s(-)",
346 stats
->deletions
, stats
->deletions
!= 1 ? "s" : "");
348 git_str_putc(out
, '\n');
350 if (git_str_oom(out
))
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
)
359 error
= diff_file_stats_summary_to_buf(out
, delta
);
368 void git_diff_stats_free(git_diff_stats
*stats
)
373 git_diff_free(stats
->diff
); /* bumped refcount in constructor */
374 git__free(stats
->filestats
);