diff options
Diffstat (limited to 'tests-clar/checkout')
| -rw-r--r-- | tests-clar/checkout/binaryunicode.c | 58 | ||||
| -rw-r--r-- | tests-clar/checkout/checkout_helpers.c | 93 | ||||
| -rw-r--r-- | tests-clar/checkout/checkout_helpers.h | 21 | ||||
| -rw-r--r-- | tests-clar/checkout/crlf.c | 147 | ||||
| -rw-r--r-- | tests-clar/checkout/head.c | 63 | ||||
| -rw-r--r-- | tests-clar/checkout/index.c | 507 | ||||
| -rw-r--r-- | tests-clar/checkout/tree.c | 503 | ||||
| -rw-r--r-- | tests-clar/checkout/typechange.c | 240 |
8 files changed, 1632 insertions, 0 deletions
diff --git a/tests-clar/checkout/binaryunicode.c b/tests-clar/checkout/binaryunicode.c new file mode 100644 index 000000000..14ab9fdfa --- /dev/null +++ b/tests-clar/checkout/binaryunicode.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo/repo_helpers.h" +#include "path.h" +#include "fileops.h" + +static git_repository *g_repo; + +void test_checkout_binaryunicode__initialize(void) +{ + g_repo = cl_git_sandbox_init("binaryunicode"); +} + +void test_checkout_binaryunicode__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void execute_test(void) +{ + git_oid oid, check; + git_commit *commit; + git_tree *tree; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/branch1")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + cl_git_pass(git_commit_tree(&tree, commit)); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_checkout_tree(g_repo, (git_object *)tree, &opts)); + + git_tree_free(tree); + git_commit_free(commit); + + /* Verify that the lenna.jpg file was checked out correctly */ + cl_git_pass(git_oid_fromstr(&check, "8ab005d890fe53f65eda14b23672f60d9f4ec5a1")); + cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/lenna.jpg", GIT_OBJ_BLOB)); + cl_assert(git_oid_equal(&oid, &check)); + + /* Verify that the text file was checked out correctly */ + cl_git_pass(git_oid_fromstr(&check, "965b223880dd4249e2c66a0cc0b4cffe1dc40f5a")); + cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/utf16_withbom_noeol_crlf.txt", GIT_OBJ_BLOB)); + cl_assert(git_oid_equal(&oid, &check)); +} + +void test_checkout_binaryunicode__noautocrlf(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", false); + execute_test(); +} + +void test_checkout_binaryunicode__autocrlf(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", true); + execute_test(); +} diff --git a/tests-clar/checkout/checkout_helpers.c b/tests-clar/checkout/checkout_helpers.c new file mode 100644 index 000000000..ab93a89bd --- /dev/null +++ b/tests-clar/checkout/checkout_helpers.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" +#include "refs.h" +#include "fileops.h" + +/* this is essentially the code from git__unescape modified slightly */ +void strip_cr_from_buf(git_buf *buf) +{ + char *scan, *pos = buf->ptr, *end = pos + buf->size; + + for (scan = pos; scan < end; pos++, scan++) { + if (*scan == '\r') + scan++; /* skip '\r' */ + if (pos != scan) + *pos = *scan; + } + + *pos = '\0'; + buf->size = (pos - buf->ptr); +} + +void assert_on_branch(git_repository *repo, const char *branch) +{ + git_reference *head; + git_buf bname = GIT_BUF_INIT; + + cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); + cl_assert_(git_reference_type(head) == GIT_REF_SYMBOLIC, branch); + + cl_git_pass(git_buf_joinpath(&bname, "refs/heads", branch)); + cl_assert_equal_s(bname.ptr, git_reference_symbolic_target(head)); + + git_reference_free(head); + git_buf_free(&bname); +} + +void reset_index_to_treeish(git_object *treeish) +{ + git_object *tree; + git_index *index; + git_repository *repo = git_object_owner(treeish); + + cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJ_TREE)); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_read_tree(index, (git_tree *)tree)); + cl_git_pass(git_index_write(index)); + + git_object_free(tree); + git_index_free(index); +} + +static void check_file_contents_internal( + const char *path, + const char *expected_content, + bool strip_cr, + const char *file, + int line, + const char *msg) +{ + int fd; + char data[1024] = {0}; + git_buf buf = GIT_BUF_INIT; + size_t expected_len = expected_content ? strlen(expected_content) : 0; + + fd = p_open(path, O_RDONLY); + cl_assert(fd >= 0); + + buf.ptr = data; + buf.size = p_read(fd, buf.ptr, sizeof(data)); + + cl_git_pass(p_close(fd)); + + if (strip_cr) + strip_cr_from_buf(&buf); + + clar__assert_equal_i((int)expected_len, (int)buf.size, file, line, "strlen(expected_content) != strlen(actual_content)", 1); + clar__assert_equal_s(expected_content, buf.ptr, file, line, msg, 1); +} + +void check_file_contents_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg) +{ + check_file_contents_internal(path, expected, false, file, line, msg); +} + +void check_file_contents_nocr_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg) +{ + check_file_contents_internal(path, expected, true, file, line, msg); +} diff --git a/tests-clar/checkout/checkout_helpers.h b/tests-clar/checkout/checkout_helpers.h new file mode 100644 index 000000000..34053809d --- /dev/null +++ b/tests-clar/checkout/checkout_helpers.h @@ -0,0 +1,21 @@ +#include "buffer.h" +#include "git2/object.h" +#include "git2/repository.h" + +extern void strip_cr_from_buf(git_buf *buf); +extern void assert_on_branch(git_repository *repo, const char *branch); +extern void reset_index_to_treeish(git_object *treeish); + +extern void check_file_contents_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg); + +extern void check_file_contents_nocr_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg); + +#define check_file_contents(PATH,EXP) \ + check_file_contents_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH) + +#define check_file_contents_nocr(PATH,EXP) \ + check_file_contents_nocr_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH) diff --git a/tests-clar/checkout/crlf.c b/tests-clar/checkout/crlf.c new file mode 100644 index 000000000..285b1f272 --- /dev/null +++ b/tests-clar/checkout/crlf.c @@ -0,0 +1,147 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" + +#include "git2/checkout.h" +#include "repository.h" + +#define UTF8_BOM "\xEF\xBB\xBF" +#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n" +#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n" +#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n" + +#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" +#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n" + +static git_repository *g_repo; + +void test_checkout_crlf__initialize(void) +{ + g_repo = cl_git_sandbox_init("crlf"); +} + +void test_checkout_crlf__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_checkout_crlf__detect_crlf_autocrlf_false(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + git_checkout_head(g_repo, &opts); + + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void) +{ + git_index *index; + const git_index_entry *entry; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + git_checkout_head(g_repo, &opts); + + git_repository_index(&index, g_repo); + + cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); + cl_assert(entry->file_size == strlen(ALL_LF_TEXT_RAW)); + + cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); + cl_assert(entry->file_size == strlen(ALL_CRLF_TEXT_RAW)); + + git_index_free(index); +} + +void test_checkout_crlf__detect_crlf_autocrlf_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + else + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); + + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__more_lf_autocrlf_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) + check_file_contents("./crlf/more-lf", MORE_LF_TEXT_RAW); + else + check_file_contents("./crlf/more-lf", MORE_LF_TEXT_AS_CRLF); +} + +void test_checkout_crlf__more_crlf_autocrlf_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) + check_file_contents("./crlf/more-crlf", MORE_CRLF_TEXT_RAW); + else + check_file_contents("./crlf/more-crlf", MORE_CRLF_TEXT_AS_CRLF); +} + +void test_checkout_crlf__all_crlf_autocrlf_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) +{ + git_index *index; + const git_index_entry *entry; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + git_repository_index(&index, g_repo); + + cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) + cl_assert_equal_sz(strlen(ALL_LF_TEXT_RAW), entry->file_size); + else + cl_assert_equal_sz(strlen(ALL_LF_TEXT_AS_CRLF), entry->file_size); + + cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); + cl_assert_equal_sz(strlen(ALL_CRLF_TEXT_RAW), entry->file_size); + + git_index_free(index); +} diff --git a/tests-clar/checkout/head.c b/tests-clar/checkout/head.c new file mode 100644 index 000000000..46646f8bf --- /dev/null +++ b/tests-clar/checkout/head.c @@ -0,0 +1,63 @@ +#include "clar_libgit2.h" +#include "refs.h" +#include "repo/repo_helpers.h" +#include "path.h" +#include "fileops.h" + +static git_repository *g_repo; + +void test_checkout_head__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_checkout_head__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_checkout_head__orphaned_head_returns_GIT_EORPHANEDHEAD(void) +{ + make_head_orphaned(g_repo, NON_EXISTING_HEAD); + + cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL)); +} + +void test_checkout_head__with_index_only_tree(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_index *index; + + /* let's start by getting things into a known state */ + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + /* now let's stage some new stuff including a new directory */ + + cl_git_pass(git_repository_index(&index, g_repo)); + + p_mkdir("testrepo/newdir", 0777); + cl_git_mkfile("testrepo/newdir/newfile.txt", "new file\n"); + + cl_git_pass(git_index_add_bypath(index, "newdir/newfile.txt")); + cl_git_pass(git_index_write(index)); + + cl_assert(git_path_isfile("testrepo/newdir/newfile.txt")); + cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) != NULL); + + git_index_free(index); + + /* okay, so now we have staged this new file; let's see if we can remove */ + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read(index)); /* reload if needed */ + + cl_assert(!git_path_isfile("testrepo/newdir/newfile.txt")); + cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) == NULL); + + git_index_free(index); +} diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c new file mode 100644 index 000000000..78ff5ac62 --- /dev/null +++ b/tests-clar/checkout/index.c @@ -0,0 +1,507 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" + +#include "git2/checkout.h" +#include "repository.h" + +static git_repository *g_repo; + +void test_checkout_index__initialize(void) +{ + git_tree *tree; + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + reset_index_to_treeish((git_object *)tree); + git_tree_free(tree); + + cl_git_rewritefile( + "./testrepo/.gitattributes", + "* text eol=lf\n"); +} + +void test_checkout_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_checkout_index__cannot_checkout_a_bare_repository(void) +{ + test_checkout_index__cleanup(); + + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); +} + +void test_checkout_index__can_create_missing_files(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__can_remove_untracked_files(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH); + cl_git_mkfile("./testrepo/dir/one", "one\n"); + cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); + + cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir")); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/dir")); +} + +void test_checkout_index__honor_the_specified_pathspecs(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + char *entries[] = { "*.txt" }; + + opts.paths.strings = entries; + opts.paths.count = 1; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__honor_the_gitattributes_directives(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + const char *attributes = + "branch_file.txt text eol=crlf\n" + "new.txt text eol=lf\n"; + + cl_git_mkfile("./testrepo/.gitattributes", attributes); + cl_repo_set_bool(g_repo, "core.autocrlf", false); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) +{ +#ifdef GIT_WIN32 + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + const char *expected_readme_text = "hey there\r\n"; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/README", expected_readme_text); +#endif +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_repo_set_bool(g_repo, "core.symlinks", true); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + +#ifdef GIT_WIN32 + check_file_contents("./testrepo/link_to_new.txt", "new.txt"); +#else + { + char link_data[1024]; + size_t link_size = 1024; + + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + check_file_contents("./testrepo/link_to_new.txt", "my new file\n"); + } +#endif +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_repo_set_bool(g_repo, "core.symlinks", false); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/link_to_new.txt", "new.txt"); +} + +void test_checkout_index__donot_overwrite_modified_file_by_default(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + /* set this up to not return an error code on conflicts, but it + * still will not have permission to overwrite anything... + */ + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "This isn't what's stored!"); +} + +void test_checkout_index__can_overwrite_modified_file(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_disable_filters(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + opts.disable_filters = false; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "my new file\r\n"); + + p_unlink("./testrepo/new.txt"); + + opts.disable_filters = true; + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_dir_modes(void) +{ +#ifndef GIT_WIN32 + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + struct stat st; + git_oid oid; + git_commit *commit; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + + reset_index_to_treeish((git_object *)commit); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + opts.dir_mode = 0701; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_git_pass(p_stat("./testrepo/a", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0701); + + /* File-mode test, since we're on the 'dir' branch */ + cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0755); + + git_commit_free(commit); +#endif +} + +void test_checkout_index__options_override_file_modes(void) +{ +#ifndef GIT_WIN32 + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + struct stat st; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + opts.file_mode = 0700; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_git_pass(p_stat("./testrepo/new.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); +#endif +} + +void test_checkout_index__options_open_flags(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_git_mkfile("./testrepo/new.txt", "hi\n"); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); +} + +struct notify_data { + const char *file; + const char *sha; +}; + +static int test_checkout_notify_cb( + 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) +{ + struct notify_data *expectations = (struct notify_data *)payload; + + GIT_UNUSED(workdir); + + cl_assert_equal_i(GIT_CHECKOUT_NOTIFY_CONFLICT, why); + cl_assert_equal_s(expectations->file, path); + cl_assert_equal_i(0, git_oid_streq(&baseline->oid, expectations->sha)); + cl_assert_equal_i(0, git_oid_streq(&target->oid, expectations->sha)); + + return 0; +} + +void test_checkout_index__can_notify_of_skipped_files(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + struct notify_data data; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + /* + * $ git ls-tree HEAD + * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README + * 100644 blob 3697d64be941a53d4ae8f6a271e4e3fa56b022cc branch_file.txt + * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt + */ + data.file = "new.txt"; + data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"; + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; + opts.notify_cb = test_checkout_notify_cb; + opts.notify_payload = &data; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); +} + +static int dont_notify_cb( + 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) +{ + GIT_UNUSED(why); + GIT_UNUSED(path); + GIT_UNUSED(baseline); + GIT_UNUSED(target); + GIT_UNUSED(workdir); + GIT_UNUSED(payload); + + cl_assert(false); + + return 0; +} + +void test_checkout_index__wont_notify_of_expected_line_ending_changes(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_mkfile("./testrepo/new.txt", "my new file\r\n"); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT; + opts.notify_cb = dont_notify_cb; + opts.notify_payload = NULL; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); +} + +static void checkout_progress_counter( + const char *path, size_t cur, size_t tot, void *payload) +{ + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + (*(int *)payload)++; +} + +void test_checkout_index__calls_progress_callback(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + int calls = 0; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + opts.progress_cb = checkout_progress_counter; + opts.progress_payload = &calls; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + cl_assert(calls > 0); +} + +void test_checkout_index__can_overcome_name_clashes(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_index *index; + + cl_git_pass(git_repository_index(&index, g_repo)); + git_index_clear(index); + + cl_git_mkfile("./testrepo/path0", "content\r\n"); + cl_git_pass(p_mkdir("./testrepo/path1", 0777)); + cl_git_mkfile("./testrepo/path1/file1", "content\r\n"); + + cl_git_pass(git_index_add_bypath(index, "path0")); + cl_git_pass(git_index_add_bypath(index, "path1/file1")); + + cl_git_pass(p_unlink("./testrepo/path0")); + cl_git_pass(git_futils_rmdir_r( + "./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_git_mkfile("./testrepo/path1", "content\r\n"); + cl_git_pass(p_mkdir("./testrepo/path0", 0777)); + cl_git_mkfile("./testrepo/path0/file0", "content\r\n"); + + cl_assert(git_path_isfile("./testrepo/path1")); + cl_assert(git_path_isfile("./testrepo/path0/file0")); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS; + cl_git_pass(git_checkout_index(g_repo, index, &opts)); + + cl_assert(git_path_isfile("./testrepo/path1")); + cl_assert(git_path_isfile("./testrepo/path0/file0")); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_index(g_repo, index, &opts)); + + cl_assert(git_path_isfile("./testrepo/path0")); + cl_assert(git_path_isfile("./testrepo/path1/file1")); + + git_index_free(index); +} + +void test_checkout_index__validates_struct_version(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + const git_error *err; + + opts.version = 1024; + cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); + + err = giterr_last(); + cl_assert_equal_i(err->klass, GITERR_INVALID); + + opts.version = 0; + giterr_clear(); + cl_git_fail(git_checkout_index(g_repo, NULL, &opts)); + + err = giterr_last(); + cl_assert_equal_i(err->klass, GITERR_INVALID); +} + +void test_checkout_index__can_update_prefixed_files(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_mkfile("./testrepo/READ", "content\n"); + cl_git_mkfile("./testrepo/README.after", "content\n"); + cl_git_pass(p_mkdir("./testrepo/branch_file", 0777)); + cl_git_pass(p_mkdir("./testrepo/branch_file/contained_dir", 0777)); + cl_git_mkfile("./testrepo/branch_file/contained_file", "content\n"); + cl_git_pass(p_mkdir("./testrepo/branch_file.txt.after", 0777)); + + opts.checkout_strategy = + GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + /* remove untracked will remove the .gitattributes file before the blobs + * were created, so they will have had crlf filtering applied on Windows + */ + check_file_contents_nocr("./testrepo/README", "hey there\n"); + check_file_contents_nocr("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("./testrepo/new.txt", "my new file\n"); + + cl_assert(!git_path_exists("testrepo/READ")); + cl_assert(!git_path_exists("testrepo/README.after")); + cl_assert(!git_path_exists("testrepo/branch_file")); + cl_assert(!git_path_exists("testrepo/branch_file.txt.after")); +} + +void test_checkout_index__can_checkout_a_newly_initialized_repository(void) +{ + test_checkout_index__cleanup(); + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); +} + +void test_checkout_index__issue_1397(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + test_checkout_index__cleanup(); + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); +} diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c new file mode 100644 index 000000000..0748b22e0 --- /dev/null +++ b/tests-clar/checkout/tree.c @@ -0,0 +1,503 @@ +#include "clar_libgit2.h" +#include "checkout_helpers.h" + +#include "git2/checkout.h" +#include "repository.h" +#include "buffer.h" +#include "fileops.h" + +static git_repository *g_repo; +static git_checkout_opts g_opts; +static git_object *g_object; + +void test_checkout_tree__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + + GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTS_VERSION); + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; +} + +void test_checkout_tree__cleanup(void) +{ + git_object_free(g_object); + g_object = NULL; + + cl_git_sandbox_cleanup(); +} + +void test_checkout_tree__cannot_checkout_a_non_treeish(void) +{ + /* blob */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + cl_git_fail(git_checkout_tree(g_repo, g_object, NULL)); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) +{ + char *entries[] = { "ab/de/" }; + + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt")); +} + +void test_checkout_tree__can_checkout_and_remove_directory(void) +{ + cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); + + /* Checkout brach "subtrees" and update HEAD, so that HEAD matches the + * current working tree + */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); + + cl_assert_equal_i(true, git_path_isdir("./testrepo/ab/")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt")); + + git_object_free(g_object); + g_object = NULL; + + /* Checkout brach "master" and update HEAD, so that HEAD matches the + * current working tree + */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); + + /* This directory should no longer exist */ + cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) +{ + char *entries[] = { "de/" }; + + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab")); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/de/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert_equal_i(true, git_path_isfile("./testrepo/de/2.txt")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/de/fgh/1.txt")); +} + +static void progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + *was_called = true; +} + +void test_checkout_tree__calls_progress_callback(void) +{ + bool was_called = 0; + + g_opts.progress_cb = progress; + g_opts.progress_payload = &was_called; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert_equal_i(was_called, true); +} + +void test_checkout_tree__doesnt_write_unrequested_files_to_worktree(void) +{ + git_oid master_oid; + git_oid chomped_oid; + git_commit* p_master_commit; + git_commit* p_chomped_commit; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + git_oid_fromstr(&master_oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + git_oid_fromstr(&chomped_oid, "e90810b8df3e80c413d903f631643c716887138d"); + cl_git_pass(git_commit_lookup(&p_master_commit, g_repo, &master_oid)); + cl_git_pass(git_commit_lookup(&p_chomped_commit, g_repo, &chomped_oid)); + + /* GIT_CHECKOUT_NONE should not add any file to the working tree from the + * index as it is supposed to be a dry run. + */ + opts.checkout_strategy = GIT_CHECKOUT_NONE; + git_checkout_tree(g_repo, (git_object*)p_chomped_commit, &opts); + cl_assert_equal_i(false, git_path_isfile("testrepo/readme.txt")); + + git_commit_free(p_master_commit); + git_commit_free(p_chomped_commit); +} + +void test_checkout_tree__can_switch_branches(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid oid; + git_object *obj = NULL; + + assert_on_branch(g_repo, "master"); + + /* do first checkout with FORCE because we don't know if testrepo + * base data is clean for a checkout or not + */ + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + cl_assert(git_path_isfile("testrepo/README")); + cl_assert(git_path_isfile("testrepo/branch_file.txt")); + cl_assert(git_path_isfile("testrepo/new.txt")); + cl_assert(git_path_isfile("testrepo/a/b.txt")); + + cl_assert(!git_path_isdir("testrepo/ab")); + + assert_on_branch(g_repo, "dir"); + + git_object_free(obj); + + /* do second checkout safe because we should be clean after first */ + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees")); + + cl_assert(git_path_isfile("testrepo/README")); + cl_assert(git_path_isfile("testrepo/branch_file.txt")); + cl_assert(git_path_isfile("testrepo/new.txt")); + cl_assert(git_path_isfile("testrepo/ab/4.txt")); + cl_assert(git_path_isfile("testrepo/ab/c/3.txt")); + cl_assert(git_path_isfile("testrepo/ab/de/2.txt")); + cl_assert(git_path_isfile("testrepo/ab/de/fgh/1.txt")); + + cl_assert(!git_path_isdir("testrepo/a")); + + assert_on_branch(g_repo, "subtrees"); + + git_object_free(obj); +} + +void test_checkout_tree__can_remove_untracked(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_mkfile("testrepo/untracked_file", "as you wish"); + cl_assert(git_path_isfile("testrepo/untracked_file")); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_path_isfile("testrepo/untracked_file")); +} + +void test_checkout_tree__can_remove_ignored(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + int ignored = 0; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_IGNORED; + + cl_git_mkfile("testrepo/ignored_file", "as you wish"); + + cl_git_pass(git_ignore_add_rule(g_repo, "ignored_file\n")); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ignored_file")); + cl_assert_equal_i(1, ignored); + + cl_assert(git_path_isfile("testrepo/ignored_file")); + + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_path_isfile("testrepo/ignored_file")); +} + +void test_checkout_tree__can_update_only(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid oid; + git_object *obj = NULL; + + /* first let's get things into a known state - by checkout out the HEAD */ + + assert_on_branch(g_repo, "master"); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_head(g_repo, &opts)); + + cl_assert(!git_path_isdir("testrepo/a")); + + check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); + + /* now checkout branch but with update only */ + + opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir")); + + assert_on_branch(g_repo, "dir"); + + /* this normally would have been created (which was tested separately in + * the test_checkout_tree__can_switch_branches test), but with + * UPDATE_ONLY it will not have been created. + */ + cl_assert(!git_path_isdir("testrepo/a")); + + /* but this file still should have been updated */ + check_file_contents_nocr("testrepo/branch_file.txt", "hi\n"); + + git_object_free(obj); +} + +void test_checkout_tree__can_checkout_with_pattern(void) +{ + char *entries[] = { "[l-z]*.txt" }; + + /* reset to beginning of history (i.e. just a README file) */ + + g_opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(g_object))); + + git_object_free(g_object); + g_object = NULL; + + cl_assert(git_path_exists("testrepo/README")); + cl_assert(!git_path_exists("testrepo/branch_file.txt")); + cl_assert(!git_path_exists("testrepo/link_to_new.txt")); + cl_assert(!git_path_exists("testrepo/new.txt")); + + /* now to a narrow patterned checkout */ + + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert(git_path_exists("testrepo/README")); + cl_assert(!git_path_exists("testrepo/branch_file.txt")); + cl_assert(git_path_exists("testrepo/link_to_new.txt")); + cl_assert(git_path_exists("testrepo/new.txt")); +} + +void test_checkout_tree__can_disable_pattern_match(void) +{ + char *entries[] = { "b*.txt" }; + + /* reset to beginning of history (i.e. just a README file) */ + + g_opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(g_object))); + + git_object_free(g_object); + g_object = NULL; + + cl_assert(!git_path_isfile("testrepo/branch_file.txt")); + + /* now to a narrow patterned checkout, but disable pattern */ + + g_opts.checkout_strategy = + GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert(!git_path_isfile("testrepo/branch_file.txt")); + + /* let's try that again, but allow the pattern match */ + + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert(git_path_isfile("testrepo/branch_file.txt")); +} + +void assert_conflict( + const char *entry_path, + const char *new_content, + const char *parent_sha, + const char *commit_sha) +{ + git_index *index; + git_object *hack_tree; + git_reference *branch, *head; + git_buf file_path = GIT_BUF_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Create a branch pointing at the parent */ + cl_git_pass(git_revparse_single(&g_object, g_repo, parent_sha)); + cl_git_pass(git_branch_create(&branch, g_repo, + "potential_conflict", (git_commit *)g_object, 0)); + + /* Make HEAD point to this branch */ + cl_git_pass(git_reference_symbolic_create( + &head, g_repo, "HEAD", git_reference_name(branch), 1)); + git_reference_free(head); + git_reference_free(branch); + + /* Checkout the parent */ + g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + /* Hack-ishy workaound to ensure *all* the index entries + * match the content of the tree + */ + cl_git_pass(git_object_peel(&hack_tree, g_object, GIT_OBJ_TREE)); + cl_git_pass(git_index_read_tree(index, (git_tree *)hack_tree)); + git_object_free(hack_tree); + git_object_free(g_object); + g_object = NULL; + + /* Create a conflicting file */ + cl_git_pass(git_buf_joinpath(&file_path, "./testrepo", entry_path)); + cl_git_mkfile(git_buf_cstr(&file_path), new_content); + git_buf_free(&file_path); + + /* Trying to checkout the original commit */ + cl_git_pass(git_revparse_single(&g_object, g_repo, commit_sha)); + + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + cl_assert_equal_i( + GIT_EMERGECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts)); + + /* Stage the conflicting change */ + cl_git_pass(git_index_add_bypath(index, entry_path)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_assert_equal_i( + GIT_EMERGECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts)); +} + +void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT(void) +{ + /* + * 099faba adds a symlink named 'link_to_new.txt' + * a65fedf is the parent of 099faba + */ + + assert_conflict("link_to_new.txt", "old.txt", "a65fedf", "099faba"); +} + +void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT_2(void) +{ + /* + * cf80f8d adds a directory named 'a/' + * a4a7dce is the parent of cf80f8d + */ + + assert_conflict("a", "hello\n", "a4a7dce", "cf80f8d"); +} + +void test_checkout_tree__checking_out_a_conflicting_content_change_returns_EMERGECONFLICT(void) +{ + /* + * c47800c adds a symlink named 'branch_file.txt' + * 5b5b025 is the parent of 763d71a + */ + + assert_conflict("branch_file.txt", "hello\n", "5b5b025", "c47800c"); +} + +void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void) +{ + git_index *index = NULL; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid tree_id, commit_id; + git_tree *tree = NULL; + git_commit *commit = NULL; + + git_repository_index(&index, g_repo); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&commit_id, g_repo, "refs/heads/master")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id)); + + cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &opts)); + cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master")); + + cl_git_pass(p_mkdir("./testrepo/this-is-dir", 0777)); + cl_git_mkfile("./testrepo/this-is-dir/contained_file", "content\n"); + + cl_git_pass(git_index_add_bypath(index, "this-is-dir/contained_file")); + git_index_write_tree(&tree_id, index); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + cl_git_pass(p_unlink("./testrepo/this-is-dir/contained_file")); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + opts.checkout_strategy = 1; + git_checkout_tree(g_repo, (git_object *)tree, &opts); + + git_tree_free(tree); + git_commit_free(commit); + git_index_free(index); +} + +void test_checkout_tree__issue_1397(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + const char *partial_oid = "8a7ef04"; + git_object *tree = NULL; + + test_checkout_tree__cleanup(); /* cleanup default checkout */ + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_revparse_single(&tree, g_repo, partial_oid)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_tree(g_repo, tree, &opts)); + + check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); + + git_object_free(tree); +} diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c new file mode 100644 index 000000000..b92cc23fa --- /dev/null +++ b/tests-clar/checkout/typechange.c @@ -0,0 +1,240 @@ +#include "clar_libgit2.h" +#include "git2/checkout.h" +#include "path.h" +#include "posix.h" +#include "fileops.h" + +static git_repository *g_repo = NULL; + +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 +}; + +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_path_isfile(path), path); +} + +static void assert_dir_exists(const char *path) +{ + cl_assert_(git_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_buf path = GIT_BUF_INIT; + + if (!root) + root = git_repository_workdir(repo); + cl_assert(root); + + cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJ_ANY)); + cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJ_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_buf_joinpath(&path, root, git_tree_entry_name(te))); + + switch (git_tree_entry_type(te)) { + case GIT_OBJ_COMMIT: + assert_dir_exists(path.ptr); + break; + case GIT_OBJ_TREE: + assert_dir_exists(path.ptr); + if (recurse) + assert_workdir_matches_tree( + repo, git_tree_entry_id(te), path.ptr, true); + break; + case GIT_OBJ_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_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_buf_free(&path); +} + +void test_checkout_typechange__checkout_typechanges_safe(void) +{ + int i; + git_object *obj; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_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 = GIT_CHECKOUT_FORCE; + + /* There are bugs in some submodule->tree changes that prevent + * SAFE from passing here, even though the following should work: + */ + /* !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_path_isdir("typechanges")); + cl_assert(git_path_exists("typechanges/a")); + cl_assert(git_path_exists("typechanges/b")); + cl_assert(git_path_exists("typechanges/c")); + cl_assert(git_path_exists("typechanges/d")); + cl_assert(git_path_exists("typechanges/e")); + } else { + cl_assert(git_path_isdir("typechanges")); + cl_assert(!git_path_exists("typechanges/a")); + cl_assert(!git_path_exists("typechanges/b")); + cl_assert(!git_path_exists("typechanges/c")); + cl_assert(!git_path_exists("typechanges/d")); + cl_assert(!git_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!"); +} + +void test_checkout_typechange__checkout_with_conflicts(void) +{ + int i; + git_object *obj; + git_checkout_opts opts = GIT_CHECKOUT_OPTS_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"); + + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + memset(&cts, 0, sizeof(cts)); + + cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); + cl_assert(cts.conflicts > 0); + cl_assert(cts.untracked > 0); + + opts.checkout_strategy = + GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED; + memset(&cts, 0, sizeof(cts)); + + cl_assert(git_path_exists("typechanges/untracked")); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + cl_assert_equal_i(0, cts.conflicts); + + cl_assert(!git_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); + } +} |
