Skip to content

Commit e02ca72

Browse files
jiangxingitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 203439b commit e02ca72

File tree

5 files changed

+111
-51
lines changed

5 files changed

+111
-51
lines changed

cache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ int is_directory(const char *);
737737
const char *real_path(const char *path);
738738
const char *real_path_if_valid(const char *path);
739739
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);
741741
int normalize_path_copy(char *dst, const char *src);
742742
int longest_ancestor_length(const char *path, struct string_list *prefixes);
743743
char *strip_path_suffix(const char *path, const char *suffix);

path.c

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -441,42 +441,100 @@ int adjust_shared_perm(const char *path)
441441
return 0;
442442
}
443443

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)
445452
{
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;
447457
int i = 0, j = 0;
448458

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]))
456467
i++;
457-
while (is_dir_sep(abs[j]))
468+
while (is_dir_sep(in[j]))
458469
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++;
459528
continue;
460-
} else if (abs[j] != base[i]) {
461-
return abs;
462529
}
463530
i++;
464-
j++;
465531
}
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;
480538
}
481539

482540
/*

setup.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ int is_inside_work_tree(void)
360360

361361
void setup_work_tree(void)
362362
{
363+
struct strbuf sb = STRBUF_INIT;
363364
const char *work_tree, *git_dir;
364365
static int initialized = 0;
365366

@@ -379,8 +380,10 @@ void setup_work_tree(void)
379380
if (getenv(GIT_WORK_TREE_ENVIRONMENT))
380381
setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
381382

382-
set_git_dir(relative_path(git_dir, work_tree));
383+
set_git_dir(relative_path(git_dir, work_tree, &sb));
383384
initialized = 1;
385+
386+
strbuf_release(&sb);
384387
}
385388

386389
static int check_repository_format_gently(const char *gitdir, int *nongit_ok)

t/t0060-path-utils.sh

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -191,33 +191,30 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
191191
relative_path /a/b/c/ /a/b/ c/
192192
relative_path /a/b/c/ /a/b c/
193193
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
201201
relative_path /a/b "<empty>" /a/b POSIX
202202
relative_path /a/b "<null>" /a/b POSIX
203203
relative_path a/b/c/ a/b/ c/
204204
relative_path a/b/c/ a/b c/
205205
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
211211
relative_path a/b "<empty>" a/b
212212
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 ./
222219

223220
test_done

test-path-utils.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,16 @@ int main(int argc, char **argv)
117117
}
118118

119119
if (argc == 4 && !strcmp(argv[1], "relative_path")) {
120+
struct strbuf sb = STRBUF_INIT;
120121
const char *in, *prefix, *rel;
121122
normalize_argv_string(&in, argv[2]);
122123
normalize_argv_string(&prefix, argv[3]);
123-
rel = relative_path(in, prefix);
124+
rel = relative_path(in, prefix, &sb);
124125
if (!rel)
125126
puts("(null)");
126127
else
127128
puts(strlen(rel) > 0 ? rel : "(empty)");
129+
strbuf_release(&sb);
128130
return 0;
129131
}
130132

0 commit comments

Comments
 (0)