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:

committed by
Junio C Hamano

parent
203439b284
commit
e02ca72f70
112
path.c
112
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user