Commit d3bc95fd664095a0c8dfcdf99f62741b1ecd6ffc

Russell Belfer 2014-03-25T12:37:05

Update behavior for untracked sub-repos When a directory containing a .git directory (or even just a plain gitlink) was found, libgit2 was going out of its way to treat it specially. This seemed like it was necessary because the diff code was not originally emulating Git's behavior for untracked directories correctly (i.e. scanning for ignored vs untracked items inside). Now that libgit2 diff mimics Git's untracked directory behavior, the special handling for contained Git repos is actually incorrect and this commit rips it out.

diff --git a/src/iterator.c b/src/iterator.c
index 401b5de..e9ec652 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -1313,8 +1313,8 @@ static int workdir_iterator__update_entry(fs_iterator *fi)
 	if (error < 0)
 		giterr_clear();
 
-	/* mark submodule (or any dir with .git) as GITLINK and remove slash */
-	if (!error || error == GIT_EEXISTS) {
+	/* mark submodule as GITLINK and remove slash */
+	if (!error) {
 		fi->entry.mode = S_IFGITLINK;
 		fi->entry.path[strlen(fi->entry.path) - 1] = '\0';
 	}
diff --git a/src/path.c b/src/path.c
index fa800b7..1dccf90 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1051,15 +1051,8 @@ int git_path_dirload_with_stat(
 		}
 
 		if (S_ISDIR(ps->st.st_mode)) {
-			if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0)
-				break;
-
-			if (p_access(full.ptr, F_OK) == 0) {
-				ps->st.st_mode = GIT_FILEMODE_COMMIT;
-			} else {
-				ps->path[ps->path_len++] = '/';
-				ps->path[ps->path_len] = '\0';
-			}
+			ps->path[ps->path_len++] = '/';
+			ps->path[ps->path_len] = '\0';
 		}
 	}
 
diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c
index 92e6f72..891d8a6 100644
--- a/tests/diff/iterator.c
+++ b/tests/diff/iterator.c
@@ -737,13 +737,13 @@ void test_diff_iterator__workdir_builtin_ignores(void)
 		{ "root_test2", false },
 		{ "root_test3", false },
 		{ "root_test4.txt", false },
-		{ "sub", false },
+		{ "sub/", false },
 		{ "sub/.gitattributes", false },
 		{ "sub/abc", false },
 		{ "sub/dir/", true },
 		{ "sub/file", false },
 		{ "sub/ign/", true },
-		{ "sub/sub", false },
+		{ "sub/sub/", false },
 		{ "sub/sub/.gitattributes", false },
 		{ "sub/sub/dir", false }, /* file is not actually a dir */
 		{ "sub/sub/file", false },
diff --git a/tests/diff/submodules.c b/tests/diff/submodules.c
index da96ba9..314cf1f 100644
--- a/tests/diff/submodules.c
+++ b/tests/diff/submodules.c
@@ -1,6 +1,7 @@
 #include "clar_libgit2.h"
 #include "repository.h"
 #include "posix.h"
+#include "diff_helpers.h"
 #include "../submodule/submodule_helpers.h"
 
 static git_repository *g_repo = NULL;
@@ -11,6 +12,7 @@ void test_diff_submodules__initialize(void)
 
 void test_diff_submodules__cleanup(void)
 {
+	cl_git_sandbox_cleanup();
 }
 
 #define get_buf_ptr(buf) ((buf)->asize ? (buf)->ptr : NULL)
@@ -34,6 +36,10 @@ static void check_diff_patches_at_line(
 
 		if (expected[d] && !strcmp(expected[d], "<SKIP>"))
 			continue;
+		if (expected[d] && !strcmp(expected[d], "<UNTRACKED>")) {
+			cl_assert_at_line(delta->status == GIT_DELTA_UNTRACKED, file, line);
+			continue;
+		}
 		if (expected[d] && !strcmp(expected[d], "<END>")) {
 			cl_git_pass(git_patch_to_buf(&buf, patch));
 			cl_assert_at_line(!strcmp(expected[d], "<END>"), file, line);
@@ -115,7 +121,9 @@ void test_diff_submodules__dirty_submodule_2(void)
 	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
 	git_diff *diff = NULL, *diff2 = NULL;
 	char *smpath = "testrepo";
-	static const char *expected_none[] = { "<END>" };
+	static const char *expected_none[] = {
+		"<END>"
+	};
 	static const char *expected_dirty[] = {
 		"diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */
 		"<END>"
@@ -170,6 +178,8 @@ void test_diff_submodules__submod2_index_to_wd(void)
 	git_diff *diff = NULL;
 	static const char *expected[] = {
 		"<SKIP>", /* .gitmodules */
+		"<UNTRACKED>", /* not-submodule */
+		"<UNTRACKED>", /* not */
 		"diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */
 		"diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
 		"diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */
@@ -349,6 +359,8 @@ void test_diff_submodules__diff_ignore_options(void)
 	git_config *cfg;
 	static const char *expected_normal[] = {
 		"<SKIP>", /* .gitmodules */
+		"<UNTRACKED>", /* not-submodule */
+		"<UNTRACKED>", /* not */
 		"diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */
 		"diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
 		"diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */
@@ -358,10 +370,14 @@ void test_diff_submodules__diff_ignore_options(void)
 	};
 	static const char *expected_ignore_all[] = {
 		"<SKIP>", /* .gitmodules */
+		"<UNTRACKED>", /* not-submodule */
+		"<UNTRACKED>", /* not */
 		"<END>"
 	};
 	static const char *expected_ignore_dirty[] = {
 		"<SKIP>", /* .gitmodules */
+		"<UNTRACKED>", /* not-submodule */
+		"<UNTRACKED>", /* not */
 		"diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
 		"diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */
 		"<END>"
@@ -423,3 +439,49 @@ void test_diff_submodules__diff_ignore_options(void)
 
 	git_config_free(cfg);
 }
+
+void test_diff_submodules__skips_empty_includes_used(void)
+{
+	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+	git_diff *diff = NULL;
+	diff_expects exp;
+	git_repository *r2;
+
+	/* A side effect of of Git's handling of untracked directories and
+	 * auto-ignoring of ".git" entries is that a newly initialized Git
+	 * repo inside another repo will be skipped by diff, but one that
+	 * actually has a commit it in will show as an untracked directory.
+	 * Let's make sure that works.
+	 */
+
+	g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+	opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+	memset(&exp, 0, sizeof(exp));
+	cl_git_pass(git_diff_foreach(
+		diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+	cl_assert_equal_i(0, exp.files);
+	git_diff_free(diff);
+
+	cl_git_pass(git_repository_init(&r2, "empty_standard_repo/subrepo", 0));
+
+	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+	memset(&exp, 0, sizeof(exp));
+	cl_git_pass(git_diff_foreach(
+		diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+	cl_assert_equal_i(1, exp.files);
+	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+	git_diff_free(diff);
+
+	cl_git_mkfile("empty_standard_repo/subrepo/README.txt", "hello\n");
+
+	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+	memset(&exp, 0, sizeof(exp));
+	cl_git_pass(git_diff_foreach(
+		diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+	cl_assert_equal_i(1, exp.files);
+	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+	git_diff_free(diff);
+}
diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c
index 449dc63..6128e82 100644
--- a/tests/diff/workdir.c
+++ b/tests/diff/workdir.c
@@ -881,7 +881,7 @@ void test_diff_workdir__submodules(void)
 	 * only significant difference is that those Added items will show up
 	 * as Untracked items in the pure libgit2 diff.
 	 *
-	 * Then add in the two extra ignored items "not" and "not-submodule"
+	 * Then add in the two extra untracked items "not" and "not-submodule"
 	 * to get the 12 files reported here.
 	 */
 
@@ -890,8 +890,8 @@ void test_diff_workdir__submodules(void)
 	cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
 	cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
 	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
-	cl_assert_equal_i(2, exp.file_status[GIT_DELTA_IGNORED]);
-	cl_assert_equal_i(8, exp.file_status[GIT_DELTA_UNTRACKED]);
+	cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+	cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]);
 
 	/* the following numbers match "git diff 873585" exactly */
 
diff --git a/tests/submodule/status.c b/tests/submodule/status.c
index f5111c8..3245923 100644
--- a/tests/submodule/status.c
+++ b/tests/submodule/status.c
@@ -324,7 +324,7 @@ static int confirm_submodule_status(
 {
 	submodule_expectations *exp = payload;
 
-	while (git__suffixcmp(exp->paths[exp->counter], "/") == 0)
+	while (exp->statuses[exp->counter] < 0)
 		exp->counter++;
 
 	cl_assert_equal_i(exp->statuses[exp->counter], (int)status_flags);
@@ -345,8 +345,10 @@ void test_submodule_status__iterator(void)
 		"just_a_dir/",
 		"just_a_dir/contents",
 		"just_a_file",
-		"not",
-		"not-submodule",
+		"not-submodule/",
+		"not-submodule/README.txt",
+		"not/",
+		"not/README.txt",
 		"README.txt",
 		"sm_added_and_uncommited",
 		"sm_changed_file",
@@ -359,11 +361,13 @@ void test_submodule_status__iterator(void)
 	};
 	static int expected_flags[] = {
 		GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, /* ".gitmodules" */
-		0,					    /* "just_a_dir/" will be skipped */
+		-1,					    /* "just_a_dir/" will be skipped */
 		GIT_STATUS_CURRENT,     /* "just_a_dir/contents" */
 		GIT_STATUS_CURRENT,	    /* "just_a_file" */
-		GIT_STATUS_IGNORED,	    /* "not" (contains .git) */
-		GIT_STATUS_IGNORED,     /* "not-submodule" (contains .git) */
+		GIT_STATUS_WT_NEW,      /* "not-submodule/" untracked item */
+		-1,                     /* "not-submodule/README.txt" */
+		GIT_STATUS_WT_NEW,      /* "not/" untracked item */
+		-1,                     /* "not/README.txt" */
 		GIT_STATUS_CURRENT,     /* "README.txt */
 		GIT_STATUS_INDEX_NEW,   /* "sm_added_and_uncommited" */
 		GIT_STATUS_WT_MODIFIED, /* "sm_changed_file" */