diff options
Diffstat (limited to 'tests/libgit2/checkout/typechange.c')
-rw-r--r-- | tests/libgit2/checkout/typechange.c | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/tests/libgit2/checkout/typechange.c b/tests/libgit2/checkout/typechange.c new file mode 100644 index 000000000..b888843f0 --- /dev/null +++ b/tests/libgit2/checkout/typechange.c @@ -0,0 +1,335 @@ +#include "clar_libgit2.h" +#include "diff_generate.h" +#include "git2/checkout.h" +#include "path.h" +#include "posix.h" +#include "futils.h" + +static git_repository *g_repo = NULL; + +/* +From the test repo used for this test: +-------------------------------------- + +This is a test repo for libgit2 where tree entries have type changes + +The key types that could be found in tree entries are: + +1 - GIT_FILEMODE_NEW = 0000000 +2 - GIT_FILEMODE_TREE = 0040000 +3 - GIT_FILEMODE_BLOB = 0100644 +4 - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755 +5 - GIT_FILEMODE_LINK = 0120000 +6 - GIT_FILEMODE_COMMIT = 0160000 + +I will try to have every type of transition somewhere in the history +of this repo. + +Commits +------- +Initial commit - a(1) b(1) c(1) d(1) e(1) +Create content - a(1->2) b(1->3) c(1->4) d(1->5) e(1->6) +Changes #1 - a(2->3) b(3->4) c(4->5) d(5->6) e(6->2) +Changes #2 - a(3->5) b(4->6) c(5->2) d(6->3) e(2->4) +Changes #3 - a(5->3) b(6->4) c(2->5) d(3->6) e(4->2) +Changes #4 - a(3->2) b(4->3) c(5->4) d(6->5) e(2->6) +Changes #5 - a(2->1) b(3->1) c(4->1) d(5->1) e(6->1) + +*/ + +static const char *g_typechange_oids[] = { + "79b9f23e85f55ea36a472a902e875bc1121a94cb", + "9bdb75b73836a99e3dbeea640a81de81031fdc29", + "0e7ed140b514b8cae23254cb8656fe1674403aff", + "9d0235c7a7edc0889a18f97a42ee6db9fe688447", + "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a", + "1b63caae4a5ca96f78e8dfefc376c6a39a142475", + "6eae26c90e8ccc4d16208972119c40635489c6f0", + NULL +}; + +static bool g_typechange_empty[] = { + true, false, false, false, false, false, true, true +}; + +static const int g_typechange_expected_conflicts[] = { + 1, 2, 3, 3, 2, 3, 2 +}; + +static const int g_typechange_expected_untracked[] = { + 6, 4, 3, 2, 3, 2, 5 +}; + +void test_checkout_typechange__initialize(void) +{ + g_repo = cl_git_sandbox_init("typechanges"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); +} + +void test_checkout_typechange__cleanup(void) +{ + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("submod2_target"); +} + +static void assert_file_exists(const char *path) +{ + cl_assert_(git_fs_path_isfile(path), path); +} + +static void assert_dir_exists(const char *path) +{ + cl_assert_(git_fs_path_isdir(path), path); +} + +static void assert_workdir_matches_tree( + git_repository *repo, const git_oid *id, const char *root, bool recurse) +{ + git_object *obj; + git_tree *tree; + size_t i, max_i; + git_str path = GIT_STR_INIT; + + if (!root) + root = git_repository_workdir(repo); + cl_assert(root); + + cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJECT_ANY)); + cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJECT_TREE)); + git_object_free(obj); + + max_i = git_tree_entrycount(tree); + + for (i = 0; i < max_i; ++i) { + const git_tree_entry *te = git_tree_entry_byindex(tree, i); + cl_assert(te); + + cl_git_pass(git_str_joinpath(&path, root, git_tree_entry_name(te))); + + switch (git_tree_entry_type(te)) { + case GIT_OBJECT_COMMIT: + assert_dir_exists(path.ptr); + break; + case GIT_OBJECT_TREE: + assert_dir_exists(path.ptr); + if (recurse) + assert_workdir_matches_tree( + repo, git_tree_entry_id(te), path.ptr, true); + break; + case GIT_OBJECT_BLOB: + switch (git_tree_entry_filemode(te)) { + case GIT_FILEMODE_BLOB: + case GIT_FILEMODE_BLOB_EXECUTABLE: + assert_file_exists(path.ptr); + /* because of cross-platform, don't confirm exec bit yet */ + break; + case GIT_FILEMODE_LINK: + cl_assert_(git_fs_path_exists(path.ptr), path.ptr); + /* because of cross-platform, don't confirm link yet */ + break; + default: + cl_assert(false); /* really?! */ + } + break; + default: + cl_assert(false); /* really?!! */ + } + } + + git_tree_free(tree); + git_str_dispose(&path); +} + +void test_checkout_typechange__checkout_typechanges_safe(void) +{ + int i; + git_object *obj; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + for (i = 0; g_typechange_oids[i] != NULL; ++i) { + cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); + + opts.checkout_strategy = !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE; + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(obj))); + + assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); + + git_object_free(obj); + + if (!g_typechange_empty[i]) { + cl_assert(git_fs_path_isdir("typechanges")); + cl_assert(git_fs_path_exists("typechanges/a")); + cl_assert(git_fs_path_exists("typechanges/b")); + cl_assert(git_fs_path_exists("typechanges/c")); + cl_assert(git_fs_path_exists("typechanges/d")); + cl_assert(git_fs_path_exists("typechanges/e")); + } else { + cl_assert(git_fs_path_isdir("typechanges")); + cl_assert(!git_fs_path_exists("typechanges/a")); + cl_assert(!git_fs_path_exists("typechanges/b")); + cl_assert(!git_fs_path_exists("typechanges/c")); + cl_assert(!git_fs_path_exists("typechanges/d")); + cl_assert(!git_fs_path_exists("typechanges/e")); + } + } +} + +typedef struct { + int conflicts; + int dirty; + int updates; + int untracked; + int ignored; +} notify_counts; + +static int notify_counter( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + notify_counts *cts = payload; + + GIT_UNUSED(path); + GIT_UNUSED(baseline); + GIT_UNUSED(target); + GIT_UNUSED(workdir); + + switch (why) { + case GIT_CHECKOUT_NOTIFY_CONFLICT: cts->conflicts++; break; + case GIT_CHECKOUT_NOTIFY_DIRTY: cts->dirty++; break; + case GIT_CHECKOUT_NOTIFY_UPDATED: cts->updates++; break; + case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break; + case GIT_CHECKOUT_NOTIFY_IGNORED: cts->ignored++; break; + default: break; + } + + return 0; +} + +static void force_create_file(const char *file) +{ + int error = git_futils_rmdir_r(file, NULL, + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS); + cl_assert(!error || error == GIT_ENOTFOUND); + cl_git_pass(git_futils_mkpath2file(file, 0777)); + cl_git_rewritefile(file, "yowza!!"); +} + +static int make_submodule_dirty(git_submodule *sm, const char *name, void *payload) +{ + git_str submodulepath = GIT_STR_INIT; + git_str dirtypath = GIT_STR_INIT; + git_repository *submodule_repo; + + GIT_UNUSED(name); + GIT_UNUSED(payload); + + /* remove submodule directory in preparation for init and repo_init */ + cl_git_pass(git_str_joinpath( + &submodulepath, + git_repository_workdir(g_repo), + git_submodule_path(sm) + )); + git_futils_rmdir_r(git_str_cstr(&submodulepath), NULL, GIT_RMDIR_REMOVE_FILES); + + /* initialize submodule's repository */ + cl_git_pass(git_submodule_repo_init(&submodule_repo, sm, 0)); + + /* create a file in the submodule workdir to make it dirty */ + cl_git_pass( + git_str_joinpath(&dirtypath, git_repository_workdir(submodule_repo), "dirty")); + force_create_file(git_str_cstr(&dirtypath)); + + git_str_dispose(&dirtypath); + git_str_dispose(&submodulepath); + git_repository_free(submodule_repo); + + return 0; +} + +void test_checkout_typechange__checkout_with_conflicts(void) +{ + int i; + git_object *obj; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + notify_counts cts = {0}; + + opts.notify_flags = + GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED; + opts.notify_cb = notify_counter; + opts.notify_payload = &cts; + + for (i = 0; g_typechange_oids[i] != NULL; ++i) { + cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); + + force_create_file("typechanges/a/blocker"); + force_create_file("typechanges/b"); + force_create_file("typechanges/c/sub/sub/file"); + git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES); + p_mkdir("typechanges/d", 0777); /* intentionally empty dir */ + force_create_file("typechanges/untracked"); + cl_git_pass(git_submodule_foreach(g_repo, make_submodule_dirty, NULL)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + memset(&cts, 0, sizeof(cts)); + + cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); + cl_assert_equal_i(cts.conflicts, g_typechange_expected_conflicts[i]); + cl_assert_equal_i(cts.untracked, g_typechange_expected_untracked[i]); + cl_assert_equal_i(cts.dirty, 0); + cl_assert_equal_i(cts.updates, 0); + cl_assert_equal_i(cts.ignored, 0); + + opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + memset(&cts, 0, sizeof(cts)); + + cl_assert(git_fs_path_exists("typechanges/untracked")); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_assert_equal_i(0, cts.conflicts); + + cl_assert(!git_fs_path_exists("typechanges/untracked")); + + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(obj))); + + assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true); + + git_object_free(obj); + } +} + +void test_checkout_typechange__status_char(void) +{ + size_t i; + git_oid oid; + git_commit *commit; + git_diff *diff; + const git_diff_delta *delta; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + char expected[8] = {'M', 'M', 'R', 'T', 'D', 'R', 'A', 'R'}; + + git_oid_fromstr(&oid, "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a"); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + diffopts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; + cl_git_pass(git_diff__commit(&diff, g_repo, commit, &diffopts)); + cl_git_pass(git_diff_find_similar(diff, NULL)); + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + cl_assert_equal_i(expected[i], git_diff_status_char(delta->status)); + } + + git_diff_free(diff); + git_commit_free(commit); +} |