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 int git_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
;
58 git_off_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 (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 if (git_buf_printf(out
, " %s", old_path
) < 0)
88 padding
= stats
->max_name
- strlen(old_path
);
90 if (stats
->renames
> 0)
91 padding
+= strlen(DIFF_RENAME_FILE_SEPARATOR
);
94 if (git_buf_putcn(out
, ' ', padding
) < 0 ||
95 git_buf_puts(out
, " | ") < 0)
98 if (delta
->flags
& GIT_DIFF_FLAG_BINARY
) {
99 if (git_buf_printf(out
,
100 "Bin %" PRId64
" -> %" PRId64
" bytes", old_size
, new_size
) < 0)
104 if (git_buf_printf(out
,
105 "%*" PRIuZ
, stats
->max_digits
,
106 filestat
->insertions
+ filestat
->deletions
) < 0)
109 if (filestat
->insertions
|| filestat
->deletions
) {
110 if (git_buf_putc(out
, ' ') < 0)
114 if (git_buf_putcn(out
, '+', filestat
->insertions
) < 0 ||
115 git_buf_putcn(out
, '-', filestat
->deletions
) < 0)
118 size_t total
= filestat
->insertions
+ filestat
->deletions
;
119 size_t full
= (total
* width
+ stats
->max_filestat
/ 2) /
121 size_t plus
= full
* filestat
->insertions
/ total
;
122 size_t minus
= full
- plus
;
124 if (git_buf_putcn(out
, '+', max(plus
, 1)) < 0 ||
125 git_buf_putcn(out
, '-', max(minus
, 1)) < 0)
131 git_buf_putc(out
, '\n');
134 return (git_buf_oom(out
) ? -1 : 0);
137 int git_diff_file_stats__number_to_buf(
139 const git_diff_delta
*delta
,
140 const diff_file_stats
*filestats
)
143 const char *path
= delta
->new_file
.path
;
145 if (delta
->flags
& GIT_DIFF_FLAG_BINARY
)
146 error
= git_buf_printf(out
, "%-8c" "%-8c" "%s\n", '-', '-', path
);
148 error
= git_buf_printf(out
, "%-8" PRIuZ
"%-8" PRIuZ
"%s\n",
149 filestats
->insertions
, filestats
->deletions
, path
);
154 int git_diff_file_stats__summary_to_buf(
156 const git_diff_delta
*delta
)
158 if (delta
->old_file
.mode
!= delta
->new_file
.mode
) {
159 if (delta
->old_file
.mode
== 0) {
160 git_buf_printf(out
, " create mode %06o %s\n",
161 delta
->new_file
.mode
, delta
->new_file
.path
);
163 else if (delta
->new_file
.mode
== 0) {
164 git_buf_printf(out
, " delete mode %06o %s\n",
165 delta
->old_file
.mode
, delta
->old_file
.path
);
168 git_buf_printf(out
, " mode change %06o => %06o %s\n",
169 delta
->old_file
.mode
, delta
->new_file
.mode
, delta
->new_file
.path
);
176 int git_diff_get_stats(
177 git_diff_stats
**out
,
181 size_t total_insertions
= 0, total_deletions
= 0;
182 git_diff_stats
*stats
= NULL
;
187 stats
= git__calloc(1, sizeof(git_diff_stats
));
188 GIT_ERROR_CHECK_ALLOC(stats
);
190 deltas
= git_diff_num_deltas(diff
);
192 stats
->filestats
= git__calloc(deltas
, sizeof(diff_file_stats
));
193 if (!stats
->filestats
) {
199 GIT_REFCOUNT_INC(diff
);
201 for (i
= 0; i
< deltas
&& !error
; ++i
) {
202 git_patch
*patch
= NULL
;
203 size_t add
= 0, remove
= 0, namelen
;
204 const git_diff_delta
*delta
;
206 if ((error
= git_patch_from_diff(&patch
, diff
, i
)) < 0)
209 /* keep a count of renames because it will affect formatting */
210 delta
= patch
->delta
;
213 namelen
= strlen(delta
->new_file
.path
);
214 if (strcmp(delta
->old_file
.path
, delta
->new_file
.path
) != 0) {
215 namelen
+= strlen(delta
->old_file
.path
);
219 /* and, of course, count the line stats */
220 error
= git_patch_line_stats(NULL
, &add
, &remove
, patch
);
222 git_patch_free(patch
);
224 stats
->filestats
[i
].insertions
= add
;
225 stats
->filestats
[i
].deletions
= remove
;
227 total_insertions
+= add
;
228 total_deletions
+= remove
;
230 if (stats
->max_name
< namelen
)
231 stats
->max_name
= namelen
;
232 if (stats
->max_filestat
< add
+ remove
)
233 stats
->max_filestat
= add
+ remove
;
236 stats
->files_changed
= deltas
;
237 stats
->insertions
= total_insertions
;
238 stats
->deletions
= total_deletions
;
239 stats
->max_digits
= digits_for_value(stats
->max_filestat
+ 1);
242 git_diff_stats_free(stats
);
250 size_t git_diff_stats_files_changed(
251 const git_diff_stats
*stats
)
255 return stats
->files_changed
;
258 size_t git_diff_stats_insertions(
259 const git_diff_stats
*stats
)
263 return stats
->insertions
;
266 size_t git_diff_stats_deletions(
267 const git_diff_stats
*stats
)
271 return stats
->deletions
;
274 int git_diff_stats_to_buf(
276 const git_diff_stats
*stats
,
277 git_diff_stats_format_t format
,
282 const git_diff_delta
*delta
;
284 assert(out
&& stats
);
286 if (format
& GIT_DIFF_STATS_NUMBER
) {
287 for (i
= 0; i
< stats
->files_changed
; ++i
) {
288 if ((delta
= git_diff_get_delta(stats
->diff
, i
)) == NULL
)
291 error
= git_diff_file_stats__number_to_buf(
292 out
, delta
, &stats
->filestats
[i
]);
298 if (format
& GIT_DIFF_STATS_FULL
) {
300 if (width
> stats
->max_name
+ stats
->max_digits
+ 5)
301 width
-= (stats
->max_name
+ stats
->max_digits
+ 5);
302 if (width
< STATS_FULL_MIN_SCALE
)
303 width
= STATS_FULL_MIN_SCALE
;
305 if (width
> stats
->max_filestat
)
308 for (i
= 0; i
< stats
->files_changed
; ++i
) {
309 if ((delta
= git_diff_get_delta(stats
->diff
, i
)) == NULL
)
312 error
= git_diff_file_stats__full_to_buf(
313 out
, delta
, &stats
->filestats
[i
], stats
, width
);
319 if (format
& GIT_DIFF_STATS_FULL
|| format
& GIT_DIFF_STATS_SHORT
) {
321 out
, " %" PRIuZ
" file%s changed",
322 stats
->files_changed
, stats
->files_changed
!= 1 ? "s" : "");
324 if (stats
->insertions
|| stats
->deletions
== 0)
326 out
, ", %" PRIuZ
" insertion%s(+)",
327 stats
->insertions
, stats
->insertions
!= 1 ? "s" : "");
329 if (stats
->deletions
|| stats
->insertions
== 0)
331 out
, ", %" PRIuZ
" deletion%s(-)",
332 stats
->deletions
, stats
->deletions
!= 1 ? "s" : "");
334 git_buf_putc(out
, '\n');
336 if (git_buf_oom(out
))
340 if (format
& GIT_DIFF_STATS_INCLUDE_SUMMARY
) {
341 for (i
= 0; i
< stats
->files_changed
; ++i
) {
342 if ((delta
= git_diff_get_delta(stats
->diff
, i
)) == NULL
)
345 error
= git_diff_file_stats__summary_to_buf(out
, delta
);
354 void git_diff_stats_free(git_diff_stats
*stats
)
359 git_diff_free(stats
->diff
); /* bumped refcount in constructor */
360 git__free(stats
->filestats
);