diff options
-rw-r--r-- | include/git2/diff.h | 9 | ||||
-rw-r--r-- | src/diff_tform.c | 32 | ||||
-rw-r--r-- | tests/diff/rename.c | 183 |
3 files changed, 213 insertions, 11 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h index db6bce2eb..315cc1215 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -468,6 +468,9 @@ typedef int (*git_diff_line_cb)( * Flags to control the behavior of diff rename/copy detection. */ typedef enum { + /** Obey `diff.renames`. This is overridden by any other GIT_DIFF_FIND_ALL flag. */ + GIT_DIFF_FIND_BY_CONFIG = 0, + /** Look for renames? (`--find-renames`) */ GIT_DIFF_FIND_RENAMES = (1u << 0), @@ -573,7 +576,11 @@ typedef struct { typedef struct { unsigned int version; - /** Combination of git_diff_find_t values (default FIND_RENAMES) */ + /** + * Combination of git_diff_find_t values (default FIND_BY_CONFIG). + * Note that if you don't explicitly set this, `diff.renames` could be set + * to false, resulting in `git_diff_find_similar` doing nothing. + */ uint32_t flags; /** Similarity to consider a file renamed (default 50) */ diff --git a/src/diff_tform.c b/src/diff_tform.c index 0a28e58c7..702e43bd3 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -275,28 +275,36 @@ static int normalize_find_opts( { git_config *cfg = NULL; + GITERR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options"); + if (diff->repo != NULL && git_repository_config__weakptr(&cfg, diff->repo) < 0) return -1; - if (given != NULL) + if (given) { memcpy(opts, given, sizeof(*opts)); - else { - const char *val = NULL; - + } else { GIT_INIT_STRUCTURE(opts, GIT_DIFF_FIND_OPTIONS_VERSION); + } - opts->flags = GIT_DIFF_FIND_RENAMES; + if (!given || + (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG) + { + const char *val = NULL; if (git_config_get_string(&val, cfg, "diff.renames") < 0) giterr_clear(); - else if (val && - (!strcasecmp(val, "copies") || !strcasecmp(val, "copy"))) - opts->flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + else if (val) { + int boolval; + if (!git__parse_bool(&boolval, val) && !boolval) { + /* do nothing */ + } else if (!strcasecmp(val, "copies") || !strcasecmp(val, "copy")) + opts->flags |= (GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES); + else + opts->flags |= GIT_DIFF_FIND_RENAMES; + } } - GITERR_CHECK_VERSION(opts, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options"); - /* some flags imply others */ if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) { @@ -830,6 +838,10 @@ int git_diff_find_similar( if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0) return error; + /* No flags set; nothing to do */ + if ((opts.flags & GIT_DIFF_FIND_ALL) == 0) + return 0; + num_deltas = diff->deltas.length; /* TODO: maybe abort if deltas.length > rename_limit ??? */ diff --git a/tests/diff/rename.c b/tests/diff/rename.c index 919f5139a..ca6d076d6 100644 --- a/tests/diff/rename.c +++ b/tests/diff/rename.c @@ -919,6 +919,7 @@ void test_diff_rename__rejected_match_can_match_others(void) char *ptr; opts.checkout_strategy = GIT_CHECKOUT_FORCE; + findopts.flags = GIT_DIFF_FIND_RENAMES; cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); cl_git_pass(git_reference_symbolic_set_target( @@ -1003,6 +1004,7 @@ void test_diff_rename__rejected_match_can_match_others_two(void) struct rename_expected expect = { 2, status, sources, targets }; opts.checkout_strategy = GIT_CHECKOUT_FORCE; + findopts.flags = GIT_DIFF_FIND_RENAMES; cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); cl_git_pass(git_reference_symbolic_set_target( @@ -1060,6 +1062,7 @@ void test_diff_rename__rejected_match_can_match_others_three(void) struct rename_expected expect = { 2, status, sources, targets }; opts.checkout_strategy = GIT_CHECKOUT_FORCE; + findopts.flags = GIT_DIFF_FIND_RENAMES; cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); cl_git_pass(git_reference_symbolic_set_target( @@ -1381,3 +1384,183 @@ void test_diff_rename__can_delete_unmodified_deltas(void) git_buf_free(&c1); } + +void test_diff_rename__matches_config_behavior(void) +{ + const char *sha0 = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; + const char *sha1 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + const char *sha2 = "1c068dee5790ef1580cfc4cd670915b48d790084"; + + git_tree *tree0, *tree1, *tree2; + git_config *cfg; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + opts.flags = GIT_DIFF_FIND_BY_CONFIG; + tree0 = resolve_commit_oid_to_tree(g_repo, sha0); + tree1 = resolve_commit_oid_to_tree(g_repo, sha1); + tree2 = resolve_commit_oid_to_tree(g_repo, sha2); + + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + cl_git_pass(git_repository_config(&cfg, g_repo)); + + /* diff.renames = false; no rename detection should happen */ + cl_git_pass(git_config_set_bool(cfg, "diff.renames", false)); + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree0, tree1, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + git_diff_free(diff); + + /* diff.renames = true; should act like -M */ + cl_git_pass(git_config_set_bool(cfg, "diff.renames", true)); + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree0, tree1, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + git_diff_free(diff); + + /* diff.renames = copies; should act like -M -C */ + cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + git_diff_free(diff); + + /* NULL find options is the same as GIT_DIFF_FIND_BY_CONFIG */ + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, NULL)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + git_diff_free(diff); + + /* Cleanup */ + git_tree_free(tree0); + git_tree_free(tree1); + git_tree_free(tree2); + git_config_free(cfg); +} + +void test_diff_rename__can_override_thresholds_when_obeying_config(void) +{ + const char *sha1 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + const char *sha2 = "1c068dee5790ef1580cfc4cd670915b48d790084"; + + git_tree *tree1, *tree2; + git_config *cfg; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + tree1 = resolve_commit_oid_to_tree(g_repo, sha1); + tree2 = resolve_commit_oid_to_tree(g_repo, sha2); + + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + opts.flags = GIT_DIFF_FIND_BY_CONFIG; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); + git_config_free(cfg); + + /* copy threshold = 96%, should see creation of ikeepsix.txt */ + opts.copy_threshold = 96; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + git_diff_free(diff); + + /* copy threshold = 20%, should see sixserving.txt => ikeepsix.txt */ + opts.copy_threshold = 20; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + git_diff_free(diff); + + /* Cleanup */ + git_tree_free(tree1); + git_tree_free(tree2); +} + +void test_diff_rename__by_config_doesnt_mess_with_whitespace_settings(void) +{ + const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084"; + const char *sha2 = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13"; + + git_tree *tree1, *tree2; + git_config *cfg; + git_diff *diff; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + diff_expects exp; + + tree1 = resolve_commit_oid_to_tree(g_repo, sha1); + tree2 = resolve_commit_oid_to_tree(g_repo, sha2); + + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + opts.flags = GIT_DIFF_FIND_BY_CONFIG; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies")); + git_config_free(cfg); + + /* Don't ignore whitespace; this should find a change in sixserving.txt */ + opts.flags |= 0 | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, tree1, tree2, &diffopts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_find_similar(diff, &opts)); + cl_git_pass(git_diff_foreach(diff, + diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + git_diff_free(diff); + + /* Cleanup */ + git_tree_free(tree1); + git_tree_free(tree2); +} |