Subject: [PATCH v2 04/21] Make git_path() aware of file relocation in $GIT_DIR

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Sat, 14 Dec 2013 17:54:50 +0700

We allow the user to relocate certain paths out of $GIT_DIR via
environment variables, e.g. GIT_OBJECT_DIRECTORY, GIT_INDEX_FILE and
GIT_GRAFT_FILE. All callers are not supposed to use git_path() or
git_pathdup() to get those paths. Instead they must use
get_object_directory(), get_index_file() and get_graft_file()
respectively. This is inconvenient and could be missed in review
(there's git_path("objects/info/alternates") somewhere in
sha1_file.c).

This patch makes git_path() and git_pathdup() understand those
environment variables. So if you set GIT_OBJECT_DIRECTORY to /foo/bar,
git_path("objects/abc") should return /tmp/bar/abc. The same is done
for the two remaining env variables.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 cache.h               |  1 +
 environment.c         |  9 ++++++--
 path.c                | 57 ++++++++++++++++++++++++++++++++++++++++++++++++---
 t/t0060-path-utils.sh | 54 ++++++++++++++++++++++++++++++++++++++++++++++++
 test-path-utils.c     |  7 +++++++
 5 files changed, 123 insertions(+), 5 deletions(-)

diff --git a/cache.h b/cache.h
index ce377e1..cdafbd7 100644
--- a/cache.h
+++ b/cache.h
@@ -584,6 +584,7 @@ extern int fsync_object_files;
 extern int core_preload_index;
 extern int core_apply_sparse_checkout;
 extern int precomposed_unicode;
+extern int git_db_env, git_index_env, git_graft_env;
 
 /*
  * The character that begins a commented line in user-editable file
diff --git a/environment.c b/environment.c
index 0a15349..1d74dde 100644
--- a/environment.c
+++ b/environment.c
@@ -81,6 +81,7 @@ static size_t namespace_len;
 
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_graft_file;
+int git_db_env, git_index_env, git_graft_env;
 
 /*
  * Repository-local GIT_* environment variables; see cache.h for details.
@@ -134,15 +135,19 @@ static void setup_git_env(void)
 	if (!git_object_dir) {
 		git_object_dir = xmalloc(strlen(git_dir) + 9);
 		sprintf(git_object_dir, "%s/objects", git_dir);
-	}
+	} else
+		git_db_env = 1;
 	git_index_file = getenv(INDEX_ENVIRONMENT);
 	if (!git_index_file) {
 		git_index_file = xmalloc(strlen(git_dir) + 7);
 		sprintf(git_index_file, "%s/index", git_dir);
-	}
+	} else
+		git_index_env = 1;
 	git_graft_file = getenv(GRAFT_ENVIRONMENT);
 	if (!git_graft_file)
 		git_graft_file = git_pathdup("info/grafts");
+	else
+		git_graft_env = 1;
 	if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
 		read_replace_refs = 0;
 	namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
diff --git a/path.c b/path.c
index 08aa213..93e3ecc 100644
--- a/path.c
+++ b/path.c
@@ -50,10 +50,60 @@ char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 	return cleanup_path(buf);
 }
 
+static void copy_path(char *dst, size_t n, const char *src)
+{
+	if (strlen(src) < n)
+		strcpy(dst, src);
+	else
+		strlcpy(dst, bad_path, n);
+}
+
+static int dir_prefix(const char *buf, const char *dir)
+{
+	int len = strlen(dir);
+	return !strncmp(buf, dir, len) &&
+		(is_dir_sep(buf[len]) || buf[len] == '\0');
+}
+
+/* $buf =~ m|$dir/+$file| but without regex */
+static int is_dir_file(const char *buf, const char *dir, const char *file)
+{
+	int len = strlen(dir);
+	if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
+		return 0;
+	while (is_dir_sep(buf[len]))
+		len++;
+	return !strcmp(buf + len, file);
+}
+
+static void replace_dir(char *buf, size_t n, int len, const char *newdir)
+{
+	int newlen = strlen(newdir);
+	int buflen = strlen(buf);
+	if (buflen - len + newlen >= n) {
+		strlcpy(buf, bad_path, n);
+		return;
+	}
+	memmove(buf + newlen + 1, buf + len, buflen - len + 1);
+	memcpy(buf, newdir, newlen);
+	buf[newlen] = '/';
+}
+
+static void adjust_git_path(char *buf, size_t n, int git_dir_len)
+{
+	char *base = buf + git_dir_len;
+	if (git_graft_env && is_dir_file(base, "info", "grafts"))
+		copy_path(buf, n, get_graft_file());
+	else if (git_index_env && !strcmp(base, "index"))
+		copy_path(buf, n, get_index_file());
+	else if (git_db_env && dir_prefix(base, "objects"))
+		replace_dir(buf, n, git_dir_len + 7, get_object_directory());
+}
+
 static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)
 {
 	const char *git_dir = get_git_dir();
-	size_t len;
+	size_t len, total_len;
 
 	len = strlen(git_dir);
 	if (n < len + 1)
@@ -61,9 +111,10 @@ static char *git_vsnpath(char *buf, size_t n, const char *fmt, va_list args)
 	memcpy(buf, git_dir, len);
 	if (len && !is_dir_sep(git_dir[len-1]))
 		buf[len++] = '/';
-	len += vsnprintf(buf + len, n - len, fmt, args);
-	if (len >= n)
+	total_len = len + vsnprintf(buf + len, n - len, fmt, args);
+	if (total_len >= n)
 		goto bad;
+	adjust_git_path(buf, n, len);
 	return cleanup_path(buf);
 bad:
 	strlcpy(buf, bad_path, n);
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 07c10c8..bce16f5 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -223,4 +223,58 @@ relative_path "<null>"		"<empty>"	./
 relative_path "<null>"		"<null>"	./
 relative_path "<null>"		/foo/a/b	./
 
+test_expect_success 'git_path info/grafts without GIT_GRAFT_FILE' '
+	test-path-utils git_path info/grafts >actual1 &&
+	echo .git/info/grafts >expect1 &&
+	test_cmp expect1 actual1
+'
+
+test_expect_success 'git_path info/grafts with GIT_GRAFT_FILE' '
+	GIT_GRAFT_FILE=foo test-path-utils git_path info/grafts >actual2 &&
+	echo foo >expect2 &&
+	test_cmp expect2 actual2
+'
+
+test_expect_success 'git_path info/////grafts with GIT_GRAFT_FILE' '
+	GIT_GRAFT_FILE=foo test-path-utils git_path info/////grafts >actual2 &&
+	echo foo >expect2 &&
+	test_cmp expect2 actual2
+'
+
+test_expect_success 'git_path index' '
+	GIT_INDEX_FILE=foo test-path-utils git_path index >actual &&
+	echo foo >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git_path index/foo' '
+	GIT_INDEX_FILE=foo test-path-utils git_path index/foo >actual &&
+	echo .git/index/foo >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git_path index2' '
+	GIT_INDEX_FILE=foo test-path-utils git_path index2 >actual &&
+	echo .git/index2 >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git_path objects' '
+	GIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects >actual &&
+	echo foo/ >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git_path objects/foo' '
+	GIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects/foo >actual &&
+	echo foo//foo >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git_path objects2' '
+	GIT_OBJECT_DIRECTORY=foo test-path-utils git_path objects2 >actual &&
+	echo .git/objects2 >expect &&
+	test_cmp expect actual
+'
+
 test_done
diff --git a/test-path-utils.c b/test-path-utils.c
index 3dd3744..55e4fe0 100644
--- a/test-path-utils.c
+++ b/test-path-utils.c
@@ -135,6 +135,13 @@ int main(int argc, char **argv)
 		return 0;
 	}
 
+	if (argc == 3 && !strcmp(argv[1], "git_path")) {
+		int nongit_ok = 0;
+		setup_git_directory_gently(&nongit_ok);
+		puts(git_path("%s", argv[2]));
+		return 0;
+	}
+
 	fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
 		argv[1] ? argv[1] : "(there was none)");
 	return 1;
-- 
1.8.5.1.77.g42c48fa