Skip to content

Commit cb6e5d0

Browse files
authored
Merge pull request #1221 from akinomyoga/load-path
fix: source files using absolute paths for absolute BASH_SOURCE
2 parents 0e7a0cf + d599dcf commit cb6e5d0

File tree

2 files changed

+159
-20
lines changed

2 files changed

+159
-20
lines changed

bash_completion

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,15 +2190,22 @@ _comp_compgen_fstypes()
21902190
_comp_abspath()
21912191
{
21922192
REPLY=$1
2193-
case $REPLY in
2194-
/*) ;;
2195-
../*) REPLY=$PWD/${REPLY:3} ;;
2196-
*) REPLY=$PWD/$REPLY ;;
2197-
esac
2198-
while [[ $REPLY == */./* ]]; do
2199-
REPLY=${REPLY//\/.\//\/}
2200-
done
2193+
[[ $REPLY == /* ]] || REPLY=$PWD/$REPLY
22012194
REPLY=${REPLY//+(\/)/\/}
2195+
while true; do
2196+
# Process "." and "..". To avoid reducing "/../../ => /", we convert
2197+
# "/*/../" one by one. "/.." at the beginning is ignored. Then, /*/../
2198+
# in the middle is processed. Finally, /*/.. at the end is removed.
2199+
case $REPLY in
2200+
*/./*) REPLY=${REPLY//\/.\//\/} ;;
2201+
*/.) REPLY=${REPLY%/.} ;;
2202+
/..?(/*)) REPLY=${REPLY#/..} ;;
2203+
*/+([^/])/../*) REPLY=${REPLY/\/+([^\/])\/..\//\/} ;;
2204+
*/+([^/])/..) REPLY=${REPLY%/+([^/])/..} ;;
2205+
*) break ;;
2206+
esac
2207+
done
2208+
[[ $REPLY ]] || REPLY=/
22022209
}
22032210

22042211
# Get real command.
@@ -3141,6 +3148,18 @@ _comp_complete_minimal()
31413148
# https://lists.gnu.org/archive/html/bug-bash/2012-01/msg00045.html
31423149
complete -F _comp_complete_minimal ''
31433150
3151+
# Initialize the variable "_comp__base_directory"
3152+
# @var[out] _comp__base_directory
3153+
_comp__init_base_directory()
3154+
{
3155+
local REPLY
3156+
_comp_abspath "${BASH_SOURCE[0]-./bash_completion}"
3157+
_comp__base_directory=${REPLY%/*}
3158+
[[ $_comp__base_directory ]] || _comp__base_directory=/
3159+
unset -f "$FUNCNAME"
3160+
}
3161+
_comp__init_base_directory
3162+
31443163
# @since 2.12
31453164
_comp_load()
31463165
{
@@ -3193,11 +3212,7 @@ _comp_load()
31933212
# we want to prefer in-tree completions over ones possibly coming with a
31943213
# system installed bash-completion. (Due to usual install layouts, this
31953214
# often hits the correct completions in system installations, too.)
3196-
if [[ $BASH_SOURCE == */* ]]; then
3197-
dirs+=("${BASH_SOURCE%/*}/completions")
3198-
else
3199-
dirs+=(./completions)
3200-
fi
3215+
dirs+=("$_comp__base_directory/completions")
32013216
32023217
# 3) From bin directories extracted from the specified path to the command,
32033218
# the real path to the command, and $PATH
@@ -3339,12 +3354,10 @@ _comp__init_collect_startup_configs()
33393354
# run-in-place-from-git-clone setups. Notably we do it after the
33403355
# system location here, in order to prefer in-tree variables and
33413356
# functions.
3342-
if [[ ${base_path%/*} == */share/bash-completion ]]; then
3343-
compat_dir=${base_path%/share/bash-completion/*}/etc/bash_completion.d
3344-
elif [[ $base_path == */* ]]; then
3345-
compat_dir="${base_path%/*}/bash_completion.d"
3357+
if [[ $_comp__base_directory == */share/bash-completion ]]; then
3358+
compat_dir=${_comp__base_directory%/share/bash-completion}/etc/bash_completion.d
33463359
else
3347-
compat_dir=./bash_completion.d
3360+
compat_dir=$_comp__base_directory/bash_completion.d
33483361
fi
33493362
[[ ${compat_dirs[0]} == "$compat_dir" ]] ||
33503363
compat_dirs+=("$compat_dir")

test/t/unit/test_unit_abspath.py

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_relative(self, bash, functions):
4646
)
4747
assert output.strip().endswith("/shared/foo/bar")
4848

49-
def test_cwd(self, bash, functions):
49+
def test_cwd1(self, bash, functions):
5050
output = assert_bash_exec(
5151
bash,
5252
"__tester ./foo/./bar",
@@ -55,7 +55,34 @@ def test_cwd(self, bash, functions):
5555
)
5656
assert output.strip().endswith("/shared/foo/bar")
5757

58-
def test_parent(self, bash, functions):
58+
def test_cwd2(self, bash, functions):
59+
output = assert_bash_exec(
60+
bash,
61+
"__tester /.",
62+
want_output=True,
63+
want_newline=False,
64+
)
65+
assert output.strip() == "/"
66+
67+
def test_cwd3(self, bash, functions):
68+
output = assert_bash_exec(
69+
bash,
70+
"__tester /foo/.",
71+
want_output=True,
72+
want_newline=False,
73+
)
74+
assert output.strip() == "/foo"
75+
76+
def test_cwd4(self, bash, functions):
77+
output = assert_bash_exec(
78+
bash,
79+
"__tester /././.",
80+
want_output=True,
81+
want_newline=False,
82+
)
83+
assert output.strip() == "/"
84+
85+
def test_parent1(self, bash, functions):
5986
output = assert_bash_exec(
6087
bash,
6188
"__tester ../shared/foo/bar",
@@ -65,3 +92,102 @@ def test_parent(self, bash, functions):
6592
assert output.strip().endswith(
6693
"/shared/foo/bar"
6794
) and not output.strip().endswith("../shared/foo/bar")
95+
96+
def test_parent2(self, bash, functions):
97+
output = assert_bash_exec(
98+
bash,
99+
"__tester /foo/..",
100+
want_output=True,
101+
want_newline=False,
102+
)
103+
assert output.strip() == "/"
104+
105+
def test_parent3(self, bash, functions):
106+
output = assert_bash_exec(
107+
bash,
108+
"__tester /..",
109+
want_output=True,
110+
want_newline=False,
111+
)
112+
assert output.strip() == "/"
113+
114+
def test_parent4(self, bash, functions):
115+
output = assert_bash_exec(
116+
bash,
117+
"__tester /../foo/bar",
118+
want_output=True,
119+
want_newline=False,
120+
)
121+
assert output.strip() == "/foo/bar"
122+
123+
def test_parent5(self, bash, functions):
124+
output = assert_bash_exec(
125+
bash,
126+
"__tester /../../foo/bar",
127+
want_output=True,
128+
want_newline=False,
129+
)
130+
assert output.strip() == "/foo/bar"
131+
132+
def test_parent6(self, bash, functions):
133+
output = assert_bash_exec(
134+
bash,
135+
"__tester /foo/../bar",
136+
want_output=True,
137+
want_newline=False,
138+
)
139+
assert output.strip() == "/bar"
140+
141+
def test_parent7(self, bash, functions):
142+
output = assert_bash_exec(
143+
bash,
144+
"__tester /foo/../../bar",
145+
want_output=True,
146+
want_newline=False,
147+
)
148+
assert output.strip() == "/bar"
149+
150+
def test_parent8(self, bash, functions):
151+
output = assert_bash_exec(
152+
bash,
153+
"__tester /dir1/dir2/dir3/../dir4/../../foo",
154+
want_output=True,
155+
want_newline=False,
156+
)
157+
assert output.strip() == "/dir1/foo"
158+
159+
def test_parent9(self, bash, functions):
160+
output = assert_bash_exec(
161+
bash,
162+
"__tester //dir1/dir2///../foo",
163+
want_output=True,
164+
want_newline=False,
165+
)
166+
assert output.strip() == "/dir1/foo"
167+
168+
def test_parent10(self, bash, functions):
169+
output = assert_bash_exec(
170+
bash,
171+
"__tester /dir1/dir2/dir3/..",
172+
want_output=True,
173+
want_newline=False,
174+
)
175+
assert output.strip() == "/dir1/dir2"
176+
177+
def test_parent11(self, bash, functions):
178+
output = assert_bash_exec(
179+
bash,
180+
"__tester /dir1/dir2/dir3/../..",
181+
want_output=True,
182+
want_newline=False,
183+
)
184+
assert output.strip() == "/dir1"
185+
186+
def test_parent12(self, bash, functions):
187+
output = assert_bash_exec(
188+
bash,
189+
"__tester /dir1/dir2/dir3/../../../..",
190+
want_output=True,
191+
want_newline=False,
192+
)
193+
assert output.strip() == "/"

0 commit comments

Comments
 (0)