Commit 2a1d97e6e7ca0fee3fcc5e86e8bf61ab2f182d64

Edward Thomson 2020-05-11T00:09:18

Merge pull request #5378 from libgit2/ethomson/checkout_pathspecs Honor GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH for all checkout types

diff --git a/src/checkout.c b/src/checkout.c
index 5cfa728..f0dd736 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -2544,6 +2544,17 @@ cleanup:
 #define CHECKOUT_INDEX_DONT_WRITE_MASK \
 	(GIT_CHECKOUT_DONT_UPDATE_INDEX | GIT_CHECKOUT_DONT_WRITE_INDEX)
 
+GIT_INLINE(void) setup_pathspecs(
+	git_iterator_options *iter_opts,
+	const git_checkout_options *checkout_opts)
+{
+	if (checkout_opts &&
+		(checkout_opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) {
+		iter_opts->pathlist.count = checkout_opts->paths.count;
+		iter_opts->pathlist.strings = checkout_opts->paths.strings;
+	}
+}
+
 int git_checkout_iterator(
 	git_iterator *target,
 	git_index *index,
@@ -2586,6 +2597,8 @@ int git_checkout_iterator(
 	workdir_opts.start = data.pfx;
 	workdir_opts.end = data.pfx;
 
+	setup_pathspecs(&workdir_opts, opts);
+
 	if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 ||
 		(error = git_iterator_for_workdir_ext(
 			&workdir, data.repo, data.opts.target_directory, index, NULL,
@@ -2596,10 +2609,8 @@ int git_checkout_iterator(
 		GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
 	baseline_opts.start = data.pfx;
 	baseline_opts.end = data.pfx;
-	if (opts && (opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) {
-		baseline_opts.pathlist.count = opts->paths.count;
-		baseline_opts.pathlist.strings = opts->paths.strings;
-	}
+
+	setup_pathspecs(&baseline_opts, opts);
 
 	if (data.opts.baseline_index) {
 		if ((error = git_iterator_for_index(
@@ -2689,6 +2700,7 @@ int git_checkout_index(
 	git_index *index,
 	const git_checkout_options *opts)
 {
+	git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
 	int error, owned = 0;
 	git_iterator *index_i;
 
@@ -2716,7 +2728,9 @@ int git_checkout_index(
 		return error;
 	GIT_REFCOUNT_INC(index);
 
-	if (!(error = git_iterator_for_index(&index_i, repo, index, NULL)))
+	setup_pathspecs(&iter_opts, opts);
+
+	if (!(error = git_iterator_for_index(&index_i, repo, index, &iter_opts)))
 		error = git_checkout_iterator(index_i, index, opts);
 
 	if (owned)
@@ -2773,10 +2787,7 @@ int git_checkout_tree(
 	if ((error = git_repository_index(&index, repo)) < 0)
 		return error;
 
-	if (opts && (opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) {
-		iter_opts.pathlist.count = opts->paths.count;
-		iter_opts.pathlist.strings = opts->paths.strings;
-	}
+	setup_pathspecs(&iter_opts, opts);
 
 	if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts)))
 		error = git_checkout_iterator(tree_i, index, opts);
diff --git a/tests/checkout/index.c b/tests/checkout/index.c
index a76c471..8134673 100644
--- a/tests/checkout/index.c
+++ b/tests/checkout/index.c
@@ -89,6 +89,65 @@ void test_checkout_index__can_remove_untracked_files(void)
 	cl_assert_equal_i(false, git_path_isdir("./testrepo/dir"));
 }
 
+void test_checkout_index__can_disable_pathspec_match(void)
+{
+	static git_index *index;
+	git_oid commit_id;
+	git_checkout_options g_opts = GIT_CHECKOUT_OPTIONS_INIT;
+	git_object *g_object;
+
+	char *files_to_checkout[] = { "test10.txt", "test11.txt"};
+	size_t files_to_checkout_size = 2;
+
+	/* 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_git_pass(git_repository_index(&index, g_repo));
+
+	/* We create 4 files and commit them */
+	cl_git_mkfile("testrepo/test9.txt", "original\n");
+	cl_git_mkfile("testrepo/test10.txt", "original\n");
+	cl_git_mkfile("testrepo/test11.txt", "original\n");
+	cl_git_mkfile("testrepo/test12.txt", "original\n");
+
+	cl_git_pass(git_index_add_bypath(index, "test9.txt"));
+	cl_git_pass(git_index_add_bypath(index, "test10.txt"));
+	cl_git_pass(git_index_add_bypath(index, "test11.txt"));
+	cl_git_pass(git_index_add_bypath(index, "test12.txt"));
+	cl_git_pass(git_index_write(index));
+
+	cl_repo_commit_from_index(&commit_id, g_repo, NULL, 0, "commit our test files");
+
+	/* We modify the content of all 4 of our files */
+	cl_git_rewritefile("testrepo/test9.txt", "modified\n");
+	cl_git_rewritefile("testrepo/test10.txt", "modified\n");
+	cl_git_rewritefile("testrepo/test11.txt", "modified\n");
+	cl_git_rewritefile("testrepo/test12.txt", "modified\n");
+
+	/* We checkout only test10.txt and test11.txt */
+	g_opts.checkout_strategy =
+		GIT_CHECKOUT_FORCE |
+		GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
+	g_opts.paths.strings = files_to_checkout;
+	g_opts.paths.count = files_to_checkout_size;
+	cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
+
+	/* The only files that have been reverted to their original content
+	   should be test10.txt and test11.txt */
+	check_file_contents("testrepo/test9.txt", "modified\n");
+	check_file_contents("testrepo/test10.txt", "original\n");
+	check_file_contents("testrepo/test11.txt", "original\n");
+	check_file_contents("testrepo/test12.txt", "modified\n");
+}
+
 void test_checkout_index__honor_the_specified_pathspecs(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;