summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacques Germishuys <jacquesg@striata.com>2014-04-11 19:03:29 +0200
committerJacques Germishuys <jacquesg@striata.com>2014-04-15 17:22:12 +0200
commit360314c9dbb383b3737876a9c229e22050ec9c49 (patch)
treeed4650f1e1e0af1dda8a6febd5f2485f6f89ae8b
parentcab39378dc4dba7cc501255f066808c713dd11ef (diff)
downloadlibgit2-360314c9dbb383b3737876a9c229e22050ec9c49.tar.gz
Introduce git_diff_get_stats, git_diff_stats_files_changed, git_diff_stats_insertions, git_diff_stats_deletions and git_diff_stats_to_buf
-rw-r--r--include/git2/diff.h85
-rw-r--r--src/diff_stats.c343
-rw-r--r--tests/diff/stats.c428
3 files changed, 856 insertions, 0 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h
index f855f52ba..571f0c887 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -1072,6 +1072,91 @@ GIT_EXTERN(int) git_diff_buffers(
git_diff_line_cb line_cb,
void *payload);
+/**
+ * This is an opaque structure which is allocated by `git_diff_get_stats`.
+ * You are responsible for releasing the object memory when done, using the
+ * `git_diff_stats_free()` function.
+ */
+typedef struct git_diff_stats git_diff_stats;
+
+/**
+ * Formatting options for diff stats
+ */
+typedef enum {
+ /** No stats*/
+ GIT_DIFF_STATS_NONE = 0,
+
+ /** Full statistics, equivalent of `--stat` */
+ GIT_DIFF_STATS_FULL = (1u << 0),
+
+ /** Short statistics, equivalent of `--shortstat` */
+ GIT_DIFF_STATS_SHORT = (1u << 1),
+
+ /** Number statistics, equivalent of `--numstat` */
+ GIT_DIFF_STATS_NUMBER = (1u << 2),
+
+ /** Extended header information such as creations, renames and mode changes, equivalent of `--summary` */
+ GIT_DIFF_STATS_INCLUDE_SUMMARY = (1u << 3),
+} git_diff_stats_format_t;
+
+/**
+ * Accumlate diff statistics for all patches.
+ *
+ * @param out Structure containg the diff statistics.
+ * @param diff A git_diff generated by one of the above functions.
+ * @return 0 on success; non-zero on error
+ */
+GIT_EXTERN(int) git_diff_get_stats(
+ git_diff_stats **out,
+ git_diff *diff);
+
+/**
+ * Get the total number of files changed in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of files changed in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_files_changed(
+ const git_diff_stats *stats);
+
+/**
+ * Get the total number of insertions in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of insertions in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_insertions(
+ const git_diff_stats *stats);
+
+/**
+ * Get the total number of deletions in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of deletions in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_deletions(
+ const git_diff_stats *stats);
+
+/**
+ * Print diff statistics to a `git_buf`.
+ *
+ * @param out buffer to store the formatted diff statistics in.
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @param format Formatting option.
+ * @return 0 on success; non-zero on error
+ */
+GIT_EXTERN(int) git_diff_stats_to_buf(
+ git_buf *out,
+ const git_diff_stats *stats,
+ git_diff_stats_format_t format);
+
+/**
+ * Deallocate a `git_diff_stats`.
+ *
+ * @param stats The previously created statistics object;
+ * cannot be used after free.
+ */
+GIT_EXTERN(void) git_diff_stats_free(git_diff_stats *stats);
GIT_END_DECL
diff --git a/src/diff_stats.c b/src/diff_stats.c
new file mode 100644
index 000000000..bb436bf7b
--- /dev/null
+++ b/src/diff_stats.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "vector.h"
+#include "diff.h"
+#include "diff_patch.h"
+
+#define DIFF_RENAME_FILE_SEPARATOR " => "
+
+struct git_diff_stats {
+ git_vector patches;
+
+ 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;
+ }
+
+ return max_padding;
+}
+
+int git_diff_file_stats__full_to_buf(
+ git_buf *out,
+ size_t max_padding,
+ int has_renames,
+ const git_patch *patch)
+{
+ 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)
+ goto on_error;
+
+ if (strcmp(old_path, new_path) != 0) {
+ padding = max_padding - strlen(old_path) - strlen(new_path);
+
+ if ((error = git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path)) < 0)
+ goto on_error;
+ }
+ else {
+ padding = max_padding - strlen(old_path);
+
+ if (has_renames)
+ padding += strlen(DIFF_RENAME_FILE_SEPARATOR);
+ }
+
+ if ((error = git_buf_putcn(out, ' ', padding)) < 0 ||
+ (error = 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)
+ goto on_error;
+ }
+ else {
+ size_t insertions, deletions;
+
+ if ((error = git_patch_line_stats(NULL, &insertions, &deletions, patch)) < 0)
+ goto on_error;
+
+ if ((error = git_buf_printf(out, "%" PRIuZ, insertions + deletions)) < 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)
+ goto on_error;
+ }
+ }
+
+ error = git_buf_putc(out, '\n');
+
+on_error:
+ return error;
+}
+
+int git_diff_file_stats__number_to_buf(
+ git_buf *out,
+ const git_patch *patch)
+{
+ 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;
+
+ 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);
+
+ return error;
+}
+
+int git_diff_file_stats__summary_to_buf(
+ git_buf *out,
+ const git_patch *patch)
+{
+ 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",
+ delta->new_file.mode, delta->new_file.path);
+ }
+ else if (delta->new_file.mode == 0) {
+ git_buf_printf(out, " delete mode %06o %s\n",
+ delta->old_file.mode, delta->old_file.path);
+ }
+ else {
+ git_buf_printf(out, " mode change %06o => %06o %s\n",
+ delta->old_file.mode, delta->new_file.mode, delta->new_file.path);
+ }
+ }
+
+ 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)
+{
+ size_t i, deltas;
+ size_t total_insertions = 0, total_deletions = 0;
+ git_diff_stats *stats = NULL;
+ int error = 0;
+
+ assert(out && diff);
+
+ stats = git__calloc(1, sizeof(git_diff_stats));
+ GITERR_CHECK_ALLOC(stats);
+
+ deltas = git_diff_num_deltas(diff);
+
+ for (i = 0; i < deltas; ++i) {
+ git_patch *patch = NULL;
+ size_t add, remove;
+
+ if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
+ goto on_error;
+
+ 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;
+ }
+
+ total_insertions += add;
+ total_deletions += remove;
+ }
+
+ stats->files_changed = deltas;
+ stats->insertions = total_insertions;
+ stats->deletions = total_deletions;
+
+ *out = stats;
+
+ goto done;
+
+on_error:
+ git_diff_stats_free(stats);
+
+done:
+ return error;
+}
+
+size_t git_diff_stats_files_changed(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->files_changed;
+}
+
+size_t git_diff_stats_insertions(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->insertions;
+}
+
+size_t git_diff_stats_deletions(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->deletions;
+}
+
+int git_diff_stats_to_buf(
+ git_buf *out,
+ const git_diff_stats *stats,
+ git_diff_stats_format_t format)
+{
+ git_patch *patch = NULL;
+ size_t i;
+ int has_renames = 0, error = 0;
+
+ 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);
+
+ error = git_diff_file_stats__full_to_buf(out, max_padding, has_renames, patch);
+ }
+ else if (format & GIT_DIFF_STATS_NUMBER) {
+ error = git_diff_file_stats__number_to_buf(out, patch);
+ }
+
+ 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);
+
+ 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)
+ return error;
+ }
+
+ if (git_vector_length(&stats->patches) > 0)
+ git_buf_putc(out, '\n');
+ }
+
+ return error;
+}
+
+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__free(stats);
+}
+
diff --git a/tests/diff/stats.c b/tests/diff/stats.c
new file mode 100644
index 000000000..131b7681d
--- /dev/null
+++ b/tests/diff/stats.c
@@ -0,0 +1,428 @@
+#include "clar.h"
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "commit.h"
+#include "diff.h"
+
+static git_repository *repo;
+
+void test_diff_stats__initialize(void)
+{
+ repo = cl_git_sandbox_init("diff_format_email");
+}
+
+void test_diff_stats__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_diff_stats__stat(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file1.txt | 8 +++++---\n" \
+ " 1 file changed, 5 insertions(+), 3 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 1);
+ cl_assert(git_diff_stats_insertions(stats) == 5);
+ cl_assert(git_diff_stats_deletions(stats) == 3);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__multiple_hunks(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file2.txt | 5 +++--\n" \
+ " file3.txt | 6 ++++--\n" \
+ " 2 files changed, 7 insertions(+), 4 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 2);
+ cl_assert(git_diff_stats_insertions(stats) == 7);
+ cl_assert(git_diff_stats_deletions(stats) == 4);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__numstat(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ "3 2 file2.txt\n"
+ "4 2 file3.txt\n";
+
+ git_oid_fromstr(&oid, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_NUMBER));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__shortstat(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " 1 file changed, 5 insertions(+), 3 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 1);
+ cl_assert(git_diff_stats_insertions(stats) == 5);
+ cl_assert(git_diff_stats_deletions(stats) == 3);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_SHORT));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file2.txt => file2.txt.renamed | 1 +\n"
+ " file3.txt => file3.txt.renamed | 4 +++-\n"
+ " 2 files changed, 4 insertions(+), 1 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "8947a46e2097638ca6040ad4877246f4186ec3bd");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_pass(git_diff_find_similar(diff, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 2);
+ cl_assert(git_diff_stats_insertions(stats) == 4);
+ cl_assert(git_diff_stats_deletions(stats) == 1);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_nochanges(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file2.txt.renamed => file2.txt.renamed2 | 0\n"
+ " file3.txt.renamed => file3.txt.renamed2 | 0\n"
+ " 2 files changed, 0 insertions(+), 0 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_pass(git_diff_find_similar(diff, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 2);
+ cl_assert(git_diff_stats_insertions(stats) == 0);
+ cl_assert(git_diff_stats_deletions(stats) == 0);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_and_modifiy(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file2.txt.renamed2 | 2 +-\n"
+ " file3.txt.renamed2 => file3.txt.renamed | 0\n"
+ " 2 files changed, 1 insertions(+), 1 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "4ca10087e696d2ba78d07b146a118e9a7096ed4f");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_pass(git_diff_find_similar(diff, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 2);
+ cl_assert(git_diff_stats_insertions(stats) == 1);
+ cl_assert(git_diff_stats_deletions(stats) == 1);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_no_find(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file2.txt | 5 -----\n"
+ " file2.txt.renamed | 6 ++++++\n"
+ " file3.txt | 5 -----\n"
+ " file3.txt.renamed | 7 +++++++\n"
+ " 4 files changed, 13 insertions(+), 10 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "8947a46e2097638ca6040ad4877246f4186ec3bd");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 4);
+ cl_assert(git_diff_stats_insertions(stats) == 13);
+ cl_assert(git_diff_stats_deletions(stats) == 10);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_nochanges_no_find(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file2.txt.renamed | 6 ------\n"
+ " file2.txt.renamed2 | 6 ++++++\n"
+ " file3.txt.renamed | 7 -------\n"
+ " file3.txt.renamed2 | 7 +++++++\n"
+ " 4 files changed, 13 insertions(+), 13 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 4);
+ cl_assert(git_diff_stats_insertions(stats) == 13);
+ cl_assert(git_diff_stats_deletions(stats) == 13);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_and_modifiy_no_find(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file2.txt.renamed2 | 2 +-\n"
+ " file3.txt.renamed | 7 +++++++\n"
+ " file3.txt.renamed2 | 7 -------\n"
+ " 3 files changed, 8 insertions(+), 8 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "4ca10087e696d2ba78d07b146a118e9a7096ed4f");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 3);
+ cl_assert(git_diff_stats_insertions(stats) == 8);
+ cl_assert(git_diff_stats_deletions(stats) == 8);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__binary(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* TODO: Actually 0 bytes here should be 5!. Seems like we don't load the new content for binary files? */
+ const char *stat =
+ " binary.bin | Bin 3 -> 0 bytes\n"
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n";
+
+ git_oid_fromstr(&oid, "8d7523f6fcb2404257889abe0d96f093d9f524f9");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+ cl_assert(git_diff_stats_files_changed(stats) == 1);
+ cl_assert(git_diff_stats_insertions(stats) == 0);
+ cl_assert(git_diff_stats_deletions(stats) == 0);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__binary_numstat(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ "- - binary.bin\n";
+
+ git_oid_fromstr(&oid, "8d7523f6fcb2404257889abe0d96f093d9f524f9");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_NUMBER));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__mode_change(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_stats *stats = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *stat =
+ " file1.txt.renamed | 0\n" \
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \
+ " mode change 100644 => 100755 file1.txt.renamed\n" \
+ "\n";
+
+ git_oid_fromstr(&oid, "7ade76dd34bba4733cf9878079f9fd4a456a9189");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+
+ cl_git_pass(git_diff_get_stats(&stats, diff));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+
+ git_diff_stats_free(stats);
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}