diff options
author | Jiang Xin <worldhello.net@gmail.com> | 2013-06-25 23:53:43 +0800 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2013-06-26 09:59:00 -0700 |
commit | e02ca72f70ed8f0268a81f72cb3230c72e538e77 (patch) | |
tree | 922b62a3d30e9d5a604acaab92d0c83aba4114f8 /path.c | |
parent | 203439b2840c4c384060df2fa192994e4b6740ed (diff) | |
download | git-e02ca72f70ed8f0268a81f72cb3230c72e538e77.tar.gz |
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>
Diffstat (limited to 'path.c')
-rw-r--r-- | path.c | 112 |
1 files changed, 85 insertions, 27 deletions
@@ -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; } /* |