Commit 37ab5ecc107118d5fa541cb42cc203649e549fd8

Vicent Marti 2014-04-16T10:51:25

Merge pull request #2269 from libgit2/rb/fix-leading-slash-ignores Fix core.excludesfile named .gitignore

diff --git a/src/attr_file.c b/src/attr_file.c
index 4eb7324..ea92336 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -61,8 +61,7 @@ int git_attr_file__parse_buffer(
 	git_repository *repo, void *parsedata, const char *buffer, git_attr_file *attrs)
 {
 	int error = 0;
-	const char *scan = NULL;
-	char *context = NULL;
+	const char *scan = NULL, *context = NULL;
 	git_attr_rule *rule = NULL;
 
 	GIT_UNUSED(parsedata);
@@ -72,10 +71,10 @@ int git_attr_file__parse_buffer(
 	scan = buffer;
 
 	/* if subdir file path, convert context for file paths */
-	if (attrs->key && git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0) {
+	if (attrs->key &&
+		git_path_root(attrs->key + 2) < 0 &&
+		git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0)
 		context = attrs->key + 2;
-		context[strlen(context) - strlen(GIT_ATTR_FILE)] = '\0';
-	}
 
 	while (!error && *scan) {
 		/* allocate rule if needed */
@@ -115,10 +114,6 @@ int git_attr_file__parse_buffer(
 
 	git_attr_rule__free(rule);
 
-	/* restore file path used for context */
-	if (context)
-		context[strlen(context)] = '.'; /* first char of GIT_ATTR_FILE */
-
 	return error;
 }
 
@@ -414,7 +409,10 @@ int git_attr_fnmatch__parse(
 	if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
 		source != NULL && git_path_root(pattern) < 0)
 	{
-		size_t sourcelen = strlen(source);
+        /* use context path minus the trailing filename */
+        char *slash = strrchr(source, '/');
+        size_t sourcelen = slash ? slash - source + 1 : 0;
+
 		/* given an unrooted fullpath match from a file inside a repo,
 		 * prefix the pattern with the relative directory of the source file
 		 */
diff --git a/src/ignore.c b/src/ignore.c
index aef3e39..5cf4fca 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -14,8 +14,7 @@ static int parse_ignore_file(
 {
 	int error = 0;
 	git_attr_fnmatch *match = NULL;
-	const char *scan = NULL;
-	char *context = NULL;
+	const char *scan = NULL, *context = NULL;
 	int ignore_case = false;
 
 	/* Prefer to have the caller pass in a git_ignores as the parsedata
@@ -25,10 +24,10 @@ static int parse_ignore_file(
 	else if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
 		return error;
 
-	if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) {
+	if (ignores->key &&
+		git_path_root(ignores->key + 2) < 0 &&
+		git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0)
 		context = ignores->key + 2;
-		context[strlen(context) - strlen(GIT_IGNORE_FILE)] = '\0';
-	}
 
 	scan = buffer;
 
@@ -64,9 +63,6 @@ static int parse_ignore_file(
 	}
 
 	git__free(match);
-	/* restore file path used for context */
-	if (context)
-		context[strlen(context)] = '.'; /* first char of GIT_IGNORE_FILE */
 
 	return error;
 }
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
index 4ed9238..a83c5bd 100644
--- a/tests/attr/ignore.c
+++ b/tests/attr/ignore.c
@@ -130,22 +130,18 @@ void test_attr_ignore__skip_gitignore_directory(void)
 
 void test_attr_ignore__expand_tilde_to_homedir(void)
 {
-	git_buf path = GIT_BUF_INIT;
+	git_buf cleanup = GIT_BUF_INIT;
 	git_config *cfg;
 
 	assert_is_ignored(false, "example.global_with_tilde");
 
-	/* construct fake home with fake global excludes */
-
-	cl_must_pass(p_mkdir("home", 0777));
-	cl_git_pass(git_path_prettify(&path, "home", NULL));
-	cl_git_pass(git_libgit2_opts(
-		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+	cl_fake_home(&cleanup);
 
-	cl_git_mkfile("home/globalexcludes", "# found me\n*.global_with_tilde\n");
+	/* construct fake home with fake global excludes */
+	cl_git_mkfile("home/globalexclude", "# found me\n*.global_with_tilde\n");
 
 	cl_git_pass(git_repository_config(&cfg, g_repo));
-	cl_git_pass(git_config_set_string(cfg, "core.excludesfile", "~/globalexcludes"));
+	cl_git_pass(git_config_set_string(cfg, "core.excludesfile", "~/globalexclude"));
 	git_config_free(cfg);
 
 	git_attr_cache_flush(g_repo); /* must reset to pick up change */
@@ -154,8 +150,9 @@ void test_attr_ignore__expand_tilde_to_homedir(void)
 
 	cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES));
 
-	cl_git_pass(git_libgit2_opts(
-		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, NULL));
+	cl_fake_home_cleanup(&cleanup);
 
-	git_buf_free(&path);
+	git_attr_cache_flush(g_repo); /* must reset to pick up change */
+
+	assert_is_ignored(false, "example.global_with_tilde");
 }
diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c
index 9b062ef..90e53c5 100644
--- a/tests/clar_libgit2.c
+++ b/tests/clar_libgit2.c
@@ -490,3 +490,24 @@ void clar__assert_equal_file(
 	clar__assert_equal(file, line, "mismatched file length", 1, "%"PRIuZ,
 		(size_t)expected_bytes, (size_t)total_bytes);
 }
+
+void cl_fake_home(git_buf *restore)
+{
+	git_buf path = GIT_BUF_INIT;
+
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, restore));
+
+	cl_must_pass(p_mkdir("home", 0777));
+	cl_git_pass(git_path_prettify(&path, "home", NULL));
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+	git_buf_free(&path);
+}
+
+void cl_fake_home_cleanup(git_buf *restore)
+{
+	cl_git_pass(git_libgit2_opts(
+		GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, restore->ptr));
+	git_buf_free(restore);
+}
diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h
index 3de80bf..c2489db 100644
--- a/tests/clar_libgit2.h
+++ b/tests/clar_libgit2.h
@@ -120,4 +120,7 @@ int cl_repo_get_bool(git_repository *repo, const char *cfg);
 
 void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value);
 
+void cl_fake_home(git_buf *restore);
+void cl_fake_home_cleanup(git_buf *restore);
+
 #endif
diff --git a/tests/status/ignore.c b/tests/status/ignore.c
index 1fefcba..d6c26a8 100644
--- a/tests/status/ignore.c
+++ b/tests/status/ignore.c
@@ -364,6 +364,62 @@ void test_status_ignore__subdirectories_not_at_root(void)
 	cl_assert_equal_i(0, counts.wrong_sorted_path);
 }
 
+void test_status_ignore__leading_slash_ignores(void)
+{
+	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+	status_entry_counts counts;
+	git_buf home = GIT_BUF_INIT;
+	static const char *paths_2[] = {
+		"dir/.gitignore",
+		"dir/a/ignore_me",
+		"dir/b/ignore_me",
+		"dir/ignore_me",
+		"ignore_also/file",
+		"ignore_me",
+		"test/.gitignore",
+		"test/ignore_me/and_me/file",
+		"test/ignore_me/file",
+		"test/ignore_me/file2",
+	};
+	static const unsigned int statuses_2[] = {
+		GIT_STATUS_WT_NEW,  GIT_STATUS_WT_NEW,  GIT_STATUS_WT_NEW,
+		GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+		GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW,
+	};
+
+	make_test_data();
+
+	cl_fake_home(&home);
+	cl_git_mkfile("home/.gitignore", "/ignore_me\n");
+	{
+		git_config *cfg;
+		cl_git_pass(git_repository_config(&cfg, g_repo));
+		cl_git_pass(git_config_set_string(
+			cfg, "core.excludesfile", "~/.gitignore"));
+		git_config_free(cfg);
+	}
+
+	cl_git_rewritefile("empty_standard_repo/.git/info/exclude", "/ignore_also\n");
+	cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "/ignore_me\n");
+	cl_git_rewritefile("empty_standard_repo/test/.gitignore", "/and_me\n");
+
+	memset(&counts, 0x0, sizeof(status_entry_counts));
+	counts.expected_entry_count = 10;
+	counts.expected_paths = paths_2;
+	counts.expected_statuses = statuses_2;
+
+	opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+	cl_git_pass(git_status_foreach_ext(
+		g_repo, &opts, cb_status__normal, &counts));
+
+	cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+	cl_assert_equal_i(0, counts.wrong_status_flags_count);
+	cl_assert_equal_i(0, counts.wrong_sorted_path);
+
+	cl_fake_home_cleanup(&home);
+}
+
 void test_status_ignore__adding_internal_ignores(void)
 {
 	int ignored;