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>

Jiang Xin committed Jun 25, 2013 at 23:53 UTC e02ca72f70ed8f0268a81f72cb3230c72e538e77
5 files changed +111 -51
cache.h
+1 -1
@@ -737,7 +737,7 @@ int is_directory(const char *);
737 const char *real_path(const char *path);
738 const char *real_path_if_valid(const char *path);
739 const char *absolute_path(const char *path);
740 -const char *relative_path(const char *abs, const char *base);
740 +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
741 int normalize_path_copy(char *dst, const char *src);
742 int longest_ancestor_length(const char *path, struct string_list *prefixes);
743 char *strip_path_suffix(const char *path, const char *suffix);
path.c
+85 -27
@@ -441,42 +441,100 @@ int adjust_shared_perm(const char *path)
441 return 0;
442 }
443
444 -const char *relative_path(const char *abs, const char *base)
444 +/*
445 + * Give path as relative to prefix.
446 + *
447 + * The strbuf may or may not be used, so do not assume it contains the
448 + * returned path.
449 + */
450 +const char *relative_path(const char *in, const char *prefix,
451 + struct strbuf *sb)
452 {
446 - static char buf[PATH_MAX + 1];
453 + int in_len = in ? strlen(in) : 0;
454 + int prefix_len = prefix ? strlen(prefix) : 0;
455 + int in_off = 0;
456 + int prefix_off = 0;
457 int i = 0, j = 0;
458
449 - if (!base || !base[0])
450 - return abs;
451 - while (base[i]) {
452 - if (is_dir_sep(base[i])) {
453 - if (!is_dir_sep(abs[j]))
454 - return abs;
455 - while (is_dir_sep(base[i]))
459 + if (!in_len)
460 + return "./";
461 + else if (!prefix_len)
462 + return in;
463 +
464 + while (i < prefix_len && j < in_len && prefix[i] == in[j]) {
465 + if (is_dir_sep(prefix[i])) {
466 + while (is_dir_sep(prefix[i]))
467 i++;
457 - while (is_dir_sep(abs[j]))
468 + while (is_dir_sep(in[j]))
469 j++;
470 + prefix_off = i;
471 + in_off = j;
472 + } else {
473 + i++;
474 + j++;
475 + }
476 + }
477 +
478 + if (
479 + /* "prefix" seems like prefix of "in" */
480 + i >= prefix_len &&
481 + /*
482 + * but "/foo" is not a prefix of "/foobar"
483 + * (i.e. prefix not end with '/')
484 + */
485 + prefix_off < prefix_len) {
486 + if (j >= in_len) {
487 + /* in="/a/b", prefix="/a/b" */
488 + in_off = in_len;
489 + } else if (is_dir_sep(in[j])) {
490 + /* in="/a/b/c", prefix="/a/b" */
491 + while (is_dir_sep(in[j]))
492 + j++;
493 + in_off = j;
494 + } else {
495 + /* in="/a/bbb/c", prefix="/a/b" */
496 + i = prefix_off;
497 + }
498 + } else if (
499 + /* "in" is short than "prefix" */
500 + j >= in_len &&
501 + /* "in" not end with '/' */
502 + in_off < in_len) {
503 + if (is_dir_sep(prefix[i])) {
504 + /* in="/a/b", prefix="/a/b/c/" */
505 + while (is_dir_sep(prefix[i]))
506 + i++;
507 + in_off = in_len;
508 + }
509 + }
510 + in += in_off;
511 + in_len -= in_off;
512 +
513 + if (i >= prefix_len) {
514 + if (!in_len)
515 + return "./";
516 + else
517 + return in;
518 + }
519 +
520 + strbuf_reset(sb);
521 + strbuf_grow(sb, in_len);
522 +
523 + while (i < prefix_len) {
524 + if (is_dir_sep(prefix[i])) {
525 + strbuf_addstr(sb, "../");
526 + while (is_dir_sep(prefix[i]))
527 + i++;
528 continue;
460 - } else if (abs[j] != base[i]) {
461 - return abs;
529 }
530 i++;
464 - j++;
531 }
466 - if (
467 - /* "/foo" is a prefix of "/foo" */
468 - abs[j] &&
469 - /* "/foo" is not a prefix of "/foobar" */
470 - !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])
471 - )
472 - return abs;
473 - while (is_dir_sep(abs[j]))
474 - j++;
475 - if (!abs[j])
476 - strcpy(buf, ".");
477 - else
478 - strcpy(buf, abs + j);
479 - return buf;
532 + if (!is_dir_sep(prefix[prefix_len - 1]))
533 + strbuf_addstr(sb, "../");
534 +
535 + strbuf_addstr(sb, in);
536 +
537 + return sb->buf;
538 }
539
540 /*
setup.c
+4 -1
@@ -360,6 +360,7 @@ int is_inside_work_tree(void)
360
361 void setup_work_tree(void)
362 {
363 + struct strbuf sb = STRBUF_INIT;
364 const char *work_tree, *git_dir;
365 static int initialized = 0;
366
@@ -379,8 +380,10 @@ void setup_work_tree(void)
380 if (getenv(GIT_WORK_TREE_ENVIRONMENT))
381 setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
382
382 - set_git_dir(relative_path(git_dir, work_tree));
383 + set_git_dir(relative_path(git_dir, work_tree, &sb));
384 initialized = 1;
385 +
386 + strbuf_release(&sb);
387 }
388
389 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
t/t0060-path-utils.sh
+18 -21
@@ -191,33 +191,30 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
191 relative_path /a/b/c/ /a/b/ c/
192 relative_path /a/b/c/ /a/b c/
193 relative_path /a//b//c/ //a/b// c/ POSIX
194 -relative_path /a/b /a/b .
195 -relative_path /a/b/ /a/b .
196 -relative_path /a /a/b /a POSIX
197 -relative_path / /a/b/ / POSIX
198 -relative_path /a/c /a/b/ /a/c POSIX
199 -relative_path /a/c /a/b /a/c POSIX
200 -relative_path /x/y /a/b/ /x/y POSIX
194 +relative_path /a/b /a/b ./
195 +relative_path /a/b/ /a/b ./
196 +relative_path /a /a/b ../
197 +relative_path / /a/b/ ../../
198 +relative_path /a/c /a/b/ ../c
199 +relative_path /a/c /a/b ../c
200 +relative_path /x/y /a/b/ ../../x/y
201 relative_path /a/b "<empty>" /a/b POSIX
202 relative_path /a/b "<null>" /a/b POSIX
203 relative_path a/b/c/ a/b/ c/
204 relative_path a/b/c/ a/b c/
205 relative_path a/b//c a//b c
206 -relative_path a/b/ a/b/ .
207 -relative_path a/b/ a/b .
208 -relative_path a a/b a # TODO: should be: ..
209 -relative_path x/y a/b x/y # TODO: should be: ../../x/y
210 -relative_path a/c a/b a/c # TODO: should be: ../c
206 +relative_path a/b/ a/b/ ./
207 +relative_path a/b/ a/b ./
208 +relative_path a a/b ../
209 +relative_path x/y a/b ../../x/y
210 +relative_path a/c a/b ../c
211 relative_path a/b "<empty>" a/b
212 relative_path a/b "<null>" a/b
213 -relative_path "<empty>" /a/b "(empty)"
214 -relative_path "<empty>" "<empty>" "(empty)"
215 -relative_path "<empty>" "<null>" "(empty)"
216 -relative_path "<null>" "<empty>" "(null)"
217 -relative_path "<null>" "<null>" "(null)"
218 -
219 -test_expect_failure 'relative path: <null> /a/b => segfault' '
220 - test-path-utils relative_path "<null>" "/a/b"
221 -'
213 +relative_path "<empty>" /a/b ./
214 +relative_path "<empty>" "<empty>" ./
215 +relative_path "<empty>" "<null>" ./
216 +relative_path "<null>" "<empty>" ./
217 +relative_path "<null>" "<null>" ./
218 +relative_path "<null>" /a/b ./
219
220 test_done
test-path-utils.c
+3 -1
@@ -117,14 +117,16 @@ int main(int argc, char **argv)
117 }
118
119 if (argc == 4 && !strcmp(argv[1], "relative_path")) {
120 + struct strbuf sb = STRBUF_INIT;
121 const char *in, *prefix, *rel;
122 normalize_argv_string(&in, argv[2]);
123 normalize_argv_string(&prefix, argv[3]);
123 - rel = relative_path(in, prefix);
124 + rel = relative_path(in, prefix, &sb);
125 if (!rel)
126 puts("(null)");
127 else
128 puts(strlen(rel) > 0 ? rel : "(empty)");
129 + strbuf_release(&sb);
130 return 0;
131 }
132