Skip to content

Commit e10d48d

Browse files
committed
Merge branch 'js/maint-1.6.0-path-normalize' into maint-1.6.1
* js/maint-1.6.0-path-normalize: Remove unused normalize_absolute_path() Test and fix normalize_path_copy() Fix GIT_CEILING_DIRECTORIES on Windows Move sanitary_path_copy() to path.c and rename it to normalize_path_copy() Make test-path-utils more robust against incorrect use
2 parents 2990034 + f2a782b commit e10d48d

File tree

6 files changed

+115
-152
lines changed

6 files changed

+115
-152
lines changed

cache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ int is_directory(const char *);
625625
const char *make_absolute_path(const char *path);
626626
const char *make_nonrelative_path(const char *path);
627627
const char *make_relative_path(const char *abs, const char *base);
628-
int normalize_absolute_path(char *buf, const char *path);
628+
int normalize_path_copy(char *dst, const char *src);
629629
int longest_ancestor_length(const char *path, const char *prefix_list);
630630

631631
/* Read and unpack a sha1 file into memory, write memory to a sha1 file */

path.c

Lines changed: 83 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -363,56 +363,97 @@ const char *make_relative_path(const char *abs, const char *base)
363363
}
364364

365365
/*
366-
* path = absolute path
367-
* buf = buffer of at least max(2, strlen(path)+1) bytes
368-
* It is okay if buf == path, but they should not overlap otherwise.
366+
* It is okay if dst == src, but they should not overlap otherwise.
369367
*
370-
* Performs the following normalizations on path, storing the result in buf:
371-
* - Removes trailing slashes.
372-
* - Removes empty components.
368+
* Performs the following normalizations on src, storing the result in dst:
369+
* - Ensures that components are separated by '/' (Windows only)
370+
* - Squashes sequences of '/'.
373371
* - Removes "." components.
374372
* - Removes ".." components, and the components the precede them.
375-
* "" and paths that contain only slashes are normalized to "/".
376-
* Returns the length of the output.
373+
* Returns failure (non-zero) if a ".." component appears as first path
374+
* component anytime during the normalization. Otherwise, returns success (0).
377375
*
378376
* Note that this function is purely textual. It does not follow symlinks,
379377
* verify the existence of the path, or make any system calls.
380378
*/
381-
int normalize_absolute_path(char *buf, const char *path)
379+
int normalize_path_copy(char *dst, const char *src)
382380
{
383-
const char *comp_start = path, *comp_end = path;
384-
char *dst = buf;
385-
int comp_len;
386-
assert(buf);
387-
assert(path);
388-
389-
while (*comp_start) {
390-
assert(*comp_start == '/');
391-
while (*++comp_end && *comp_end != '/')
392-
; /* nothing */
393-
comp_len = comp_end - comp_start;
394-
395-
if (!strncmp("/", comp_start, comp_len) ||
396-
!strncmp("/.", comp_start, comp_len))
397-
goto next;
398-
399-
if (!strncmp("/..", comp_start, comp_len)) {
400-
while (dst > buf && *--dst != '/')
401-
; /* nothing */
402-
goto next;
403-
}
381+
char *dst0;
404382

405-
memmove(dst, comp_start, comp_len);
406-
dst += comp_len;
407-
next:
408-
comp_start = comp_end;
383+
if (has_dos_drive_prefix(src)) {
384+
*dst++ = *src++;
385+
*dst++ = *src++;
409386
}
387+
dst0 = dst;
410388

411-
if (dst == buf)
389+
if (is_dir_sep(*src)) {
412390
*dst++ = '/';
391+
while (is_dir_sep(*src))
392+
src++;
393+
}
394+
395+
for (;;) {
396+
char c = *src;
397+
398+
/*
399+
* A path component that begins with . could be
400+
* special:
401+
* (1) "." and ends -- ignore and terminate.
402+
* (2) "./" -- ignore them, eat slash and continue.
403+
* (3) ".." and ends -- strip one and terminate.
404+
* (4) "../" -- strip one, eat slash and continue.
405+
*/
406+
if (c == '.') {
407+
if (!src[1]) {
408+
/* (1) */
409+
src++;
410+
} else if (is_dir_sep(src[1])) {
411+
/* (2) */
412+
src += 2;
413+
while (is_dir_sep(*src))
414+
src++;
415+
continue;
416+
} else if (src[1] == '.') {
417+
if (!src[2]) {
418+
/* (3) */
419+
src += 2;
420+
goto up_one;
421+
} else if (is_dir_sep(src[2])) {
422+
/* (4) */
423+
src += 3;
424+
while (is_dir_sep(*src))
425+
src++;
426+
goto up_one;
427+
}
428+
}
429+
}
413430

431+
/* copy up to the next '/', and eat all '/' */
432+
while ((c = *src++) != '\0' && !is_dir_sep(c))
433+
*dst++ = c;
434+
if (is_dir_sep(c)) {
435+
*dst++ = '/';
436+
while (is_dir_sep(c))
437+
c = *src++;
438+
src--;
439+
} else if (!c)
440+
break;
441+
continue;
442+
443+
up_one:
444+
/*
445+
* dst0..dst is prefix portion, and dst[-1] is '/';
446+
* go up one level.
447+
*/
448+
dst--; /* go to trailing '/' */
449+
if (dst <= dst0)
450+
return -1;
451+
/* Windows: dst[-1] cannot be backslash anymore */
452+
while (dst0 < dst && dst[-1] != '/')
453+
dst--;
454+
}
414455
*dst = '\0';
415-
return dst - buf;
456+
return 0;
416457
}
417458

418459
/*
@@ -438,15 +479,16 @@ int longest_ancestor_length(const char *path, const char *prefix_list)
438479
return -1;
439480

440481
for (colon = ceil = prefix_list; *colon; ceil = colon+1) {
441-
for (colon = ceil; *colon && *colon != ':'; colon++);
482+
for (colon = ceil; *colon && *colon != PATH_SEP; colon++);
442483
len = colon - ceil;
443484
if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil))
444485
continue;
445486
strlcpy(buf, ceil, len+1);
446-
len = normalize_absolute_path(buf, buf);
447-
/* Strip "trailing slashes" from "/". */
448-
if (len == 1)
449-
len = 0;
487+
if (normalize_path_copy(buf, buf) < 0)
488+
continue;
489+
len = strlen(buf);
490+
if (len > 0 && buf[len-1] == '/')
491+
buf[--len] = '\0';
450492

451493
if (!strncmp(path, buf, len) &&
452494
path[len] == '/' &&

setup.c

Lines changed: 1 addition & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,92 +4,6 @@
44
static int inside_git_dir = -1;
55
static int inside_work_tree = -1;
66

7-
static int sanitary_path_copy(char *dst, const char *src)
8-
{
9-
char *dst0;
10-
11-
if (has_dos_drive_prefix(src)) {
12-
*dst++ = *src++;
13-
*dst++ = *src++;
14-
}
15-
dst0 = dst;
16-
17-
if (is_dir_sep(*src)) {
18-
*dst++ = '/';
19-
while (is_dir_sep(*src))
20-
src++;
21-
}
22-
23-
for (;;) {
24-
char c = *src;
25-
26-
/*
27-
* A path component that begins with . could be
28-
* special:
29-
* (1) "." and ends -- ignore and terminate.
30-
* (2) "./" -- ignore them, eat slash and continue.
31-
* (3) ".." and ends -- strip one and terminate.
32-
* (4) "../" -- strip one, eat slash and continue.
33-
*/
34-
if (c == '.') {
35-
if (!src[1]) {
36-
/* (1) */
37-
src++;
38-
} else if (is_dir_sep(src[1])) {
39-
/* (2) */
40-
src += 2;
41-
while (is_dir_sep(*src))
42-
src++;
43-
continue;
44-
} else if (src[1] == '.') {
45-
if (!src[2]) {
46-
/* (3) */
47-
src += 2;
48-
goto up_one;
49-
} else if (is_dir_sep(src[2])) {
50-
/* (4) */
51-
src += 3;
52-
while (is_dir_sep(*src))
53-
src++;
54-
goto up_one;
55-
}
56-
}
57-
}
58-
59-
/* copy up to the next '/', and eat all '/' */
60-
while ((c = *src++) != '\0' && !is_dir_sep(c))
61-
*dst++ = c;
62-
if (is_dir_sep(c)) {
63-
*dst++ = '/';
64-
while (is_dir_sep(c))
65-
c = *src++;
66-
src--;
67-
} else if (!c)
68-
break;
69-
continue;
70-
71-
up_one:
72-
/*
73-
* dst0..dst is prefix portion, and dst[-1] is '/';
74-
* go up one level.
75-
*/
76-
dst -= 2; /* go past trailing '/' if any */
77-
if (dst < dst0)
78-
return -1;
79-
while (1) {
80-
if (dst <= dst0)
81-
break;
82-
c = *dst--;
83-
if (c == '/') { /* MinGW: cannot be '\\' anymore */
84-
dst += 2;
85-
break;
86-
}
87-
}
88-
}
89-
*dst = '\0';
90-
return 0;
91-
}
92-
937
const char *prefix_path(const char *prefix, int len, const char *path)
948
{
959
const char *orig = path;
@@ -101,7 +15,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
10115
memcpy(sanitized, prefix, len);
10216
strcpy(sanitized + len, path);
10317
}
104-
if (sanitary_path_copy(sanitized, sanitized))
18+
if (normalize_path_copy(sanitized, sanitized))
10519
goto error_out;
10620
if (is_absolute_path(orig)) {
10721
const char *work_tree = get_git_work_tree();

t/t0060-path-utils.sh

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,37 @@ test_description='Test various path utilities'
88
. ./test-lib.sh
99

1010
norm_abs() {
11-
test_expect_success "normalize absolute" \
12-
"test \$(test-path-utils normalize_absolute_path '$1') = '$2'"
11+
test_expect_success "normalize absolute: $1 => $2" \
12+
"test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'"
1313
}
1414

1515
ancestor() {
16-
test_expect_success "longest ancestor" \
17-
"test \$(test-path-utils longest_ancestor_length '$1' '$2') = '$3'"
16+
test_expect_success "longest ancestor: $1 $2 => $3" \
17+
"test \"\$(test-path-utils longest_ancestor_length '$1' '$2')\" = '$3'"
1818
}
1919

20-
norm_abs "" /
20+
norm_abs "" ""
2121
norm_abs / /
2222
norm_abs // /
2323
norm_abs /// /
2424
norm_abs /. /
2525
norm_abs /./ /
26-
norm_abs /./.. /
27-
norm_abs /../. /
28-
norm_abs /./../.// /
26+
norm_abs /./.. ++failed++
27+
norm_abs /../. ++failed++
28+
norm_abs /./../.// ++failed++
2929
norm_abs /dir/.. /
3030
norm_abs /dir/sub/../.. /
31+
norm_abs /dir/sub/../../.. ++failed++
3132
norm_abs /dir /dir
32-
norm_abs /dir// /dir
33+
norm_abs /dir// /dir/
3334
norm_abs /./dir /dir
34-
norm_abs /dir/. /dir
35-
norm_abs /dir///./ /dir
36-
norm_abs /dir//sub/.. /dir
37-
norm_abs /dir/sub/../ /dir
38-
norm_abs //dir/sub/../. /dir
39-
norm_abs /dir/s1/../s2/ /dir/s2
40-
norm_abs /d1/s1///s2/..//../s3/ /d1/s3
35+
norm_abs /dir/. /dir/
36+
norm_abs /dir///./ /dir/
37+
norm_abs /dir//sub/.. /dir/
38+
norm_abs /dir/sub/../ /dir/
39+
norm_abs //dir/sub/../. /dir/
40+
norm_abs /dir/s1/../s2/ /dir/s2/
41+
norm_abs /d1/s1///s2/..//../s3/ /d1/s3/
4142
norm_abs /d1/s1//../s2/../../d2 /d2
4243
norm_abs /d1/.../d2 /d1/.../d2
4344
norm_abs /d1/..././../d2 /d1/d2

t/t1504-ceiling-dirs.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ GIT_CEILING_DIRECTORIES="$TRASH_ROOT/subdi"
9393
test_prefix subdir_ceil_at_subdi_slash "sub/dir/"
9494

9595

96-
GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub"
96+
GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub"
9797
test_fail second_of_two
9898

99-
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:bar"
99+
GIT_CEILING_DIRECTORIES="$TRASH_ROOT/sub:/bar"
100100
test_fail first_of_two
101101

102-
GIT_CEILING_DIRECTORIES="foo:$TRASH_ROOT/sub:bar"
102+
GIT_CEILING_DIRECTORIES="/foo:$TRASH_ROOT/sub:/bar"
103103
test_fail second_of_three
104104

105105

test-path-utils.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
int main(int argc, char **argv)
44
{
5-
if (argc == 3 && !strcmp(argv[1], "normalize_absolute_path")) {
5+
if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
66
char *buf = xmalloc(PATH_MAX + 1);
7-
int rv = normalize_absolute_path(buf, argv[2]);
8-
assert(strlen(buf) == rv);
7+
int rv = normalize_path_copy(buf, argv[2]);
8+
if (rv)
9+
buf = "++failed++";
910
puts(buf);
11+
return 0;
1012
}
1113

1214
if (argc >= 2 && !strcmp(argv[1], "make_absolute_path")) {
@@ -15,12 +17,16 @@ int main(int argc, char **argv)
1517
argc--;
1618
argv++;
1719
}
20+
return 0;
1821
}
1922

2023
if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) {
2124
int len = longest_ancestor_length(argv[2], argv[3]);
2225
printf("%d\n", len);
26+
return 0;
2327
}
2428

25-
return 0;
29+
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
30+
argv[1] ? argv[1] : "(there was none)");
31+
return 1;
2632
}

0 commit comments

Comments
 (0)