Commit 7a5ee3dc923caf2b3b9b5e9b2408340f6ae32d7d

Russell Belfer 2013-05-24T11:09:04

Add ~ expansion to global attributes and excludes This adds ~/ prefix expansion for the value of core.attributesfile and core.excludesfile, plus it fixes the fact that the attributes cache was holding on to the string data from the config for a long time (instead of making its own strdup) which could have caused a problem if the config was refreshed. Adds a test for the new expansion capability.

diff --git a/src/attr.c b/src/attr.c
index 9fe4471..6cdff29 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -596,26 +596,33 @@ static int collect_attr_files(
 }
 
 static int attr_cache__lookup_path(
-	const char **out, git_config *cfg, const char *key, const char *fallback)
+	char **out, git_config *cfg, const char *key, const char *fallback)
 {
 	git_buf buf = GIT_BUF_INIT;
 	int error;
+	const char *cfgval = NULL;
 
-	if (!(error = git_config_get_string(out, cfg, key)))
-		return 0;
+	*out = NULL;
+
+	if (!(error = git_config_get_string(&cfgval, cfg, key))) {
+
+		/* expand leading ~/ as needed */
+		if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
+			!git_futils_find_global_file(&buf, &cfgval[2]))
+			*out = git_buf_detach(&buf);
+		else if (cfgval)
+			*out = git__strdup(cfgval);
 
-	if (error == GIT_ENOTFOUND) {
+	} else if (error == GIT_ENOTFOUND) {
 		giterr_clear();
 		error = 0;
 
 		if (!git_futils_find_xdg_file(&buf, fallback))
 			*out = git_buf_detach(&buf);
-		else
-			*out = NULL;
-
-		git_buf_free(&buf);
 	}
 
+	git_buf_free(&buf);
+
 	return error;
 }
 
@@ -696,6 +703,12 @@ void git_attr_cache_flush(
 
 	git_pool_clear(&cache->pool);
 
+	git__free(cache->cfg_attr_file);
+	cache->cfg_attr_file = NULL;
+
+	git__free(cache->cfg_excl_file);
+	cache->cfg_excl_file = NULL;
+
 	cache->initialized = 0;
 }
 
diff --git a/src/attrcache.h b/src/attrcache.h
index 12cec4b..077633b 100644
--- a/src/attrcache.h
+++ b/src/attrcache.h
@@ -13,10 +13,10 @@
 typedef struct {
 	int initialized;
 	git_pool pool;
-	git_strmap *files;	/* hash path to git_attr_file of rules */
-	git_strmap *macros;	/* hash name to vector<git_attr_assignment> */
-	const char *cfg_attr_file; /* cached value of core.attributesfile */
-	const char *cfg_excl_file; /* cached value of core.excludesfile */
+	git_strmap *files;	 /* hash path to git_attr_file of rules */
+	git_strmap *macros;	 /* hash name to vector<git_attr_assignment> */
+	char *cfg_attr_file; /* cached value of core.attributesfile */
+	char *cfg_excl_file; /* cached value of core.excludesfile */
 } git_attr_cache;
 
 extern int git_attr_cache__init(git_repository *repo);
diff --git a/tests-clar/attr/ignore.c b/tests-clar/attr/ignore.c
index aa81e92..8df0eb9 100644
--- a/tests-clar/attr/ignore.c
+++ b/tests-clar/attr/ignore.c
@@ -1,6 +1,7 @@
 #include "clar_libgit2.h"
 #include "posix.h"
 #include "path.h"
+#include "fileops.h"
 
 static git_repository *g_repo = NULL;
 
@@ -20,7 +21,7 @@ void assert_is_ignored(bool expected, const char *filepath)
 	int is_ignored;
 
 	cl_git_pass(git_ignore_path_is_ignored(&is_ignored, g_repo, filepath));
-	cl_assert_equal_i(expected, is_ignored == 1);
+	cl_assert_equal_b(expected, is_ignored);
 }
 
 void test_attr_ignore__honor_temporary_rules(void)
@@ -46,3 +47,35 @@ void test_attr_ignore__skip_gitignore_directory(void)
 	assert_is_ignored(true, "NewFolder/NewFolder");
 	assert_is_ignored(true, "NewFolder/NewFolder/File.txt");
 }
+
+void test_attr_ignore__expand_tilde_to_homedir(void)
+{
+	git_buf path = 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_git_mkfile("home/globalexcludes", "# 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"));
+	git_config_free(cfg);
+
+	git_attr_cache_flush(g_repo); /* must reset to pick up change */
+
+	assert_is_ignored(true, "example.global_with_tilde");
+
+	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));
+
+	git_buf_free(&path);
+}