Commit d62e44cb8218840a0291fb5fbb7c5106e1e35a12

Segev Finer 2019-06-03T18:35:08

checkout: Fix removing untracked files by path in subdirectories The checkout code didn't iterate into a subdir if it didn't match the pathspec, but since the pathspec might match files in the subdir we should recurse into it (In contrast to gitignore handling). Fixes #5089

diff --git a/src/checkout.c b/src/checkout.c
index f0dd736..59ff873 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -371,8 +371,13 @@ static int checkout_action_wd_only(
 	if (!git_pathspec__match(
 			pathspec, wd->path,
 			(data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
-			git_iterator_ignore_case(workdir), NULL, NULL))
-		return git_iterator_advance(wditem, workdir);
+			git_iterator_ignore_case(workdir), NULL, NULL)) {
+
+		if (wd->mode == GIT_FILEMODE_TREE)
+			return git_iterator_advance_into(wditem, workdir);
+		else
+			return git_iterator_advance(wditem, workdir);
+	}
 
 	/* check if item is tracked in the index but not in the checkout diff */
 	if (data->index != NULL) {
diff --git a/tests/checkout/head.c b/tests/checkout/head.c
index 7991230..5b3a034 100644
--- a/tests/checkout/head.c
+++ b/tests/checkout/head.c
@@ -109,6 +109,32 @@ void test_checkout_head__do_not_remove_untracked_file_in_subdir(void)
 	cl_assert(git_path_isfile("testrepo/tracked/subdir/untracked"));
 }
 
+void test_checkout_head__do_remove_untracked_paths(void)
+{
+	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+	git_index *index;
+	char *paths[] = {"tracked/untracked"};
+
+	cl_git_pass(p_mkdir("testrepo/tracked", 0755));
+	cl_git_pass(p_mkdir("testrepo/tracked/subdir", 0755));
+	cl_git_mkfile("testrepo/tracked/tracked", "tracked\n");
+	cl_git_mkfile("testrepo/tracked/untracked", "untracked\n");
+
+	cl_git_pass(git_repository_index(&index, g_repo));
+	cl_git_pass(git_index_add_bypath(index, "tracked/tracked"));
+	cl_git_pass(git_index_write(index));
+
+	git_index_free(index);
+
+	opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+	opts.paths.strings = paths;
+	opts.paths.count = 1;
+	cl_git_pass(git_checkout_head(g_repo, &opts));
+
+	cl_assert(git_path_isfile("testrepo/tracked/tracked"));
+	cl_assert(!git_path_isfile("testrepo/tracked/untracked"));
+}
+
 void test_checkout_head__do_remove_tracked_subdir(void)
 {
 	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;