git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[RFC 1/2] path.c: refactor relative_path(), not only strip prefix

From
Jiang Xin <worldhello.net@gmail.com>
Date
May 15, 2013, 15:18 UTC
Message-ID
<7f77be827e21940dfe415d34f7239799d999fef4.1368630636.git.worldhello.net@gmail.com>
In-Reply-To
<cover.1368630636.git.worldhello.net@gmail.com>

Original design of relative_path() is simple, just strip the prefix (*base) from the abosolute path (*abs). In most cases, we need a real relative path, such as: ../foo, ../../bar. That's why there is another reimplementation (path_relative()) in quote.c.

Refactor relative_path() in path.c to return real relative path, so that user can reuse this function without reimplement his/her own. I will use this method for interactive git-clean later. Some of the implementations are borrowed from path_relative() in quote.c.

Different results for relative_path() before and after this refactor:
    base path  abs path  relative (orignal)  relative (refactor)
    =========  ========  ==================  ===================
    /a/b       /a/b/c/   c/                  c/
    //a///b/   /a/b//c/  c/                  c/
    /a/b       /a/b/     .                   ./
    /a/b/      /a        /a                  ../
    /a/b/      /         /                   ../../
    /a/b/      /a/c      /a/c                ../c
Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
---
 path.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 74 insertions(+), 20 deletions(-)
diff --git a/path.c b/path.c
index 04ff..4dafa8 100644
--- a/path.c
+++ b/path.c
@@ -444,38 +444,92 @@ int adjust_shared_perm(const char *path)
 const char *relative_path(const char *abs, const char *base)
 {
 	static char buf[PATH_MAX + 1];
-	int i = 0, j = 0;
+	int abs_off, base_off, i, j;
+	int abs_len, base_len;
 
 	if (!base || !base[0])
 		return abs;
-	while (base[i]) {
+
+	abs_len = strlen(abs);
+	base_len = strlen(base);
+
+	abs_off = 0;
+	base_off = 0;
+	i = 0;
+	j = 0;
+	while (i < base_len && j < abs_len && base[i] == abs[j]) {
 		if (is_dir_sep(base[i])) {
-			if (!is_dir_sep(abs[j]))
-				return abs;
 			while (is_dir_sep(base[i]))
 				i++;
 			while (is_dir_sep(abs[j]))
 				j++;
-			continue;
-		} else if (abs[j] != base[i]) {
+			base_off = i;
+			abs_off = j;
+		} else {
+			i++;
+			j++;
+		}
+	}
+	if (
+	    /* base seems like prefix of abs */
+	    i >= base_len &&
+	    /*
+	     * but "/foo" is not a prefix of "/foobar"
+	     * (i.e. base not end with '/')
+	     */
+	    base_off < base_len) {
+		if (j >= abs_len) {
+			/* abs="/a/b", base="/a/b" */
+			abs_off = abs_len;
+		} else if (is_dir_sep(abs[j])) {
+			/* abs="/a/b/c", base="/a/b" */
+			while (is_dir_sep(abs[j]))
+				j++;
+			abs_off = j;
+		} else {
+			/* abs="/a/bbb/c", base="/a/b" */
+			i = base_off;
+		}
+	} else if (
+		   /* abs is short than base (prefix of base) */
+		   j >= abs_len &&
+		   /* abs not end with '/' */
+		   abs_off < abs_len) {
+		if (is_dir_sep(base[i])) {
+			/* abs="/a/b", base="/a/b/c/" */
+			while (is_dir_sep(base[i]))
+				i++;
+			abs_off = abs_len;
+		}
+	}
+	abs += abs_off;
+	abs_len -= abs_off;
+
+	/* base is prefix of abs */
+	if (i >= base_len) {
+		if (*abs == '\0') {
+			strcpy(buf, "./");
+			return buf;
+		} else {
 			return abs;
 		}
+	}
+
+	buf[0] = '\0';
+	while (i < base_len) {
+		if (is_dir_sep(base[i])) {
+			strcat(buf, "../");
+			while (is_dir_sep(base[i]))
+				i++;
+			continue;
+		}
 		i++;
-		j++;
 	}
-	if (
-	    /* "/foo" is a prefix of "/foo" */
-	    abs[j] &&
-	    /* "/foo" is not a prefix of "/foobar" */
-	    !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])
-	   )
-		return abs;
-	while (is_dir_sep(abs[j]))
-		j++;
-	if (!abs[j])
-		strcpy(buf, ".");
-	else
-		strcpy(buf, abs + j);
+	if (!is_dir_sep(base[base_len - 1]))
+		strcat(buf, "../");
+
+	strcat(buf, abs);
+
 	return buf;
 }
 
-- 
1.8.3.rc1.404.ga32c147
Previous: Jiang XinNext: Jiang Xin
Message 8 of 19 in “interactive git-clean”
  1. 0/9 interactive git-cleanJiang Xin, May 14, 2013
  2. 1/9 git-clean: refactor git-clean into two phasesJiang Xin, May 14, 2013
  3. Junio C HamanoMay 14, 2013
  4. Jiang XinMay 15, 2013
  5. Junio C HamanoMay 15, 2013
  6. Jiang XinMay 15, 2013
  7. 0/2 refactor relative_path in path.cJiang Xin, May 15, 2013
  8. 1/2 path.c: refactor relative_path(), not only strip prefixJiang Xin, May 15, 2013
  9. 2/2 quote.c: remove path_relative, use relative_path insteadJiang Xin, May 15, 2013
  10. Junio C HamanoMay 15, 2013
  11. 2/9 git-clean: add support for -i/--interactiveJiang Xin, May 14, 2013
  12. 3/9 git-clean: show items of del_list in columnsJiang Xin, May 14, 2013
  13. 4/9 git-clean: add colors to interactive git-cleanJiang Xin, May 14, 2013
  14. 5/9 git-clean: use a git-add-interactive compatible UIJiang Xin, May 14, 2013
  15. 6/9 git-clean: add filter by pattern interactive actionJiang Xin, May 14, 2013
  16. 7/9 git-clean: add select by numbers interactive actionJiang Xin, May 14, 2013
  17. 8/9 git-clean: add ask each interactive actionJiang Xin, May 14, 2013
  18. 9/9 git-clean: add documentation for interactive git-cleanJiang Xin, May 14, 2013
  19. Junio C HamanoMay 14, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.