path.c: refactor relative_path(), not only strip prefix
authorJiang Xin <worldhello.net@gmail.com>
Tue, 25 Jun 2013 15:53:43 +0000 (23:53 +0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 26 Jun 2013 16:59:00 +0000 (09:59 -0700)
Original design of relative_path() is simple, just strip the prefix
(*base) from the absolute 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.

Borrow some codes from path_relative() in quote.c to refactor
relative_path() in path.c, so that it could return real relative
path, and user can reuse this function without reimplementing
his/her own. The function path_relative() in quote.c will be
substituted, and I would use the new relative_path() function when
implementing the interactive git-clean later.

Different results for relative_path() before and after this refactor:

abs path base path relative (original) relative (refactor)
======== ========= =================== ===================
/a/b /a/b . ./
/a/b/ /a/b . ./
/a /a/b/ /a ../
/ /a/b/ / ../../
/a/c /a/b/ /a/c ../c
/x/y /a/b/ /x/y ../../x/y

a/b/ a/b/ . ./
a/b/ a/b . ./
a a/b a ../
x/y a/b/ x/y ../../x/y
a/c a/b a/c ../c

(empty) (null) (empty) ./
(empty) (empty) (empty) ./
(empty) /a/b (empty) ./
(null) (null) (null) ./
(null) (empty) (null) ./
(null) /a/b (segfault) ./

You may notice that return value "." has been changed to "./".
It is because:

* Function quote_path_relative() in quote.c will show the relative
path as "./" if abs(in) and base(prefix) are the same.

* Function relative_path() is called only once (in setup.c), and
it will be OK for the return value as "./" instead of ".".

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
cache.h
path.c
setup.c
t/t0060-path-utils.sh
test-path-utils.c
diff --git a/cache.h b/cache.h
index 94ca1acf704bd2dfdc561b6b2d3d64740b975f61..8e42256942bdb3b1170938094963970a8e4fad66 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -737,7 +737,7 @@ int is_directory(const char *);
 const char *real_path(const char *path);
 const char *real_path_if_valid(const char *path);
 const char *absolute_path(const char *path);
-const char *relative_path(const char *abs, const char *base);
+const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
 int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, struct string_list *prefixes);
 char *strip_path_suffix(const char *path, const char *suffix);
diff --git a/path.c b/path.c
index 04ff1487ed31685b6b3f7923f9bfbfc7217f70a0..7f3324aeea8050c082f9bb52d769301cdede4805 100644 (file)
--- a/path.c
+++ b/path.c
@@ -441,42 +441,100 @@ int adjust_shared_perm(const char *path)
        return 0;
 }
 
-const char *relative_path(const char *abs, const char *base)
+/*
+ * Give path as relative to prefix.
+ *
+ * The strbuf may or may not be used, so do not assume it contains the
+ * returned path.
+ */
+const char *relative_path(const char *in, const char *prefix,
+                         struct strbuf *sb)
 {
-       static char buf[PATH_MAX + 1];
+       int in_len = in ? strlen(in) : 0;
+       int prefix_len = prefix ? strlen(prefix) : 0;
+       int in_off = 0;
+       int prefix_off = 0;
        int i = 0, j = 0;
 
-       if (!base || !base[0])
-               return abs;
-       while (base[i]) {
-               if (is_dir_sep(base[i])) {
-                       if (!is_dir_sep(abs[j]))
-                               return abs;
-                       while (is_dir_sep(base[i]))
+       if (!in_len)
+               return "./";
+       else if (!prefix_len)
+               return in;
+
+       while (i < prefix_len && j < in_len && prefix[i] == in[j]) {
+               if (is_dir_sep(prefix[i])) {
+                       while (is_dir_sep(prefix[i]))
                                i++;
-                       while (is_dir_sep(abs[j]))
+                       while (is_dir_sep(in[j]))
                                j++;
+                       prefix_off = i;
+                       in_off = j;
+               } else {
+                       i++;
+                       j++;
+               }
+       }
+
+       if (
+           /* "prefix" seems like prefix of "in" */
+           i >= prefix_len &&
+           /*
+            * but "/foo" is not a prefix of "/foobar"
+            * (i.e. prefix not end with '/')
+            */
+           prefix_off < prefix_len) {
+               if (j >= in_len) {
+                       /* in="/a/b", prefix="/a/b" */
+                       in_off = in_len;
+               } else if (is_dir_sep(in[j])) {
+                       /* in="/a/b/c", prefix="/a/b" */
+                       while (is_dir_sep(in[j]))
+                               j++;
+                       in_off = j;
+               } else {
+                       /* in="/a/bbb/c", prefix="/a/b" */
+                       i = prefix_off;
+               }
+       } else if (
+                  /* "in" is short than "prefix" */
+                  j >= in_len &&
+                  /* "in" not end with '/' */
+                  in_off < in_len) {
+               if (is_dir_sep(prefix[i])) {
+                       /* in="/a/b", prefix="/a/b/c/" */
+                       while (is_dir_sep(prefix[i]))
+                               i++;
+                       in_off = in_len;
+               }
+       }
+       in += in_off;
+       in_len -= in_off;
+
+       if (i >= prefix_len) {
+               if (!in_len)
+                       return "./";
+               else
+                       return in;
+       }
+
+       strbuf_reset(sb);
+       strbuf_grow(sb, in_len);
+
+       while (i < prefix_len) {
+               if (is_dir_sep(prefix[i])) {
+                       strbuf_addstr(sb, "../");
+                       while (is_dir_sep(prefix[i]))
+                               i++;
                        continue;
-               } else if (abs[j] != base[i]) {
-                       return abs;
                }
                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);
-       return buf;
+       if (!is_dir_sep(prefix[prefix_len - 1]))
+               strbuf_addstr(sb, "../");
+
+       strbuf_addstr(sb, in);
+
+       return sb->buf;
 }
 
 /*
diff --git a/setup.c b/setup.c
index 94c1e61bda747ed5c664bbbf8431234dc8276d29..0d9ea6239f2512760b4104440362d2e3758b3f09 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -360,6 +360,7 @@ int is_inside_work_tree(void)
 
 void setup_work_tree(void)
 {
+       struct strbuf sb = STRBUF_INIT;
        const char *work_tree, *git_dir;
        static int initialized = 0;
 
@@ -379,8 +380,10 @@ void setup_work_tree(void)
        if (getenv(GIT_WORK_TREE_ENVIRONMENT))
                setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
 
-       set_git_dir(relative_path(git_dir, work_tree));
+       set_git_dir(relative_path(git_dir, work_tree, &sb));
        initialized = 1;
+
+       strbuf_release(&sb);
 }
 
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
index 72e89ce7193a91a807d21f3cd29104f49083892c..76c779252ca21219e75d1cc9db2f9babd8f18e07 100755 (executable)
@@ -191,33 +191,30 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
 relative_path /a/b/c/  /a/b/           c/
 relative_path /a/b/c/  /a/b            c/
 relative_path /a//b//c/        //a/b//         c/      POSIX
-relative_path /a/b     /a/b            .
-relative_path /a/b/    /a/b            .
-relative_path /a       /a/b            /a      POSIX
-relative_path /                /a/b/           /       POSIX
-relative_path /a/c     /a/b/           /a/c    POSIX
-relative_path /a/c     /a/b            /a/c    POSIX
-relative_path /x/y     /a/b/           /x/y    POSIX
+relative_path /a/b     /a/b            ./
+relative_path /a/b/    /a/b            ./
+relative_path /a       /a/b            ../
+relative_path /                /a/b/           ../../
+relative_path /a/c     /a/b/           ../c
+relative_path /a/c     /a/b            ../c
+relative_path /x/y     /a/b/           ../../x/y
 relative_path /a/b     "<empty>"       /a/b    POSIX
 relative_path /a/b     "<null>"        /a/b    POSIX
 relative_path a/b/c/   a/b/            c/
 relative_path a/b/c/   a/b             c/
 relative_path a/b//c   a//b            c
-relative_path a/b/     a/b/            .
-relative_path a/b/     a/b             .
-relative_path a                a/b             a       # TODO: should be: ..
-relative_path x/y      a/b             x/y     # TODO: should be: ../../x/y
-relative_path a/c      a/b             a/c     # TODO: should be: ../c
+relative_path a/b/     a/b/            ./
+relative_path a/b/     a/b             ./
+relative_path a                a/b             ../
+relative_path x/y      a/b             ../../x/y
+relative_path a/c      a/b             ../c
 relative_path a/b      "<empty>"       a/b
 relative_path a/b      "<null>"        a/b
-relative_path "<empty>"        /a/b            "(empty)"
-relative_path "<empty>"        "<empty>"       "(empty)"
-relative_path "<empty>"        "<null>"        "(empty)"
-relative_path "<null>" "<empty>"       "(null)"
-relative_path "<null>" "<null>"        "(null)"
-
-test_expect_failure 'relative path: <null> /a/b => segfault' '
-       test-path-utils relative_path "<null>" "/a/b"
-'
+relative_path "<empty>"        /a/b            ./
+relative_path "<empty>"        "<empty>"       ./
+relative_path "<empty>"        "<null>"        ./
+relative_path "<null>" "<empty>"       ./
+relative_path "<null>" "<null>"        ./
+relative_path "<null>" /a/b            ./
 
 test_done
index 8a6d22404e186521d495d9d4d244d0ac42048381..1bf4730619fd76bc2bf672c3c24f96d3f210cdb8 100644 (file)
@@ -117,14 +117,16 @@ int main(int argc, char **argv)
        }
 
        if (argc == 4 && !strcmp(argv[1], "relative_path")) {
+               struct strbuf sb = STRBUF_INIT;
                const char *in, *prefix, *rel;
                normalize_argv_string(&in, argv[2]);
                normalize_argv_string(&prefix, argv[3]);
-               rel = relative_path(in, prefix);
+               rel = relative_path(in, prefix, &sb);
                if (!rel)
                        puts("(null)");
                else
                        puts(strlen(rel) > 0 ? rel : "(empty)");
+               strbuf_release(&sb);
                return 0;
        }