Commit d8d25acb9a2e560a5fce66d4b1c9bdf4969ea2c4

Carlos Martín Nieto 2013-09-05T19:24:20

config: add support for include directives Relative, absolute and home-relative paths are supported. The recursion limit it set at 10, just like in git.

diff --git a/src/config_file.c b/src/config_file.c
index fa8aba0..891bc29 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -34,6 +34,8 @@ typedef struct git_config_file_iter {
 	cvar_t* next_var;
 } git_config_file_iter;
 
+/* Max depth for [include] directives */
+#define MAX_INCLUDE_DEPTH 10
 
 #define CVAR_LIST_HEAD(list) ((list)->head)
 
@@ -74,6 +76,8 @@ typedef struct git_config_file_iter {
 		 (iter) = (tmp))
 
 struct reader {
+	time_t file_mtime;
+	size_t file_size;
 	char *file_path;
 	git_buf buffer;
 	char *read_ptr;
@@ -89,8 +93,6 @@ typedef struct {
 	struct reader reader;
 
 	char  *file_path;
-	time_t file_mtime;
-	size_t file_size;
 
 	git_config_level_t level;
 } diskfile_backend;
@@ -169,7 +171,7 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
 
 	git_buf_init(&b->reader.buffer, 0);
 	res = git_futils_readbuffer_updated(
-		&b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL);
+		&b->reader.buffer, b->file_path, &b->reader.file_mtime, &b->reader.file_size, NULL);
 
 	/* It's fine if the file doesn't exist */
 	if (res == GIT_ENOTFOUND)
@@ -191,7 +193,7 @@ static int config_refresh(git_config_backend *cfg)
 	git_strmap *old_values;
 
 	res = git_futils_readbuffer_updated(
-		&b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated);
+		&b->reader.buffer, b->file_path, &b->reader.file_mtime, &b->reader.file_size, &updated);
 	if (res < 0 || !updated)
 		return (res == GIT_ENOTFOUND) ? 0 : res;
 
@@ -899,6 +901,15 @@ static int strip_comments(char *line, int in_quotes)
 	return quote_count;
 }
 
+static int included_path(git_buf *out, const char *dir, const char *path)
+{
+	/* From the user's home */
+	if (path[0] == '~' && path[1] == '/')
+		return git_futils_find_global_file(out, &path[1]);
+
+	return git_path_join_unrooted(out, path, dir, NULL);
+}
+
 static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
 {
 	int c;
@@ -910,6 +921,10 @@ static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_c
 	int result = 0;
 	khiter_t pos;
 
+	/* FIXME: should we return an error? */
+	if (depth >= MAX_INCLUDE_DEPTH)
+		return 0;
+
 	/* Initialize the reading position */
 	reader->read_ptr = reader->buffer.ptr;
 	reader->eof = 0;
@@ -979,6 +994,38 @@ static int config_parse(diskfile_backend *cfg_file, struct reader *reader, git_c
 				existing->next = var;
 			}
 
+			if (!git__strcmp(var->entry->name, "include.path")) {
+				struct reader r;
+				git_buf path = GIT_BUF_INIT;
+				char *dir;
+
+				memset(&r, 0, sizeof(r));
+				if ((result = git_path_dirname_r(&path, reader->file_path)) < 0)
+					break;
+
+				dir = git_buf_detach(&path);
+				result = included_path(&path, dir, var->entry->value);
+				git__free(dir);
+
+				if (result < 0)
+					break;
+
+				r.file_path = git_buf_detach(&path);
+				git_buf_init(&r.buffer, 0);
+				if ((result = git_futils_readbuffer_updated(&r.buffer, r.file_path, &r.file_mtime,
+									    &r.file_size, NULL)) < 0) {
+					git__free(r.file_path);
+					break;
+				}
+
+				result = config_parse(cfg_file, &r, level, depth+1);
+				git__free(r.file_path);
+				git_buf_free(&r.buffer);
+
+				if (result < 0)
+					break;
+			}
+
 			break;
 		}
 	}
@@ -1199,7 +1246,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
 	git__free(current_section);
 
 	/* refresh stats - if this errors, then commit will error too */
-	(void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file);
+	(void)git_filebuf_stats(&cfg->reader.file_mtime, &cfg->reader.file_size, &file);
 
 	result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
 	git_buf_free(&cfg->reader.buffer);
diff --git a/tests-clar/config/include.c b/tests-clar/config/include.c
new file mode 100644
index 0000000..b88a7b2
--- /dev/null
+++ b/tests-clar/config/include.c
@@ -0,0 +1,50 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "fileops.h"
+
+void test_config_include__relative(void)
+{
+	git_config *cfg;
+	const char *str;
+
+	cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include")));
+
+	cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+	cl_assert_equal_s(str, "huzzah");
+
+	git_config_free(cfg);
+}
+
+void test_config_include__absolute(void)
+{
+	git_config *cfg;
+	const char *str;
+	git_buf buf = GIT_BUF_INIT;
+
+	cl_git_pass(git_buf_printf(&buf, "[include]\npath = %s/config-included", cl_fixture("config")));
+
+	cl_git_mkfile("config-include-absolute", git_buf_cstr(&buf));
+	git_buf_free(&buf);
+	cl_git_pass(git_config_open_ondisk(&cfg, "config-include-absolute"));
+
+	cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+	cl_assert_equal_s(str, "huzzah");
+
+	git_config_free(cfg);
+}
+
+void test_config_include__homedir(void)
+{
+	git_config *cfg;
+	const char *str;
+
+	cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config")));
+	cl_git_mkfile("config-include-homedir",  "[include]\npath = ~/config-included");
+
+	cl_git_pass(git_config_open_ondisk(&cfg, "config-include-homedir"));
+
+	cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+	cl_assert_equal_s(str, "huzzah");
+
+	git_config_free(cfg);
+}
diff --git a/tests-clar/resources/config/config-include b/tests-clar/resources/config/config-include
new file mode 100644
index 0000000..6b5e79d
--- /dev/null
+++ b/tests-clar/resources/config/config-include
@@ -0,0 +1,2 @@
+[include]
+	path = config-included
diff --git a/tests-clar/resources/config/config-included b/tests-clar/resources/config/config-included
new file mode 100644
index 0000000..089ca08
--- /dev/null
+++ b/tests-clar/resources/config/config-included
@@ -0,0 +1,2 @@
+[foo "bar"]
+     baz = huzzah