From 431f98070f7494cabad2d1b674dfbcdafccd30b1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 27 Mar 2015 15:33:44 -0400 Subject: [PATCH 1/8] checkout test: ignore unstaged case-changing renames On Windows, you might sloppily rewrite a file (or have a sloppy text editor that does it for you) and accidentally change its case. (eg, "README" -> "readme"). Git ignores this accidental case changing rename during checkout and will happily write the new content to the file despite the name change. We should, too. --- tests/checkout/tree.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 3973d9320..46e8b0f90 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -1327,3 +1327,25 @@ void test_checkout_tree__safe_proceeds_if_no_index(void) git_object_free(obj); } +void test_checkout_tree__ignores_unstaged_casechange(void) +{ + git_reference *orig_ref, *br2_ref; + git_commit *orig, *br2; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup_resolved(&orig_ref, g_repo, "HEAD", 100)); + cl_git_pass(git_commit_lookup(&orig, g_repo, git_reference_target(orig_ref))); + cl_git_pass(git_reset(g_repo, (git_object *)orig, GIT_RESET_HARD, NULL)); + + cl_rename("testrepo/branch_file.txt", "testrepo/Branch_File.txt"); + + cl_git_pass(git_reference_lookup_resolved(&br2_ref, g_repo, "refs/heads/br2", 100)); + cl_git_pass(git_commit_lookup(&br2, g_repo, git_reference_target(br2_ref))); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)br2, &checkout_opts)); + + git_commit_free(orig); + git_reference_free(orig_ref); +} From 6dfd8506b780a5fd973e589c924e227993e0e79c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 1 Apr 2015 15:23:37 -0400 Subject: [PATCH 2/8] checkout test: ensure we write to casechanged dir Ensure that on a case insensitive filesystem that we can checkout into some folder 'FOLDER' that exists on disk, even if the target of the checkout is a different case (eg 'folder'). --- tests/checkout/tree.c | 57 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 46e8b0f90..7b0eae411 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -1349,3 +1349,60 @@ void test_checkout_tree__ignores_unstaged_casechange(void) git_commit_free(orig); git_reference_free(orig_ref); } + +void test_checkout_tree__conflicts_with_casechanged_subtrees(void) +{ + git_reference *orig_ref; + git_object *orig, *subtrees; + git_oid oid; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup_resolved(&orig_ref, g_repo, "HEAD", 100)); + cl_git_pass(git_object_lookup(&orig, g_repo, git_reference_target(orig_ref), GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(g_repo, (git_object *)orig, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_mkdir("testrepo/AB", 0777)); + cl_must_pass(p_mkdir("testrepo/AB/C", 0777)); + cl_git_write2file("testrepo/AB/C/3.txt", "Foobar!\n", 8, O_RDWR|O_CREAT, 0666); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&subtrees, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_fail(git_checkout_tree(g_repo, subtrees, &checkout_opts)); + + git_object_free(orig); + git_object_free(subtrees); + git_reference_free(orig_ref); +} + +void test_checkout_tree__checks_out_casechanged_subtrees(void) +{ + git_reference *orig_ref; + git_object *orig, *subtrees; + git_oid oid; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup_resolved(&orig_ref, g_repo, "HEAD", 100)); + cl_git_pass(git_object_lookup(&orig, g_repo, git_reference_target(orig_ref), GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(g_repo, (git_object *)orig, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_mkdir("testrepo/AB", 0777)); + cl_must_pass(p_mkdir("testrepo/AB/C", 0777)); + cl_git_write2file("testrepo/AB/C/unrelated.txt", "Foobar!\n", 8, O_RDWR|O_CREAT, 0666); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&subtrees, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, subtrees, &checkout_opts)); + + cl_assert(git_path_isfile("testrepo/ab/c/3.txt")); + cl_assert(git_path_isfile("testrepo/ab/c/unrelated.txt")); + + git_object_free(orig); + git_object_free(subtrees); + git_reference_free(orig_ref); +} From 3520c97057a2e4236779d24ec6fece0d11514319 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 27 Mar 2015 15:39:28 -0400 Subject: [PATCH 3/8] Revert "Always checkout with case sensitive iterator" This reverts commit 40d791545abfb3cb71553a27dc64129e1a9bec28. --- src/checkout.c | 2 +- tests/checkout/tree.c | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index 478130879..b70ea1892 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -2643,7 +2643,7 @@ int git_checkout_tree( if ((error = git_repository_index(&index, repo)) < 0) return error; - if (!(error = git_iterator_for_tree(&tree_i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL))) + if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL))) error = git_checkout_iterator(tree_i, index, opts); git_iterator_free(tree_i); diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 7b0eae411..3bc9c9364 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -646,7 +646,14 @@ void test_checkout_tree__can_cancel_checkout_from_notify(void) cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), -5555); cl_assert(!git_path_exists("testrepo/new.txt")); - cl_assert_equal_i(4, ca.count); + + /* on case-insensitive FS = a/b.txt, branch_file.txt, new.txt */ + /* on case-sensitive FS = README, then above */ + + if (git_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */ + cl_assert_equal_i(3, ca.count); + else + cl_assert_equal_i(4, ca.count); /* and again with a different stopping point and return code */ ca.filename = "README"; @@ -656,7 +663,11 @@ void test_checkout_tree__can_cancel_checkout_from_notify(void) cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), 123); cl_assert(!git_path_exists("testrepo/new.txt")); - cl_assert_equal_i(1, ca.count); + + if (git_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */ + cl_assert_equal_i(4, ca.count); + else + cl_assert_equal_i(1, ca.count); git_object_free(obj); } From 05f690122e9927eece61afb17c1595a136d119b2 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 31 Mar 2015 16:28:13 -0400 Subject: [PATCH 4/8] checkout: remove blocking dir when FORCEd --- src/checkout.c | 29 +++++++++++-- tests/checkout/icase.c | 98 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 10 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index b70ea1892..dd10732b5 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -409,6 +409,14 @@ static bool submodule_is_config_only( return rval; } +static bool checkout_is_empty_dir(checkout_data *data, const char *path) +{ + git_buf_truncate(&data->path, data->workdir_len); + if (git_buf_puts(&data->path, path) < 0) + return false; + return git_path_is_empty_dir(data->path.ptr); +} + static int checkout_action_with_wd( int *action, checkout_data *data, @@ -526,6 +534,7 @@ static int checkout_action_with_wd_dir( checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL)); GITERR_CHECK_ERROR( checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd)); + *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE); break; case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */ case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */ @@ -550,8 +559,6 @@ static int checkout_action_with_wd_dir( * dir and it will succeed if no children are left. */ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE); - if (*action != CHECKOUT_ACTION__NONE) - *action |= CHECKOUT_ACTION__DEFER_REMOVE; } else if (delta->new_file.mode != GIT_FILEMODE_TREE) /* For typechange to dir, dir is already created so no action */ @@ -564,6 +571,20 @@ static int checkout_action_with_wd_dir( return checkout_action_common(action, data, delta, wd); } +static int checkout_action_with_wd_dir_empty( + int *action, + checkout_data *data, + const git_diff_delta *delta) +{ + int error = checkout_action_no_wd(action, data, delta); + + /* We can always safely remove an empty directory. */ + if (error == 0 && *action != CHECKOUT_ACTION__NONE) + *action |= CHECKOUT_ACTION__REMOVE; + + return error; +} + static int checkout_action( int *action, checkout_data *data, @@ -653,7 +674,9 @@ static int checkout_action( } } - return checkout_action_with_wd_dir(action, data, delta, workdir, wd); + return checkout_is_empty_dir(data, wd->path) ? + checkout_action_with_wd_dir_empty(action, data, delta) : + checkout_action_with_wd_dir(action, data, delta, workdir, wd); } /* case 6 - wd is after delta */ diff --git a/tests/checkout/icase.c b/tests/checkout/icase.c index 211738070..ae1a63b90 100644 --- a/tests/checkout/icase.c +++ b/tests/checkout/icase.c @@ -21,7 +21,7 @@ void test_checkout_icase__initialize(void) cl_git_pass(git_object_lookup(&obj, repo, &id, GIT_OBJ_ANY)); git_checkout_init_options(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION); - checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE; } void test_checkout_icase__cleanup(void) @@ -79,8 +79,21 @@ static void assert_name_is(const char *expected) free(actual); } -void test_checkout_icase__overwrites_files_for_files(void) +void test_checkout_icase__refuses_to_overwrite_files_for_files(void) { + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_write2file("testrepo/BRANCH_FILE.txt", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + assert_name_is("testrepo/BRANCH_FILE.txt"); +} + +void test_checkout_icase__overwrites_files_for_files_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_write2file("testrepo/NEW.txt", "neue file\n", 10, \ O_WRONLY | O_CREAT | O_TRUNC, 0644); @@ -88,8 +101,22 @@ void test_checkout_icase__overwrites_files_for_files(void) assert_name_is("testrepo/new.txt"); } -void test_checkout_icase__overwrites_links_for_files(void) +void test_checkout_icase__refuses_to_overwrite_links_for_files(void) { + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_must_pass(p_symlink("../tmp", "testrepo/BRANCH_FILE.txt")); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + + cl_assert(!git_path_exists("tmp")); + assert_name_is("testrepo/BRANCH_FILE.txt"); +} + +void test_checkout_icase__overwrites_links_for_files_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_must_pass(p_symlink("../tmp", "testrepo/NEW.txt")); cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); @@ -98,8 +125,10 @@ void test_checkout_icase__overwrites_links_for_files(void) assert_name_is("testrepo/new.txt"); } -void test_checkout_icase__overwites_folders_for_files(void) +void test_checkout_icase__overwrites_empty_folders_for_files(void) { + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + cl_must_pass(p_mkdir("testrepo/NEW.txt", 0777)); cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); @@ -108,8 +137,50 @@ void test_checkout_icase__overwites_folders_for_files(void) cl_assert(!git_path_isdir("testrepo/new.txt")); } -void test_checkout_icase__overwrites_files_for_folders(void) +void test_checkout_icase__refuses_to_overwrite_populated_folders_for_files(void) { + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_must_pass(p_mkdir("testrepo/BRANCH_FILE.txt", 0777)); + cl_git_write2file("testrepo/BRANCH_FILE.txt/foobar", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + + assert_name_is("testrepo/BRANCH_FILE.txt"); + cl_assert(git_path_isdir("testrepo/BRANCH_FILE.txt")); +} + +void test_checkout_icase__overwrites_folders_for_files_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_must_pass(p_mkdir("testrepo/NEW.txt", 0777)); + cl_git_write2file("testrepo/NEW.txt/foobar", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); + + assert_name_is("testrepo/new.txt"); + cl_assert(!git_path_isdir("testrepo/new.txt")); +} + +void test_checkout_icase__refuses_to_overwrite_files_for_folders(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_git_write2file("testrepo/A", "neue file\n", 10, \ + O_WRONLY | O_CREAT | O_TRUNC, 0644); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + assert_name_is("testrepo/A"); + cl_assert(!git_path_isdir("testrepo/A")); +} + +void test_checkout_icase__overwrites_files_for_folders_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_write2file("testrepo/A", "neue file\n", 10, \ O_WRONLY | O_CREAT | O_TRUNC, 0644); @@ -118,8 +189,22 @@ void test_checkout_icase__overwrites_files_for_folders(void) cl_assert(git_path_isdir("testrepo/a")); } -void test_checkout_icase__overwrites_links_for_folders(void) +void test_checkout_icase__refuses_to_overwrite_links_for_folders(void) { + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING; + + cl_must_pass(p_symlink("..", "testrepo/A")); + + cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts)); + + cl_assert(!git_path_exists("b.txt")); + assert_name_is("testrepo/A"); +} + +void test_checkout_icase__overwrites_links_for_folders_when_forced(void) +{ + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_must_pass(p_symlink("..", "testrepo/A")); cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts)); @@ -127,4 +212,3 @@ void test_checkout_icase__overwrites_links_for_folders(void) cl_assert(!git_path_exists("b.txt")); assert_name_is("testrepo/a"); } - From 4beab1f8bbc29a0788ed7d5c1f6bc21741392565 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 31 Mar 2015 16:29:35 -0400 Subject: [PATCH 5/8] checkout: break case-changes into delete/add When checking out with a case-insensitive working directory, we want to change the case of items in the working directory to reflect changes that occured in the checkout target. Diff now has an option to break case-changing renames into delete/add. --- include/git2/diff.h | 7 ++++++- src/checkout.c | 3 ++- src/diff.c | 13 +++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 9fcc3bb08..cac3b268a 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -124,6 +124,11 @@ typedef enum { /** Use case insensitive filename comparisons */ GIT_DIFF_IGNORE_CASE = (1u << 10), + /** May be combined with `GIT_DIFF_IGNORE_CASE` to specify that a file + * that has changed case will be returned as an add/delete pair. + */ + GIT_DIFF_INCLUDE_CASECHANGE = (1u << 11), + /** If the pathspec is set in the diff options, this flags means to * apply it as an exact match instead of as an fnmatch pattern. */ @@ -220,7 +225,7 @@ typedef struct git_diff git_diff; typedef enum { GIT_DIFF_FLAG_BINARY = (1u << 0), /**< file(s) treated as binary data */ GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /**< file(s) treated as text data */ - GIT_DIFF_FLAG_VALID_ID = (1u << 2), /**< `id` value is known correct */ + GIT_DIFF_FLAG_VALID_ID = (1u << 2), /**< `id` value is known correct */ } git_diff_flag_t; /** diff --git a/src/checkout.c b/src/checkout.c index dd10732b5..a647ce0b9 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -2485,7 +2485,8 @@ int git_checkout_iterator( GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_INCLUDE_TYPECHANGE_TREES | - GIT_DIFF_SKIP_BINARY_CHECK; + GIT_DIFF_SKIP_BINARY_CHECK | + GIT_DIFF_INCLUDE_CASECHANGE; if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; if (data.opts.paths.count > 0) diff --git a/src/diff.c b/src/diff.c index 08e218cce..f7e1c8ee4 100644 --- a/src/diff.c +++ b/src/diff.c @@ -822,6 +822,19 @@ static int maybe_modified( status = GIT_DELTA_UNMODIFIED; } + /* If we want case changes, then break this into a delete of the old + * and an add of the new so that consumers can act accordingly (eg, + * checkout will update the case on disk.) + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && + strcmp(oitem->path, nitem->path) != 0) { + + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem); + return error; + } + return diff_delta__from_two( diff, status, oitem, omode, nitem, nmode, git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec); From 64842d8756512ff22efe41e020d7339b04328cf7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 23 Apr 2015 09:21:33 -0400 Subject: [PATCH 6/8] checkout test: only run icase on icase platform --- tests/checkout/icase.c | 61 ++++++++++++++++++++++++++++++++ tests/checkout/tree.c | 79 ------------------------------------------ 2 files changed, 61 insertions(+), 79 deletions(-) diff --git a/tests/checkout/icase.c b/tests/checkout/icase.c index ae1a63b90..82ad01e2a 100644 --- a/tests/checkout/icase.c +++ b/tests/checkout/icase.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "git2/checkout.h" +#include "refs.h" #include "path.h" #ifdef GIT_WIN32 @@ -14,9 +15,18 @@ static git_checkout_options checkout_opts; void test_checkout_icase__initialize(void) { git_oid id; + git_config *cfg; + int icase = 0; repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_config_snapshot(&cfg, repo)); + git_config_get_bool(&icase, cfg, "core.ignorecase"); + git_config_free(cfg); + + if (!icase) + cl_skip(); + cl_git_pass(git_reference_name_to_id(&id, repo, "refs/heads/dir")); cl_git_pass(git_object_lookup(&obj, repo, &id, GIT_OBJ_ANY)); @@ -212,3 +222,54 @@ void test_checkout_icase__overwrites_links_for_folders_when_forced(void) cl_assert(!git_path_exists("b.txt")); assert_name_is("testrepo/a"); } + +void test_checkout_icase__ignores_unstaged_casechange(void) +{ + git_reference *orig_ref, *br2_ref; + git_commit *orig, *br2; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); + cl_git_pass(git_commit_lookup(&orig, repo, git_reference_target(orig_ref))); + cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); + + cl_rename("testrepo/branch_file.txt", "testrepo/Branch_File.txt"); + + cl_git_pass(git_reference_lookup_resolved(&br2_ref, repo, "refs/heads/br2", 100)); + cl_git_pass(git_commit_lookup(&br2, repo, git_reference_target(br2_ref))); + + cl_git_pass(git_checkout_tree(repo, (const git_object *)br2, &checkout_opts)); + + git_commit_free(orig); + git_reference_free(orig_ref); +} + +void test_checkout_icase__conflicts_with_casechanged_subtrees(void) +{ + git_reference *orig_ref; + git_object *orig, *subtrees; + git_oid oid; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + + cl_git_pass(git_reference_lookup_resolved(&orig_ref, repo, "HEAD", 100)); + cl_git_pass(git_object_lookup(&orig, repo, git_reference_target(orig_ref), GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(repo, (git_object *)orig, GIT_RESET_HARD, NULL)); + + cl_must_pass(p_mkdir("testrepo/AB", 0777)); + cl_must_pass(p_mkdir("testrepo/AB/C", 0777)); + cl_git_write2file("testrepo/AB/C/3.txt", "Foobar!\n", 8, O_RDWR|O_CREAT, 0666); + + cl_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/subtrees")); + cl_git_pass(git_object_lookup(&subtrees, repo, &oid, GIT_OBJ_ANY)); + + cl_git_fail(git_checkout_tree(repo, subtrees, &checkout_opts)); + + git_object_free(orig); + git_object_free(subtrees); + git_reference_free(orig_ref); +} + diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 3bc9c9364..50541a703 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -1338,82 +1338,3 @@ void test_checkout_tree__safe_proceeds_if_no_index(void) git_object_free(obj); } -void test_checkout_tree__ignores_unstaged_casechange(void) -{ - git_reference *orig_ref, *br2_ref; - git_commit *orig, *br2; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup_resolved(&orig_ref, g_repo, "HEAD", 100)); - cl_git_pass(git_commit_lookup(&orig, g_repo, git_reference_target(orig_ref))); - cl_git_pass(git_reset(g_repo, (git_object *)orig, GIT_RESET_HARD, NULL)); - - cl_rename("testrepo/branch_file.txt", "testrepo/Branch_File.txt"); - - cl_git_pass(git_reference_lookup_resolved(&br2_ref, g_repo, "refs/heads/br2", 100)); - cl_git_pass(git_commit_lookup(&br2, g_repo, git_reference_target(br2_ref))); - - cl_git_pass(git_checkout_tree(g_repo, (const git_object *)br2, &checkout_opts)); - - git_commit_free(orig); - git_reference_free(orig_ref); -} - -void test_checkout_tree__conflicts_with_casechanged_subtrees(void) -{ - git_reference *orig_ref; - git_object *orig, *subtrees; - git_oid oid; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup_resolved(&orig_ref, g_repo, "HEAD", 100)); - cl_git_pass(git_object_lookup(&orig, g_repo, git_reference_target(orig_ref), GIT_OBJ_COMMIT)); - cl_git_pass(git_reset(g_repo, (git_object *)orig, GIT_RESET_HARD, NULL)); - - cl_must_pass(p_mkdir("testrepo/AB", 0777)); - cl_must_pass(p_mkdir("testrepo/AB/C", 0777)); - cl_git_write2file("testrepo/AB/C/3.txt", "Foobar!\n", 8, O_RDWR|O_CREAT, 0666); - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); - cl_git_pass(git_object_lookup(&subtrees, g_repo, &oid, GIT_OBJ_ANY)); - - cl_git_fail(git_checkout_tree(g_repo, subtrees, &checkout_opts)); - - git_object_free(orig); - git_object_free(subtrees); - git_reference_free(orig_ref); -} - -void test_checkout_tree__checks_out_casechanged_subtrees(void) -{ - git_reference *orig_ref; - git_object *orig, *subtrees; - git_oid oid; - git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - cl_git_pass(git_reference_lookup_resolved(&orig_ref, g_repo, "HEAD", 100)); - cl_git_pass(git_object_lookup(&orig, g_repo, git_reference_target(orig_ref), GIT_OBJ_COMMIT)); - cl_git_pass(git_reset(g_repo, (git_object *)orig, GIT_RESET_HARD, NULL)); - - cl_must_pass(p_mkdir("testrepo/AB", 0777)); - cl_must_pass(p_mkdir("testrepo/AB/C", 0777)); - cl_git_write2file("testrepo/AB/C/unrelated.txt", "Foobar!\n", 8, O_RDWR|O_CREAT, 0666); - - cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees")); - cl_git_pass(git_object_lookup(&subtrees, g_repo, &oid, GIT_OBJ_ANY)); - - cl_git_pass(git_checkout_tree(g_repo, subtrees, &checkout_opts)); - - cl_assert(git_path_isfile("testrepo/ab/c/3.txt")); - cl_assert(git_path_isfile("testrepo/ab/c/unrelated.txt")); - - git_object_free(orig); - git_object_free(subtrees); - git_reference_free(orig_ref); -} From f286e2715d98e695462ca059de7a0206384ccbf1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 31 Mar 2015 16:06:33 -0400 Subject: [PATCH 7/8] status test: always test the new file path --- tests/status/renames.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/status/renames.c b/tests/status/renames.c index 24b8aca2b..f482d693a 100644 --- a/tests/status/renames.c +++ b/tests/status/renames.c @@ -73,16 +73,20 @@ static void check_status( cl_assert_equal_i_fmt(expected->status, actual->status, "%04x"); - if (oldname) - cl_assert(git__strcmp(oldname, expected->oldname) == 0); - else - cl_assert(expected->oldname == NULL); + if (expected->oldname) { + cl_assert(oldname != NULL); + cl_assert_equal_s(oldname, expected->oldname); + } else { + cl_assert(oldname == NULL); + } if (actual->status & (GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED)) { - if (newname) - cl_assert(git__strcmp(newname, expected->newname) == 0); - else - cl_assert(expected->newname == NULL); + if (expected->newname) { + cl_assert(newname != NULL); + cl_assert_equal_s(newname, expected->newname); + } else { + cl_assert(newname == NULL); + } } } } From cd79d99a4cd81b37f681c6f4eb6756cd8d42618c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 23 Apr 2015 15:58:53 -0400 Subject: [PATCH 8/8] checkout test: better case-insensitive test on Mac On Mac OS, `realpath` is deficient in determining the actual filename on-disk as it will simply provide the string you gave it if that file exists, instead of returning the filename as it exists. Instead we must read the directory entries for the parent directory to get the canonical filename. --- tests/checkout/icase.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/checkout/icase.c b/tests/checkout/icase.c index 82ad01e2a..510f5424d 100644 --- a/tests/checkout/icase.c +++ b/tests/checkout/icase.c @@ -6,6 +6,8 @@ #ifdef GIT_WIN32 # include +#else +# include #endif static git_repository *repo; @@ -40,7 +42,7 @@ void test_checkout_icase__cleanup(void) cl_git_sandbox_cleanup(); } -static char *test_realpath(const char *in) +static char *get_filename(const char *in) { #ifdef GIT_WIN32 HANDLE fh; @@ -65,7 +67,31 @@ static char *test_realpath(const char *in) return filename; #else - return realpath(in, NULL); + char *search_dirname, *search_filename, *filename = NULL; + git_buf out = GIT_BUF_INIT; + DIR *dir; + struct dirent *de; + + cl_assert(search_dirname = git_path_dirname(in)); + cl_assert(search_filename = git_path_basename(in)); + + cl_assert(dir = opendir(search_dirname)); + + while ((de = readdir(dir))) { + if (strcasecmp(de->d_name, search_filename) == 0) { + git_buf_join(&out, '/', search_dirname, de->d_name); + filename = git_buf_detach(&out); + break; + } + } + + closedir(dir); + + git__free(search_dirname); + git__free(search_filename); + git_buf_free(&out); + + return filename; #endif } @@ -74,7 +100,7 @@ static void assert_name_is(const char *expected) char *actual; size_t actual_len, expected_len, start; - cl_assert(actual = test_realpath(expected)); + cl_assert(actual = get_filename(expected)); expected_len = strlen(expected); actual_len = strlen(actual);