Commit 894ccf4b162f87745ce890485f198374e9404152

Patrick Steinhardt 2018-02-20T16:14:54

Merge pull request #4535 from libgit2/ethomson/checkout_typechange_with_index_and_wd checkout: when examining index (instead of workdir), also examine mode

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);
+}