Subject: [PATCH v3 3/4] setup: Add 'abspath_part_inside_repo' function

From: Martin Erik Werner <[email-masked]>
Date: Fri, 31 Jan 2014 21:22:48 +0100

In order to extract the part of an absolute path which lies inside the
repo, it is not possible to directly use real_path, since that would
dereference symlinks both outside and inside the work tree.

Add an 'abspath_part_inside_repo' function which incrementally checks
each path level by temporarily NUL-terminating at each '/' and comparing
against the work tree path. When a match is found, it copies the
remainder (which will be the in-repo part) to a destination
buffer.

The path being the filesystem root or exactly equal to the work tree are
special cases handled separately, since then there is no directory
separator between the work tree and in-repo part.

Signed-off-by: Martin Erik Werner <[email-masked]>
---
 cache.h |  1 +
 setup.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+)

diff --git a/cache.h b/cache.h
index ce377e1..242f27d 100644
--- a/cache.h
+++ b/cache.h
@@ -426,6 +426,7 @@ extern void verify_filename(const char *prefix,
 			    int diagnose_misspelt_rev);
 extern void verify_non_filename(const char *prefix, const char *name);
 extern int path_inside_repo(const char *prefix, const char *path);
+extern int abspath_part_inside_repo(char *dst, const char *path);
 
 #define INIT_DB_QUIET 0x0001
 
diff --git a/setup.c b/setup.c
index 5432a31..e606846 100644
--- a/setup.c
+++ b/setup.c
@@ -77,6 +77,69 @@ int path_inside_repo(const char *prefix, const char *path)
 	return 0;
 }
 
+/*
+ * It is ok if dst == src, but they should not overlap otherwise.
+ * No checking if the path is in fact an absolute path is done, and it must
+ * already be normalized.
+ *
+ * Find the part of an absolute path that lies inside the work tree by
+ * dereferencing symlinks outside the work tree, for example:
+ * /dir1/repo/dir2/file   (work tree is /dir1/repo)      -> dir2/file
+ * /dir/file              (work tree is /)               -> dir/file
+ * /dir/symlink1/symlink2 (symlink1 points to work tree) -> symlink2
+ * /dir/repo              (exactly equal to work tree)   -> (empty string)
+ */
+int abspath_part_inside_repo(char *dst, const char* src)
+{
+	size_t len;
+	char *dst0;
+	char temp;
+
+	const char* work_tree = get_git_work_tree();
+	if (!work_tree)
+		return -1;
+	len = strlen(src);
+	dst0 = dst;
+
+	// check root level
+	if (has_dos_drive_prefix(src)) {
+		*dst++ = *src++;
+		*dst++ = *src++;
+		*dst++ = *src++;
+	} else {
+		*dst++ = *src++;
+	}
+	temp = *dst;
+	*dst = '\0';
+	if (strcmp(real_path(dst0), work_tree) == 0) {
+		*dst = temp;
+		memmove(dst0, src, len - (dst - dst0) + 1);
+		return 0;
+	}
+	*dst = temp;
+
+	// check each level
+	while (*dst != '\0') {
+		*dst++ = *src++;
+		if (*dst == '/') {
+			*dst = '\0';
+			if (strcmp(real_path(dst0), work_tree) == 0) {
+				memmove(dst0, src + 1, len - (dst - dst0));
+				return 0;
+			}
+			*dst = '/';
+		}
+	}
+
+	// check whole path
+	if (strcmp(real_path(dst0), work_tree) == 0) {
+		*dst0 = '\0';
+		return 0;
+	}
+
+	return -1;
+}
+
 int check_filename(const char *prefix, const char *arg)
 {
 	const char *name;
-- 
1.8.5.2