]> git.proxmox.com Git - libgit2.git/blobdiff - tests/checkout/tree.c
New upstream version 1.3.0+dfsg.1
[libgit2.git] / tests / checkout / tree.c
index 50541a703a5fcde0225482fccdf5fee757636a8d..3241a3eb7672dbfe294ca0fc870833a95b63d3db 100644 (file)
@@ -4,12 +4,22 @@
 #include "git2/checkout.h"
 #include "repository.h"
 #include "buffer.h"
-#include "fileops.h"
+#include "futils.h"
 
 static git_repository *g_repo;
 static git_checkout_options g_opts;
 static git_object *g_object;
 
+static void assert_status_entrycount(git_repository *repo, size_t count)
+{
+       git_status_list *status;
+
+       cl_git_pass(git_status_list_new(&status, repo, NULL));
+       cl_assert_equal_i(count, git_status_list_entrycount(status));
+
+       git_status_list_free(status);
+}
+
 void test_checkout_tree__initialize(void)
 {
        g_repo = cl_git_sandbox_init("testrepo");
@@ -160,7 +170,7 @@ void test_checkout_tree__can_switch_branches(void)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
        cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir"));
@@ -180,7 +190,7 @@ void test_checkout_tree__can_switch_branches(void)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
        cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees"));
@@ -250,7 +260,7 @@ static int checkout_tree_with_blob_ignored_in_workdir(int strategy, bool isdir)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
        cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir"));
@@ -290,7 +300,7 @@ static int checkout_tree_with_blob_ignored_in_workdir(int strategy, bool isdir)
        cl_assert_equal_i(1, ignored);
 
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        error = git_checkout_tree(g_repo, obj, &opts);
 
@@ -306,7 +316,7 @@ void test_checkout_tree__conflict_on_ignored_when_not_overwriting(void)
        cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir(
                GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, false));
 
-       cl_assert_equal_i(GIT_EMERGECONFLICT, error);
+       cl_assert_equal_i(GIT_ECONFLICT, error);
 }
 
 void test_checkout_tree__can_overwrite_ignored_by_default(void)
@@ -327,7 +337,7 @@ void test_checkout_tree__conflict_on_ignored_folder_when_not_overwriting(void)
        cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir(
                GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, true));
 
-       cl_assert_equal_i(GIT_EMERGECONFLICT, error);
+       cl_assert_equal_i(GIT_ECONFLICT, error);
 }
 
 void test_checkout_tree__can_overwrite_ignored_folder_by_default(void)
@@ -364,7 +374,7 @@ void test_checkout_tree__can_update_only(void)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
        cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir"));
@@ -422,6 +432,41 @@ void test_checkout_tree__can_checkout_with_pattern(void)
        cl_assert(git_path_exists("testrepo/new.txt"));
 }
 
+void test_checkout_tree__pathlist_checkout_ignores_non_matches(void)
+{
+       char *entries[] = { "branch_file.txt", "link_to_new.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, "refs/heads/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"));
+
+       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"));
+
+       git_object_free(g_object);
+       cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479"));
+
+       g_opts.checkout_strategy =
+               GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
+       g_opts.paths.strings = entries;
+       g_opts.paths.count = 2;
+
+       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" };
@@ -496,8 +541,9 @@ void assert_conflict(
        /* 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_object_peel(&hack_tree, g_object, GIT_OBJECT_TREE));
        cl_git_pass(git_index_read_tree(index, (git_tree *)hack_tree));
+       cl_git_pass(git_index_write(index));
        git_object_free(hack_tree);
        git_object_free(g_object);
        g_object = NULL;
@@ -505,14 +551,14 @@ void assert_conflict(
        /* 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);
+       git_buf_dispose(&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));
+               GIT_ECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts));
 
        /* Stage the conflicting change */
        cl_git_pass(git_index_add_bypath(index, entry_path));
@@ -520,10 +566,10 @@ void assert_conflict(
        git_index_free(index);
 
        cl_assert_equal_i(
-               GIT_EMERGECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts));
+               GIT_ECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts));
 }
 
-void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT(void)
+void test_checkout_tree__checking_out_a_conflicting_type_change_returns_ECONFLICT(void)
 {
        /*
         * 099faba adds a symlink named 'link_to_new.txt'
@@ -533,7 +579,7 @@ void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECO
        assert_conflict("link_to_new.txt", "old.txt", "a65fedf", "099faba");
 }
 
-void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT_2(void)
+void test_checkout_tree__checking_out_a_conflicting_type_change_returns_ECONFLICT_2(void)
 {
        /*
         * cf80f8d adds a directory named 'a/'
@@ -543,7 +589,7 @@ void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECO
        assert_conflict("a", "hello\n", "a4a7dce", "cf80f8d");
 }
 
-void test_checkout_tree__checking_out_a_conflicting_content_change_returns_EMERGECONFLICT(void)
+void test_checkout_tree__checking_out_a_conflicting_content_change_returns_ECONFLICT(void)
 {
        /*
         * c47800c adds a symlink named 'branch_file.txt'
@@ -630,7 +676,7 @@ void test_checkout_tree__can_cancel_checkout_from_notify(void)
        assert_on_branch(g_repo, "master");
 
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        ca.filename = "new.txt";
        ca.error = -5555;
@@ -694,7 +740,9 @@ void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void)
        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_index_write(index));
+
+       cl_git_pass(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"));
@@ -748,7 +796,7 @@ void test_checkout_tree__can_write_to_empty_dirs(void)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
 
@@ -767,7 +815,7 @@ void test_checkout_tree__fails_when_dir_in_use(void)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
 
@@ -778,7 +826,7 @@ void test_checkout_tree__fails_when_dir_in_use(void)
        cl_git_pass(p_chdir("testrepo/a"));
 
        cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master"));
-       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
 
@@ -801,7 +849,7 @@ void test_checkout_tree__can_continue_when_dir_in_use(void)
                GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES;
 
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
 
@@ -812,7 +860,7 @@ void test_checkout_tree__can_continue_when_dir_in_use(void)
        cl_git_pass(p_chdir("testrepo/a"));
 
        cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master"));
-       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
 
@@ -844,7 +892,7 @@ void test_checkout_tree__target_directory_from_bare(void)
        opts.notify_payload = &cts;
 
        cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
-       cl_git_pass(git_object_lookup(&g_object, g_repo, &oid, GIT_OBJ_ANY));
+       cl_git_pass(git_object_lookup(&g_object, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_fail(git_checkout_tree(g_repo, g_object, &opts));
 
@@ -867,9 +915,9 @@ void test_checkout_tree__target_directory_from_bare(void)
 
 void test_checkout_tree__extremely_long_file_name(void)
 {
-       // A utf-8 string with 83 characters, but 249 bytes.
+       /* A utf-8 string with 83 characters, but 249 bytes. */
        const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97";
-       char path[1024];
+       char path[1024] = {0};
 
        g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
        cl_git_pass(git_revparse_single(&g_object, g_repo, "long-file-name"));
@@ -893,20 +941,20 @@ static void create_conflict(const char *path)
 
        memset(&entry, 0x0, sizeof(git_index_entry));
        entry.mode = 0100644;
-       entry.flags = 1 << GIT_IDXENTRY_STAGESHIFT;
+       GIT_INDEX_ENTRY_STAGE_SET(&entry, 1);
        git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46");
        entry.path = path;
        cl_git_pass(git_index_add(index, &entry));
 
-       entry.flags = 2 << GIT_IDXENTRY_STAGESHIFT;
+       GIT_INDEX_ENTRY_STAGE_SET(&entry, 2);
        git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf");
        cl_git_pass(git_index_add(index, &entry));
 
-       entry.flags = 3 << GIT_IDXENTRY_STAGESHIFT;
+       GIT_INDEX_ENTRY_STAGE_SET(&entry, 3);
        git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863");
        cl_git_pass(git_index_add(index, &entry));
 
-       git_index_write(index);
+       cl_git_pass(git_index_write(index));
        git_index_free(index);
 }
 
@@ -919,7 +967,7 @@ void test_checkout_tree__fails_when_conflicts_exist_in_index(void)
        opts.checkout_strategy = GIT_CHECKOUT_SAFE;
 
        cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
-       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        create_conflict("conflicts.txt");
 
@@ -946,7 +994,7 @@ void test_checkout_tree__filemode_preserved_in_index(void)
 
        cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
        cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0));
-       cl_assert_equal_i(0100755, entry->mode);
+       cl_assert(GIT_PERMS_IS_EXEC(entry->mode));
 
        git_commit_free(commit);
 
@@ -957,7 +1005,7 @@ void test_checkout_tree__filemode_preserved_in_index(void)
 
        cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
        cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0));
-       cl_assert_equal_i(0100644, entry->mode);
+       cl_assert(!GIT_PERMS_IS_EXEC(entry->mode));
 
        git_commit_free(commit);
 
@@ -968,7 +1016,18 @@ void test_checkout_tree__filemode_preserved_in_index(void)
 
        cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
        cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0));
-       cl_assert_equal_i(0100755, entry->mode);
+       cl_assert(GIT_PERMS_IS_EXEC(entry->mode));
+
+       git_commit_free(commit);
+
+
+       /* Finally, check out the text file again and check that the exec bit is cleared */
+       cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9"));
+       cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+       cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+       cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0));
+       cl_assert(!GIT_PERMS_IS_EXEC(entry->mode));
 
        git_commit_free(commit);
 
@@ -976,13 +1035,82 @@ void test_checkout_tree__filemode_preserved_in_index(void)
        git_index_free(index);
 }
 
+mode_t read_filemode(const char *path)
+{
+       git_buf fullpath = GIT_BUF_INIT;
+       struct stat st;
+       mode_t result;
+
+       git_buf_joinpath(&fullpath, "testrepo", path);
+       cl_must_pass(p_stat(fullpath.ptr, &st));
+
+       result = GIT_PERMS_IS_EXEC(st.st_mode) ?
+               GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB;
+
+       git_buf_dispose(&fullpath);
+
+       return result;
+}
+
+void test_checkout_tree__filemode_preserved_in_workdir(void)
+{
+#ifndef GIT_WIN32
+       git_oid executable_oid;
+       git_commit *commit;
+       git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+       opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+       /* test a freshly added executable */
+       cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
+       cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+       cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+       cl_assert(GIT_PERMS_IS_EXEC(read_filemode("executable.txt")));
+
+       git_commit_free(commit);
+
+
+       /* Now start with a commit which has a text file */
+       cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9"));
+       cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+       cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+       cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt")));
+
+       git_commit_free(commit);
+
+
+       /* And then check out to a commit which converts the text file to an executable */
+       cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e"));
+       cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+       cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+       cl_assert(GIT_PERMS_IS_EXEC(read_filemode("a/b.txt")));
+
+       git_commit_free(commit);
+
+
+       /* Finally, check out the text file again and check that the exec bit is cleared */
+       cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9"));
+       cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+       cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+       cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt")));
+
+       git_commit_free(commit);
+#else
+       cl_skip();
+#endif
+}
+
 void test_checkout_tree__removes_conflicts(void)
 {
        git_oid commit_id;
        git_commit *commit;
        git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
        git_index *index;
-       
+
        cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
        cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
 
@@ -999,7 +1127,7 @@ void test_checkout_tree__removes_conflicts(void)
        create_conflict("other.txt");
        cl_git_mkfile("testrepo/other.txt", "This is another conflict file.\n");
 
-       git_index_write(index);
+       cl_git_pass(git_index_write(index));
 
        cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
 
@@ -1025,7 +1153,7 @@ void test_checkout_tree__removes_conflicts_only_by_pathscope(void)
        git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
        git_index *index;
        const char *path = "executable.txt";
-       
+
        cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
        cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
 
@@ -1044,7 +1172,7 @@ void test_checkout_tree__removes_conflicts_only_by_pathscope(void)
        create_conflict("other.txt");
        cl_git_mkfile("testrepo/other.txt", "This is another conflict file.\n");
 
-       git_index_write(index);
+       cl_git_pass(git_index_write(index));
 
        cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
 
@@ -1123,7 +1251,7 @@ void test_checkout_tree__case_changing_rename(void)
 
        cl_git_pass(git_checkout_tree(g_repo, (git_object *)master_commit, &opts));
        cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master"));
-       
+
        assert_on_branch(g_repo, "master");
 
        cl_assert(git_path_isfile("testrepo/README"));
@@ -1159,7 +1287,7 @@ void test_checkout_tree__can_collect_perfdata(void)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
 
@@ -1200,7 +1328,7 @@ void test_checkout_tree__caches_attributes_during_checkout(void)
        opts.paths.count = 2;
 
        cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/ident"));
-       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+       cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
 
@@ -1218,8 +1346,8 @@ void test_checkout_tree__caches_attributes_during_checkout(void)
        cl_assert_equal_strn(ident1.ptr, "# $Id: ", 7);
        cl_assert_equal_strn(ident2.ptr, "# $Id: ", 7);
 
-       git_buf_free(&ident1);
-       git_buf_free(&ident2);
+       git_buf_dispose(&ident1);
+       git_buf_dispose(&ident2);
        git_object_free(obj);
 }
 
@@ -1235,7 +1363,7 @@ void test_checkout_tree__can_not_update_index(void)
                GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_UPDATE_INDEX;
 
        cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
-       cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJ_ANY));
+       cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_reset(g_repo, head, GIT_RESET_HARD, &g_opts));
 
@@ -1272,7 +1400,7 @@ void test_checkout_tree__can_update_but_not_write_index(void)
                GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_WRITE_INDEX;
 
        cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
-       cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJ_ANY));
+       cl_git_pass(git_object_lookup(&head, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_reset(g_repo, head, GIT_RESET_HARD, &g_opts));
 
@@ -1318,7 +1446,7 @@ void test_checkout_tree__safe_proceeds_if_no_index(void)
        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_object_lookup(&obj, g_repo, &oid, GIT_OBJECT_ANY));
 
        cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
        cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees"));
@@ -1338,3 +1466,219 @@ void test_checkout_tree__safe_proceeds_if_no_index(void)
        git_object_free(obj);
 }
 
+static int checkout_conflict_count_cb(
+       git_checkout_notify_t why,
+       const char *path,
+       const git_diff_file *b,
+       const git_diff_file *t,
+       const git_diff_file *w,
+       void *payload)
+{
+       size_t *n = payload;
+
+       GIT_UNUSED(why);
+       GIT_UNUSED(path);
+       GIT_UNUSED(b);
+       GIT_UNUSED(t);
+       GIT_UNUSED(w);
+
+       (*n)++;
+
+       return 0;
+}
+
+/* A repo that has a HEAD (even a properly born HEAD that peels to
+ * a commit) but no index should be treated as if it's an empty baseline
+ */
+void test_checkout_tree__baseline_is_empty_when_no_index(void)
+{
+       git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+       git_reference *head;
+       git_object *obj;
+       size_t conflicts = 0;
+
+       assert_on_branch(g_repo, "master");
+
+       cl_git_pass(git_repository_head(&head, g_repo));
+       cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT));
+
+       cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL));
+
+       cl_must_pass(p_unlink("testrepo/.git/index"));
+
+       /* for a safe checkout, we should have checkout conflicts with
+        * the existing untracked files.
+        */
+       opts.checkout_strategy &= ~GIT_CHECKOUT_FORCE;
+       opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
+       opts.notify_cb = checkout_conflict_count_cb;
+       opts.notify_payload = &conflicts;
+
+       cl_git_fail_with(GIT_ECONFLICT, git_checkout_tree(g_repo, obj, &opts));
+       cl_assert_equal_i(4, conflicts);
+
+       /* but force should succeed and update the index */
+       opts.checkout_strategy |= GIT_CHECKOUT_FORCE;
+       cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+
+       assert_status_entrycount(g_repo, 0);
+
+       git_object_free(obj);
+       git_reference_free(head);
+}
+
+void test_checkout_tree__mode_change_is_force_updated(void)
+{
+       git_index *index;
+       git_reference *head;
+       git_object *obj;
+
+       if (!cl_is_chmod_supported())
+               clar__skip();
+
+       assert_on_branch(g_repo, "master");
+       cl_git_pass(git_repository_index(&index, g_repo));
+       cl_git_pass(git_repository_head(&head, g_repo));
+       cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT));
+
+       cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL));
+       assert_status_entrycount(g_repo, 0);
+
+       /* update the mode on-disk */
+       cl_must_pass(p_chmod("testrepo/README", 0755));
+
+       assert_status_entrycount(g_repo, 1);
+       cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts));
+       assert_status_entrycount(g_repo, 0);
+
+       /* update the mode on-disk and in the index */
+       cl_must_pass(p_chmod("testrepo/README", 0755));
+       cl_must_pass(git_index_add_bypath(index, "README"));
+
+       cl_git_pass(git_index_write(index));
+       assert_status_entrycount(g_repo, 1);
+
+       cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts));
+       cl_git_pass(git_index_write(index));
+
+       assert_status_entrycount(g_repo, 0);
+
+       git_object_free(obj);
+       git_reference_free(head);
+       git_index_free(index);
+}
+
+void test_checkout_tree__nullopts(void)
+{
+       cl_git_pass(git_checkout_tree(g_repo, NULL, NULL));
+}
+
+static void modify_index_ondisk(void)
+{
+       git_repository *other_repo;
+       git_index *other_index;
+       git_index_entry entry = {{0}};
+
+       cl_git_pass(git_repository_open(&other_repo, git_repository_workdir(g_repo)));
+       cl_git_pass(git_repository_index(&other_index, other_repo));
+
+       cl_git_pass(git_oid_fromstr(&entry.id, "1385f264afb75a56a5bec74243be9b367ba4ca08"));
+       entry.mode = 0100644;
+       entry.path = "README";
+
+       cl_git_pass(git_index_add(other_index, &entry));
+       cl_git_pass(git_index_write(other_index));
+
+       git_index_free(other_index);
+       git_repository_free(other_repo);
+}
+
+static void modify_index_and_checkout_tree(git_checkout_options *opts)
+{
+       git_index *index;
+       git_reference *head;
+       git_object *obj;
+
+       /* External changes to the index are maintained by default */
+       cl_git_pass(git_repository_index(&index, g_repo));
+       cl_git_pass(git_repository_head(&head, g_repo));
+       cl_git_pass(git_reference_peel(&obj, head, GIT_OBJECT_COMMIT));
+
+       cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL));
+       assert_status_entrycount(g_repo, 0);
+
+       modify_index_ondisk();
+
+       /* The file in the index remains modified */
+       cl_git_pass(git_checkout_tree(g_repo, obj, opts));
+
+       git_object_free(obj);
+       git_reference_free(head);
+       git_index_free(index);
+}
+
+void test_checkout_tree__retains_external_index_changes(void)
+{
+       git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+       opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+       modify_index_and_checkout_tree(&opts);
+       assert_status_entrycount(g_repo, 1);
+}
+
+void test_checkout_tree__no_index_refresh(void)
+{
+       git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+       opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_NO_REFRESH;
+
+       modify_index_and_checkout_tree(&opts);
+       assert_status_entrycount(g_repo, 0);
+}
+
+void test_checkout_tree__dry_run(void)
+{
+       git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+       git_oid oid;
+       git_object *obj = NULL;
+       checkout_counts ct;
+
+       /* 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 dry run enabled */
+
+       memset(&ct, 0, sizeof(ct));
+       opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DRY_RUN;
+       opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+       opts.notify_cb = checkout_count_callback;
+       opts.notify_payload = &ct;
+
+       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_OBJECT_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");
+
+       /* these normally would have been created and updated, but with
+        * DRY_RUN they will be unchanged.
+        */
+       cl_assert(!git_path_isdir("testrepo/a"));
+       check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n");
+
+       /* check that notify callback was invoked */
+       cl_assert_equal_i(ct.n_updates, 2);
+       
+       git_object_free(obj);
+}