Subject: [PATCH v3] Support ent:relative_path

From: Dana How <[email-masked]>
Date: Fri, 04 May 2007 17:47:08 -0700

Most commands accept relative paths,  but this is
not true of arguments in ent:path format.  This
patch makes all of the following git-show commands
work in the git source tree (not just the first):
 % cd xdiff
 % git-show v1.5.2-rc0:xdiff/xemit.h
 % git-show v1.5.2-rc0:./xemit.h
 % git-show v1.5.2-rc0:../sha1_name.c

It also adds ent:?string as a synonym for ent:/string .
This makes the following changes possible later:
ent:/path is an absolute path and ent:path is relative.

Signed-off-by: Dana L. How <[email-masked]>
---
 cache.h     |    1 +
 setup.c     |    5 +++-
 sha1_name.c |   78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/cache.h b/cache.h
index 8e76152..53507d9 100644
--- a/cache.h
+++ b/cache.h
@@ -215,6 +215,7 @@ extern char *get_graft_file(void);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
+extern const char *prefix_to_cwd;
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
diff --git a/setup.c b/setup.c
index a45ea83..46ae6e3 100644
--- a/setup.c
+++ b/setup.c
@@ -1,5 +1,7 @@
 #include "cache.h"
 
+const char *prefix_to_cwd;
+
 const char *prefix_path(const char *prefix, int len, const char *path)
 {
 	const char *orig = path;
@@ -252,7 +254,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
 	cwd[len++] = '/';
 	cwd[len] = 0;
 	inside_git_dir = !prefixcmp(cwd + offset, ".git/");
-	return cwd + offset;
+	prefix_to_cwd = cwd + offset;
+	return prefix_to_cwd;
 }
 
 int git_config_perm(const char *var, const char *value)
diff --git a/sha1_name.c b/sha1_name.c
index 55f25a2..dd415c1 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -593,6 +593,67 @@ static int handle_one_ref(const char *path,
 }
 
 /*
+ * Future: nonzero to assume relative paths
+ */
+#define	ENT_COLON_ASSUME_RELATIVE	0
+
+/*
+ * Modify path into a full absolute path with no leading "/"
+ * and no "." or "..".  If we can't,
+ * leave input unaltered for client's error message.
+ */
+static void prepend_prefix(const char **cp, int *namelen)
+{
+	char *t, *t2;
+	const char *cp2 = *cp;
+	int type, namelen2 = *namelen;
+	static char fullpath[PATH_MAX];
+	int fixup = strstr(cp2, "/./") || strstr(cp2, "/../");
+	for (type = 4; --type > 0; )
+		if (namelen2 > type && !memcmp(cp2, "../" + 3 - type, type))
+			break;
+
+	/* handle simple cases else create absolute path */
+	fullpath[0] = 0;
+	if ((type == 0 && !ENT_COLON_ASSUME_RELATIVE) || type == 1) {
+		if (!fixup) goto done;
+	} else
+	if (prefix_to_cwd) {
+		namelen2 += strlen(prefix_to_cwd);
+		if (namelen2 >= PATH_MAX)
+			die("path too long");
+		strcpy(fullpath, prefix_to_cwd);
+		type = 0;
+	} else
+	if (type == 2) {
+		if (!fixup) goto done;
+	} else
+	if (type == 3)
+		return;	/* client will complain */
+	cp2 = strcat(fullpath, cp2);
+
+	while ((t = strstr(cp2, "/./")) != NULL)
+		memmove(t, t + 2, (namelen2 -= 2) - (t - cp2) + 1);
+
+	while ((t = strstr(cp2, "/../")) != NULL) {
+		if (t == cp2 || (t == cp2 + 1 && t[-1] == '.'))
+			return;	/* client will complain */
+		for (t2 = t; --t2 >= cp2 && *t2 != '/'; )
+			/* nothing */;
+		if (t2 < cp2) {
+			namelen2 -= t + 4 - cp2;
+			cp2 = t + 4;
+		} else {
+			memmove(t2, t + 3, namelen2 - (t + 3 - cp2) + 1);
+			namelen2 -= t + 3 - t2;
+		}
+	}
+
+done:	*cp = cp2 + type;
+	*namelen = namelen2 - type;
+}
+
+/*
  * This interprets names like ':/Initial revision of "git"' by searching
  * through history and returning the first commit whose message starts
  * with the given string.
@@ -647,6 +708,11 @@ int get_sha1(const char *name, unsigned char *sha1)
 	return get_sha1_with_mode(name, sha1, &unused);
 }
 
+/*
+ * Future: change to '?'
+ */
+#define	ENT_COLON_OLD_SEARCH	'/'
+
 int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
 {
 	int ret, bracket_depth;
@@ -666,7 +732,8 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
 		int stage = 0;
 		struct cache_entry *ce;
 		int pos;
-		if (namelen > 2 && name[1] == '/')
+		if (namelen > 2 &&
+		    (name[1] == ENT_COLON_OLD_SEARCH || name[1] == '?'))
 			return get_sha1_oneline(name + 2, sha1);
 		if (namelen < 3 ||
 		    name[2] != ':' ||
@@ -681,6 +748,7 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
 			read_cache();
 		if (active_nr < 0)
 			return -1;
+		prepend_prefix(&cp, &namelen);
 		pos = cache_name_pos(cp, namelen);
 		if (pos < 0)
 			pos = -pos - 1;
@@ -708,9 +776,11 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
 	}
 	if (*cp == ':') {
 		unsigned char tree_sha1[20];
-		if (!get_sha1_1(name, cp-name, tree_sha1))
-			return get_tree_entry(tree_sha1, cp+1, sha1,
-					      mode);
+		if (!get_sha1_1(name, cp - name, tree_sha1)) {
+			namelen -= ++cp - name;
+			prepend_prefix(&cp, &namelen);
+			return get_tree_entry(tree_sha1, cp, sha1, mode);
+		}
 	}
 	return ret;
 }
-- 
1.5.2.rc0.787.g0014