path.c: refactor relative_path(), not only strip prefix

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>
This commit is contained in:
Jiang Xin 2013-06-25 23:53:43 +08:00 committed by Junio C Hamano
parent 203439b284
commit e02ca72f70
5 changed files with 111 additions and 51 deletions

View File

@ -737,7 +737,7 @@ int is_directory(const char *);
const char *real_path(const char *path); const char *real_path(const char *path);
const char *real_path_if_valid(const char *path); const char *real_path_if_valid(const char *path);
const char *absolute_path(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 normalize_path_copy(char *dst, const char *src);
int longest_ancestor_length(const char *path, struct string_list *prefixes); int longest_ancestor_length(const char *path, struct string_list *prefixes);
char *strip_path_suffix(const char *path, const char *suffix); char *strip_path_suffix(const char *path, const char *suffix);

108
path.c
View File

@ -441,42 +441,100 @@ int adjust_shared_perm(const char *path)
return 0; 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; int i = 0, j = 0;
if (!base || !base[0]) if (!in_len)
return abs; return "./";
while (base[i]) { else if (!prefix_len)
if (is_dir_sep(base[i])) { return in;
if (!is_dir_sep(abs[j]))
return abs; while (i < prefix_len && j < in_len && prefix[i] == in[j]) {
while (is_dir_sep(base[i])) if (is_dir_sep(prefix[i])) {
while (is_dir_sep(prefix[i]))
i++; i++;
while (is_dir_sep(abs[j])) while (is_dir_sep(in[j]))
j++; j++;
continue; prefix_off = i;
} else if (abs[j] != base[i]) { in_off = j;
return abs; } else {
}
i++; i++;
j++; j++;
} }
}
if ( if (
/* "/foo" is a prefix of "/foo" */ /* "prefix" seems like prefix of "in" */
abs[j] && i >= prefix_len &&
/* "/foo" is not a prefix of "/foobar" */ /*
!is_dir_sep(base[i-1]) && !is_dir_sep(abs[j]) * but "/foo" is not a prefix of "/foobar"
) * (i.e. prefix not end with '/')
return abs; */
while (is_dir_sep(abs[j])) 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++; j++;
if (!abs[j]) in_off = j;
strcpy(buf, "."); } 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 else
strcpy(buf, abs + j); return in;
return buf; }
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;
}
i++;
}
if (!is_dir_sep(prefix[prefix_len - 1]))
strbuf_addstr(sb, "../");
strbuf_addstr(sb, in);
return sb->buf;
} }
/* /*

View File

@ -360,6 +360,7 @@ int is_inside_work_tree(void)
void setup_work_tree(void) void setup_work_tree(void)
{ {
struct strbuf sb = STRBUF_INIT;
const char *work_tree, *git_dir; const char *work_tree, *git_dir;
static int initialized = 0; static int initialized = 0;
@ -379,8 +380,10 @@ void setup_work_tree(void)
if (getenv(GIT_WORK_TREE_ENVIRONMENT)) if (getenv(GIT_WORK_TREE_ENVIRONMENT))
setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); 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; initialized = 1;
strbuf_release(&sb);
} }
static int check_repository_format_gently(const char *gitdir, int *nongit_ok) static int check_repository_format_gently(const char *gitdir, int *nongit_ok)

View File

@ -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/ relative_path /a/b/c/ /a/b c/
relative_path /a//b//c/ //a/b// c/ POSIX relative_path /a//b//c/ //a/b// c/ POSIX
relative_path /a/b /a/b . relative_path /a/b /a/b ./
relative_path /a/b/ /a/b . relative_path /a/b/ /a/b ./
relative_path /a /a/b /a POSIX relative_path /a /a/b ../
relative_path / /a/b/ / POSIX relative_path / /a/b/ ../../
relative_path /a/c /a/b/ /a/c POSIX relative_path /a/c /a/b/ ../c
relative_path /a/c /a/b /a/c POSIX relative_path /a/c /a/b ../c
relative_path /x/y /a/b/ /x/y POSIX relative_path /x/y /a/b/ ../../x/y
relative_path /a/b "<empty>" /a/b POSIX relative_path /a/b "<empty>" /a/b POSIX
relative_path /a/b "<null>" /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/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/b/ a/b . relative_path a/b/ a/b ./
relative_path a a/b a # TODO: should be: .. relative_path a a/b ../
relative_path x/y a/b x/y # TODO: should be: ../../x/y relative_path x/y a/b ../../x/y
relative_path a/c a/b a/c # TODO: should be: ../c relative_path a/c a/b ../c
relative_path a/b "<empty>" a/b relative_path a/b "<empty>" a/b
relative_path a/b "<null>" a/b relative_path a/b "<null>" a/b
relative_path "<empty>" /a/b "(empty)" relative_path "<empty>" /a/b ./
relative_path "<empty>" "<empty>" "(empty)" relative_path "<empty>" "<empty>" ./
relative_path "<empty>" "<null>" "(empty)" relative_path "<empty>" "<null>" ./
relative_path "<null>" "<empty>" "(null)" relative_path "<null>" "<empty>" ./
relative_path "<null>" "<null>" "(null)" relative_path "<null>" "<null>" ./
relative_path "<null>" /a/b ./
test_expect_failure 'relative path: <null> /a/b => segfault' '
test-path-utils relative_path "<null>" "/a/b"
'
test_done test_done

View File

@ -117,14 +117,16 @@ int main(int argc, char **argv)
} }
if (argc == 4 && !strcmp(argv[1], "relative_path")) { if (argc == 4 && !strcmp(argv[1], "relative_path")) {
struct strbuf sb = STRBUF_INIT;
const char *in, *prefix, *rel; const char *in, *prefix, *rel;
normalize_argv_string(&in, argv[2]); normalize_argv_string(&in, argv[2]);
normalize_argv_string(&prefix, argv[3]); normalize_argv_string(&prefix, argv[3]);
rel = relative_path(in, prefix); rel = relative_path(in, prefix, &sb);
if (!rel) if (!rel)
puts("(null)"); puts("(null)");
else else
puts(strlen(rel) > 0 ? rel : "(empty)"); puts(strlen(rel) > 0 ? rel : "(empty)");
strbuf_release(&sb);
return 0; return 0;
} }