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....@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

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to