Merge pull request #4535 from libgit2/ethomson/checkout_typechange_with_index_and_wd checkout: when examining index (instead of workdir), also examine mode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
diff --git a/src/checkout.c b/src/checkout.c
index 528fbdf..2ad736a 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -205,17 +205,23 @@ static bool checkout_is_workdir_modified(
return rval;
}
- /* Look at the cache to decide if the workdir is modified. If not,
- * we can simply compare the oid in the cache to the baseitem instead
- * of hashing the file. If so, we allow the checkout to proceed if the
- * oid is identical (ie, the staged item is what we're trying to check
- * out.)
+ /*
+ * Look at the cache to decide if the workdir is modified: if the
+ * cache contents match the workdir contents, then we do not need
+ * to examine the working directory directly, instead we can
+ * examine the cache to see if _it_ has been modified. This allows
+ * us to avoid touching the disk.
*/
- if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) {
- if (git_index_time_eq(&wditem->mtime, &ie->mtime) &&
- wditem->file_size == ie->file_size &&
- !is_file_mode_changed(wditem->mode, ie->mode))
- return !is_workdir_base_or_new(&ie->id, baseitem, newitem);
+ ie = git_index_get_bypath(data->index, wditem->path, 0);
+
+ if (ie != NULL &&
+ git_index_time_eq(&wditem->mtime, &ie->mtime) &&
+ wditem->file_size == ie->file_size &&
+ !is_file_mode_changed(wditem->mode, ie->mode)) {
+
+ /* The workdir is modified iff the index entry is modified */
+ return !is_workdir_base_or_new(&ie->id, baseitem, newitem) ||
+ is_file_mode_changed(baseitem->mode, ie->mode);
}
/* depending on where base is coming from, we may or may not know
diff --git a/tests/checkout/head.c b/tests/checkout/head.c
index ded86df..46b2257 100644
--- a/tests/checkout/head.c
+++ b/tests/checkout/head.c
@@ -136,3 +136,48 @@ void test_checkout_head__do_remove_tracked_subdir(void)
cl_assert(!git_path_isfile("testrepo/subdir/tracked-file"));
cl_assert(git_path_isfile("testrepo/subdir/untracked-file"));
}
+
+void test_checkout_head__typechange_workdir(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_object *target;
+ struct stat st;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_revparse_single(&target, g_repo, "HEAD"));
+ cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL));
+
+ cl_must_pass(p_chmod("testrepo/new.txt", 0755));
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+
+ cl_git_pass(p_stat("testrepo/new.txt", &st));
+ cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode));
+
+ git_object_free(target);
+}
+
+void test_checkout_head__typechange_index_and_workdir(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_object *target;
+ git_index *index;
+ struct stat st;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_revparse_single(&target, g_repo, "HEAD"));
+ cl_git_pass(git_reset(g_repo, target, GIT_RESET_HARD, NULL));
+
+ cl_must_pass(p_chmod("testrepo/new.txt", 0755));
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_add_bypath(index, "new.txt"));
+ cl_git_pass(git_index_write(index));
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+
+ cl_git_pass(p_stat("testrepo/new.txt", &st));
+ cl_assert(!GIT_PERMS_IS_EXEC(st.st_mode));
+
+ git_object_free(target);
+ git_index_free(index);
+}