diff options
author | Russell Belfer <rb@github.com> | 2014-04-22 12:33:27 -0700 |
---|---|---|
committer | Russell Belfer <rb@github.com> | 2014-04-22 12:33:27 -0700 |
commit | 8d09efa24ee01e9e4b14672978bfd1bb1ca2436a (patch) | |
tree | d68210d412062a84d73a60ddb01a7a2501af0b29 /src | |
parent | 12e422a0562de2aebb05f5f414dbcde7caf85886 (diff) | |
download | libgit2-8d09efa24ee01e9e4b14672978bfd1bb1ca2436a.tar.gz |
Use git_diff_get_stats in example/diff + refactor
This takes the `--stat` and related example options in the example
diff.c program and converts them to use the `git_diff_get_stats`
API which nicely formats stats for you.
I went to add bar-graph scaling to the stats formatter and noticed
that the `git_diff_stats` structure was holding on to all of the
`git_patch` objects. Unfortunately, each of these objects keeps
the full text of the diff in memory, so this is very expensive. I
ended up modifying `git_diff_stats` to keep just the data that it
needs to keep and allowed it to release the patches. Then, I added
width scaling to the output on top of that.
In making the diff example program match 'git diff' output, I ended
up removing an newline from the sumamry output which I then had to
compensate for in the email formatting to match the expectations.
Lastly, I went through and refactored the tests to use a couple of
helper functions and reduce the overall amount of code there.
Diffstat (limited to 'src')
-rw-r--r-- | src/diff.c | 3 | ||||
-rw-r--r-- | src/diff_stats.c | 299 |
2 files changed, 141 insertions, 161 deletions
diff --git a/src/diff.c b/src/diff.c index 0d1aed4ad..fd881c6f6 100644 --- a/src/diff.c +++ b/src/diff.c @@ -1590,7 +1590,8 @@ int git_diff_format_email( if ((error = git_buf_puts(out, "---\n")) < 0 || (error = git_diff_get_stats(&stats, diff)) < 0 || - (error = git_diff_stats_to_buf(out, stats, format_flags)) < 0 || + (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 || + (error = git_buf_putc(out, '\n')) < 0 || (error = git_diff_format_email__append_patches_tobuf(out, diff)) < 0) goto on_error; diff --git a/src/diff_stats.c b/src/diff_stats.c index bb436bf7b..8a71be894 100644 --- a/src/diff_stats.c +++ b/src/diff_stats.c @@ -8,151 +8,123 @@ #include "vector.h" #include "diff.h" #include "diff_patch.h" +#include <math.h> #define DIFF_RENAME_FILE_SEPARATOR " => " +#define STATS_FULL_MIN_SCALE 7 + +typedef struct { + size_t insertions; + size_t deletions; +} diff_file_stats; struct git_diff_stats { - git_vector patches; + git_diff *diff; + diff_file_stats *filestats; size_t files_changed; size_t insertions; size_t deletions; -}; - -static size_t diff_get_filename_padding( - int has_renames, - const git_diff_stats *stats) -{ - const git_patch *patch = NULL; - size_t i, max_padding = 0; - - if (has_renames) { - git_vector_foreach(&stats->patches, i, patch) { - const git_diff_delta *delta = NULL; - size_t len; - - delta = git_patch_get_delta(patch); - if (strcmp(delta->old_file.path, delta->new_file.path) == 0) - continue; - - if ((len = strlen(delta->old_file.path) + strlen(delta->new_file.path)) > max_padding) - max_padding = len; - } - } - - git_vector_foreach(&stats->patches, i, patch) { - const git_diff_delta *delta = NULL; - size_t len; - - delta = git_patch_get_delta(patch); - len = strlen(delta->new_file.path); - - if (strcmp(delta->old_file.path, delta->new_file.path) != 0) - continue; - - if (len > max_padding) - max_padding = len; - } + size_t renames; - return max_padding; -} + size_t max_name; + size_t max_filestat; + int max_digits; +}; int git_diff_file_stats__full_to_buf( git_buf *out, - size_t max_padding, - int has_renames, - const git_patch *patch) + const git_diff_delta *delta, + const diff_file_stats *filestat, + const git_diff_stats *stats, + double scale_to) { const char *old_path = NULL, *new_path = NULL; - const git_diff_delta *delta = NULL; size_t padding, old_size, new_size; - int error; - - delta = git_patch_get_delta(patch); old_path = delta->old_file.path; new_path = delta->new_file.path; old_size = delta->old_file.size; new_size = delta->new_file.size; - if ((error = git_buf_printf(out, " %s", old_path)) < 0) + if (git_buf_printf(out, " %s", old_path) < 0) goto on_error; if (strcmp(old_path, new_path) != 0) { - padding = max_padding - strlen(old_path) - strlen(new_path); + padding = stats->max_name - strlen(old_path) - strlen(new_path); - if ((error = git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path)) < 0) + if (git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path) < 0) goto on_error; - } - else { - padding = max_padding - strlen(old_path); + } else { + padding = stats->max_name - strlen(old_path); - if (has_renames) + if (stats->renames > 0) padding += strlen(DIFF_RENAME_FILE_SEPARATOR); } - if ((error = git_buf_putcn(out, ' ', padding)) < 0 || - (error = git_buf_puts(out, " | ")) < 0) - goto on_error; + if (git_buf_putcn(out, ' ', padding) < 0 || + git_buf_puts(out, " | ") < 0) + goto on_error; if (delta->flags & GIT_DIFF_FLAG_BINARY) { - if ((error = git_buf_printf(out, "Bin %" PRIuZ " -> %" PRIuZ " bytes", old_size, new_size)) < 0) + if (git_buf_printf(out, + "Bin %" PRIuZ " -> %" PRIuZ " bytes", old_size, new_size) < 0) goto on_error; } else { - size_t insertions, deletions; - - if ((error = git_patch_line_stats(NULL, &insertions, &deletions, patch)) < 0) + if (git_buf_printf(out, + "%*" PRIuZ, stats->max_digits, + filestat->insertions + filestat->deletions) < 0) goto on_error; - if ((error = git_buf_printf(out, "%" PRIuZ, insertions + deletions)) < 0) - goto on_error; + if (filestat->insertions || filestat->deletions) { + if (git_buf_putc(out, ' ') < 0) + goto on_error; - if (insertions || deletions) { - if ((error = git_buf_putc(out, ' ')) < 0 || - (error = git_buf_putcn(out, '+', insertions)) < 0 || - (error = git_buf_putcn(out, '-', deletions)) < 0) + if (scale_to <= 0) { + if (git_buf_putcn(out, '+', filestat->insertions) < 0 || + git_buf_putcn(out, '-', filestat->deletions) < 0) goto on_error; + } else { + size_t total = filestat->insertions + filestat->deletions; + double full = round(total * scale_to / stats->max_filestat); + size_t plus = full * filestat->insertions / total; + size_t minus = (size_t)full - plus; + + if (git_buf_putcn(out, '+', max(plus, 1)) < 0 || + git_buf_putcn(out, '-', max(minus, 1)) < 0) + goto on_error; + } } } - error = git_buf_putc(out, '\n'); + git_buf_putc(out, '\n'); on_error: - return error; + return (git_buf_oom(out) ? -1 : 0); } int git_diff_file_stats__number_to_buf( git_buf *out, - const git_patch *patch) + const git_diff_delta *delta, + const diff_file_stats *filestats) { - const git_diff_delta *delta = NULL; - const char *path = NULL; - size_t insertions, deletions; int error; - - delta = git_patch_get_delta(patch); - path = delta->new_file.path; - - if ((error = git_patch_line_stats(NULL, &insertions, &deletions, patch)) < 0) - return error; + const char *path = delta->new_file.path; if (delta->flags & GIT_DIFF_FLAG_BINARY) error = git_buf_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path); else - error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", insertions, deletions, path); + error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", + filestats->insertions, filestats->deletions, path); return error; } int git_diff_file_stats__summary_to_buf( git_buf *out, - const git_patch *patch) + const git_diff_delta *delta) { - const git_diff_delta *delta = NULL; - - delta = git_patch_get_delta(patch); - if (delta->old_file.mode != delta->new_file.mode) { if (delta->old_file.mode == 0) { git_buf_printf(out, " create mode %06o %s\n", @@ -171,39 +143,6 @@ int git_diff_file_stats__summary_to_buf( return 0; } -int git_diff_stats__has_renames( - const git_diff_stats *stats) -{ - git_patch *patch = NULL; - size_t i; - - git_vector_foreach(&stats->patches, i, patch) { - const git_diff_delta *delta = git_patch_get_delta(patch); - - if (strcmp(delta->old_file.path, delta->new_file.path) != 0) { - return 1; - } - } - - return 0; -} - -int git_diff_stats__add_file_stats( - git_diff_stats *stats, - git_patch *patch) -{ - const git_diff_delta *delta = NULL; - int error = 0; - - if ((delta = git_patch_get_delta(patch)) == NULL) - return -1; - - if ((error = git_vector_insert(&stats->patches, patch)) < 0) - return error; - - return error; -} - int git_diff_get_stats( git_diff_stats **out, git_diff *diff) @@ -220,35 +159,60 @@ int git_diff_get_stats( deltas = git_diff_num_deltas(diff); - for (i = 0; i < deltas; ++i) { + stats->filestats = git__calloc(deltas, sizeof(diff_file_stats)); + if (!stats->filestats) { + git__free(stats); + return -1; + } + + stats->diff = diff; + GIT_REFCOUNT_INC(diff); + + for (i = 0; i < deltas && !error; ++i) { git_patch *patch = NULL; - size_t add, remove; + size_t add = 0, remove = 0, namelen; + const git_diff_delta *delta; if ((error = git_patch_from_diff(&patch, diff, i)) < 0) - goto on_error; + break; - if ((error = git_patch_line_stats(NULL, &add, &remove, patch)) < 0 || - (error = git_diff_stats__add_file_stats(stats, patch)) < 0) { - git_patch_free(patch); - goto on_error; + /* keep a count of renames because it will affect formatting */ + delta = git_patch_get_delta(patch); + + namelen = strlen(delta->new_file.path); + if (strcmp(delta->old_file.path, delta->new_file.path) != 0) { + namelen += strlen(delta->old_file.path); + stats->renames++; } + /* and, of course, count the line stats */ + error = git_patch_line_stats(NULL, &add, &remove, patch); + + git_patch_free(patch); + + stats->filestats[i].insertions = add; + stats->filestats[i].deletions = remove; + total_insertions += add; total_deletions += remove; + + if (stats->max_name < namelen) + stats->max_name = namelen; + if (stats->max_filestat < add + remove) + stats->max_filestat = add + remove; } stats->files_changed = deltas; stats->insertions = total_insertions; stats->deletions = total_deletions; + stats->max_digits = (int)ceil(log10(stats->max_filestat + 1)); - *out = stats; - - goto done; - -on_error: - git_diff_stats_free(stats); + if (error < 0) { + git_diff_stats_free(stats); + stats = NULL; + } -done: + *out = stats; return error; } @@ -279,48 +243,68 @@ size_t git_diff_stats_deletions( int git_diff_stats_to_buf( git_buf *out, const git_diff_stats *stats, - git_diff_stats_format_t format) + git_diff_stats_format_t format, + size_t width) { - git_patch *patch = NULL; + int error = 0; size_t i; - int has_renames = 0, error = 0; + const git_diff_delta *delta; assert(out && stats); - /* check if we have renames, it affects the padding */ - has_renames = git_diff_stats__has_renames(stats); - - git_vector_foreach(&stats->patches, i, patch) { - if (format & GIT_DIFF_STATS_FULL) { - size_t max_padding = diff_get_filename_padding(has_renames, stats); + if (format & GIT_DIFF_STATS_NUMBER) { + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; - error = git_diff_file_stats__full_to_buf(out, max_padding, has_renames, patch); + error = git_diff_file_stats__number_to_buf( + out, delta, &stats->filestats[i]); + if (error < 0) + return error; } - else if (format & GIT_DIFF_STATS_NUMBER) { - error = git_diff_file_stats__number_to_buf(out, patch); + } + + if (format & GIT_DIFF_STATS_FULL) { + double scale_to = -1; + if (width > 0) { + if (width > stats->max_name + stats->max_digits + 5) + scale_to = width - (stats->max_name + stats->max_digits + 5); + if (scale_to < STATS_FULL_MIN_SCALE) + scale_to = STATS_FULL_MIN_SCALE; } - if (error < 0) - return error; + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = git_diff_file_stats__full_to_buf( + out, delta, &stats->filestats[i], stats, scale_to); + if (error < 0) + return error; + } } if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) { - error = git_buf_printf(out, " %" PRIuZ " file%s changed, %" PRIuZ " insertions(+), %" PRIuZ " deletions(-)\n", - stats->files_changed, stats->files_changed > 1 ? "s" : "", - stats->insertions, stats->deletions); + error = git_buf_printf( + out, " %" PRIuZ " file%s changed, %" PRIuZ + " insertion%s(+), %" PRIuZ " deletion%s(-)\n", + stats->files_changed, stats->files_changed != 1 ? "s" : "", + stats->insertions, stats->insertions != 1 ? "s" : "", + stats->deletions, stats->deletions != 1 ? "s" : ""); if (error < 0) return error; } if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) { - git_vector_foreach(&stats->patches, i, patch) { - if ((error = git_diff_file_stats__summary_to_buf(out, patch)) < 0) + for (i = 0; i < stats->files_changed; ++i) { + if ((delta = git_diff_get_delta(stats->diff, i)) == NULL) + continue; + + error = git_diff_file_stats__summary_to_buf(out, delta); + if (error < 0) return error; } - - if (git_vector_length(&stats->patches) > 0) - git_buf_putc(out, '\n'); } return error; @@ -328,16 +312,11 @@ int git_diff_stats_to_buf( void git_diff_stats_free(git_diff_stats *stats) { - size_t i; - git_patch *patch; - if (stats == NULL) return; - git_vector_foreach(&stats->patches, i, patch) - git_patch_free(patch); - - git_vector_free(&stats->patches); + git_diff_free(stats->diff); /* bumped refcount in constructor */ + git__free(stats->filestats); git__free(stats); } |