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