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.
12 #include "patch_generate.h"
14 #define DIFF_RENAME_FILE_SEPARATOR " => "
15 #define STATS_FULL_MIN_SCALE 7
22 struct git_diff_stats
{
24 diff_file_stats
*filestats
;
36 static int digits_for_value(size_t val
)
39 size_t placevalue
= 10;
41 while (val
>= placevalue
) {
49 static int diff_file_stats_full_to_buf(
51 const git_diff_delta
*delta
,
52 const diff_file_stats
*filestat
,
53 const git_diff_stats
*stats
,
56 const char *old_path
= NULL
, *new_path
= NULL
, *adddel_path
= NULL
;
58 git_object_size_t old_size
, new_size
;
60 old_path
= delta
->old_file
.path
;
61 new_path
= delta
->new_file
.path
;
62 old_size
= delta
->old_file
.size
;
63 new_size
= delta
->new_file
.size
;
65 if (old_path
&& new_path
&& strcmp(old_path
, new_path
) != 0) {
69 padding
= stats
->max_name
- strlen(old_path
) - strlen(new_path
);
71 if ((common_dirlen
= git_path_common_dirlen(old_path
, new_path
)) &&
72 common_dirlen
<= INT_MAX
) {
73 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
);
78 error
= git_buf_printf(out
, " %s" DIFF_RENAME_FILE_SEPARATOR
"%s",
85 adddel_path
= new_path
? new_path
: old_path
;
86 if (git_buf_printf(out
, " %s", adddel_path
) < 0)
89 padding
= stats
->max_name
- strlen(adddel_path
);
91 if (stats
->renames
> 0)
92 padding
+= strlen(DIFF_RENAME_FILE_SEPARATOR
);
95 if (git_buf_putcn(out
, ' ', padding
) < 0 ||
96 git_buf_puts(out
, " | ") < 0)
99 if (delta
->flags
& GIT_DIFF_FLAG_BINARY
) {
100 if (git_buf_printf(out
,
101 "Bin %" PRId64
" -> %" PRId64
" bytes", old_size
, new_size
) < 0)
105 if (git_buf_printf(out
,
106 "%*" PRIuZ
, stats
->max_digits
,
107 filestat
->insertions
+ filestat
->deletions
) < 0)
110 if (filestat
->insertions
|| filestat
->deletions
) {
111 if (git_buf_putc(out
, ' ') < 0)
115 if (git_buf_putcn(out
, '+', filestat
->insertions
) < 0 ||
116 git_buf_putcn(out
, '-', filestat
->deletions
) < 0)
119 size_t total
= filestat
->insertions
+ filestat
->deletions
;
120 size_t full
= (total
* width
+ stats
->max_filestat
/ 2) /
122 size_t plus
= full
* filestat
->insertions
/ total
;
123 size_t minus
= full
- plus
;
125 if (git_buf_putcn(out
, '+', max(plus
, 1)) < 0 ||
126 git_buf_putcn(out
, '-', max(minus
, 1)) < 0)
132 git_buf_putc(out
, '\n');
135 return (git_buf_oom(out
) ? -1 : 0);
138 static int diff_file_stats_number_to_buf(
140 const git_diff_delta
*delta
,
141 const diff_file_stats
*filestats
)
144 const char *path
= delta
->new_file
.path
;
146 if (delta
->flags
& GIT_DIFF_FLAG_BINARY
)
147 error
= git_buf_printf(out
, "%-8c" "%-8c" "%s\n", '-', '-', path
);
149 error
= git_buf_printf(out
, "%-8" PRIuZ
"%-8" PRIuZ
"%s\n",
150 filestats
->insertions
, filestats
->deletions
, path
);
155 static int diff_file_stats_summary_to_buf(
157 const git_diff_delta
*delta
)
159 if (delta
->old_file
.mode
!= delta
->new_file
.mode
) {
160 if (delta
->old_file
.mode
== 0) {
161 git_buf_printf(out
, " create mode %06o %s\n",
162 delta
->new_file
.mode
, delta
->new_file
.path
);
164 else if (delta
->new_file
.mode
== 0) {
165 git_buf_printf(out
, " delete mode %06o %s\n",
166 delta
->old_file
.mode
, delta
->old_file
.path
);
169 git_buf_printf(out
, " mode change %06o => %06o %s\n",
170 delta
->old_file
.mode
, delta
->new_file
.mode
, delta
->new_file
.path
);
177 int git_diff_get_stats(
178 git_diff_stats
**out
,
182 size_t total_insertions
= 0, total_deletions
= 0;
183 git_diff_stats
*stats
= NULL
;
187 GIT_ASSERT_ARG(diff
);
189 stats
= git__calloc(1, sizeof(git_diff_stats
));
190 GIT_ERROR_CHECK_ALLOC(stats
);
192 deltas
= git_diff_num_deltas(diff
);
194 stats
->filestats
= git__calloc(deltas
, sizeof(diff_file_stats
));
195 if (!stats
->filestats
) {
201 GIT_REFCOUNT_INC(diff
);
203 for (i
= 0; i
< deltas
&& !error
; ++i
) {
204 git_patch
*patch
= NULL
;
205 size_t add
= 0, remove
= 0, namelen
;
206 const git_diff_delta
*delta
;
208 if ((error
= git_patch_from_diff(&patch
, diff
, i
)) < 0)
211 /* keep a count of renames because it will affect formatting */
212 delta
= patch
->delta
;
215 namelen
= strlen(delta
->new_file
.path
);
216 if (delta
->old_file
.path
&& strcmp(delta
->old_file
.path
, delta
->new_file
.path
) != 0) {
217 namelen
+= strlen(delta
->old_file
.path
);
221 /* and, of course, count the line stats */
222 error
= git_patch_line_stats(NULL
, &add
, &remove
, patch
);
224 git_patch_free(patch
);
226 stats
->filestats
[i
].insertions
= add
;
227 stats
->filestats
[i
].deletions
= remove
;
229 total_insertions
+= add
;
230 total_deletions
+= remove
;
232 if (stats
->max_name
< namelen
)
233 stats
->max_name
= namelen
;
234 if (stats
->max_filestat
< add
+ remove
)
235 stats
->max_filestat
= add
+ remove
;
238 stats
->files_changed
= deltas
;
239 stats
->insertions
= total_insertions
;
240 stats
->deletions
= total_deletions
;
241 stats
->max_digits
= digits_for_value(stats
->max_filestat
+ 1);
244 git_diff_stats_free(stats
);
252 size_t git_diff_stats_files_changed(
253 const git_diff_stats
*stats
)
255 GIT_ASSERT_ARG(stats
);
257 return stats
->files_changed
;
260 size_t git_diff_stats_insertions(
261 const git_diff_stats
*stats
)
263 GIT_ASSERT_ARG(stats
);
265 return stats
->insertions
;
268 size_t git_diff_stats_deletions(
269 const git_diff_stats
*stats
)
271 GIT_ASSERT_ARG(stats
);
273 return stats
->deletions
;
276 int git_diff_stats_to_buf(
278 const git_diff_stats
*stats
,
279 git_diff_stats_format_t format
,
284 const git_diff_delta
*delta
;
287 GIT_ASSERT_ARG(stats
);
289 if (format
& GIT_DIFF_STATS_NUMBER
) {
290 for (i
= 0; i
< stats
->files_changed
; ++i
) {
291 if ((delta
= git_diff_get_delta(stats
->diff
, i
)) == NULL
)
294 error
= diff_file_stats_number_to_buf(
295 out
, delta
, &stats
->filestats
[i
]);
301 if (format
& GIT_DIFF_STATS_FULL
) {
303 if (width
> stats
->max_name
+ stats
->max_digits
+ 5)
304 width
-= (stats
->max_name
+ stats
->max_digits
+ 5);
305 if (width
< STATS_FULL_MIN_SCALE
)
306 width
= STATS_FULL_MIN_SCALE
;
308 if (width
> stats
->max_filestat
)
311 for (i
= 0; i
< stats
->files_changed
; ++i
) {
312 if ((delta
= git_diff_get_delta(stats
->diff
, i
)) == NULL
)
315 error
= diff_file_stats_full_to_buf(
316 out
, delta
, &stats
->filestats
[i
], stats
, width
);
322 if (format
& GIT_DIFF_STATS_FULL
|| format
& GIT_DIFF_STATS_SHORT
) {
324 out
, " %" PRIuZ
" file%s changed",
325 stats
->files_changed
, stats
->files_changed
!= 1 ? "s" : "");
327 if (stats
->insertions
|| stats
->deletions
== 0)
329 out
, ", %" PRIuZ
" insertion%s(+)",
330 stats
->insertions
, stats
->insertions
!= 1 ? "s" : "");
332 if (stats
->deletions
|| stats
->insertions
== 0)
334 out
, ", %" PRIuZ
" deletion%s(-)",
335 stats
->deletions
, stats
->deletions
!= 1 ? "s" : "");
337 git_buf_putc(out
, '\n');
339 if (git_buf_oom(out
))
343 if (format
& GIT_DIFF_STATS_INCLUDE_SUMMARY
) {
344 for (i
= 0; i
< stats
->files_changed
; ++i
) {
345 if ((delta
= git_diff_get_delta(stats
->diff
, i
)) == NULL
)
348 error
= diff_file_stats_summary_to_buf(out
, delta
);
357 void git_diff_stats_free(git_diff_stats
*stats
)
362 git_diff_free(stats
->diff
); /* bumped refcount in constructor */
363 git__free(stats
->filestats
);