summaryrefslogtreecommitdiff
path: root/path.c
diff options
context:
space:
mode:
authorJiang Xin <worldhello.net@gmail.com>2013-06-25 15:53:43 (GMT)
committerJunio C Hamano <gitster@pobox.com>2013-06-26 16:59:00 (GMT)
commite02ca72f70ed8f0268a81f72cb3230c72e538e77 (patch)
tree922b62a3d30e9d5a604acaab92d0c83aba4114f8 /path.c
parent203439b2840c4c384060df2fa192994e4b6740ed (diff)
downloadgit-e02ca72f70ed8f0268a81f72cb3230c72e538e77.zip
git-e02ca72f70ed8f0268a81f72cb3230c72e538e77.tar.gz
git-e02ca72f70ed8f0268a81f72cb3230c72e538e77.tar.bz2
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.c112
1 files changed, 85 insertions, 27 deletions
diff --git a/path.c b/path.c
index 04ff148..7f3324a 100644
--- 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;
}
/*