diff --git a/bash_completion b/bash_completion index 1b1c8bef742..66d5e4fa31c 100644 --- a/bash_completion +++ b/bash_completion @@ -1142,6 +1142,15 @@ _comp_compgen_filedir() -f ${_plusdirs+"${_plusdirs[@]}"} fi + if ((${#toks[@]} != 0)); then + # Remove . and .. (as well as */. and */..) from suggestions, unless + # .. or */.. was typed explicitly by the user (for users who use + # tab-completion to append a slash after '..') + if [[ $cur != ?(*/).. ]]; then + _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' + fi + fi + if ((${#toks[@]} != 0)); then # 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir # unit test @@ -3042,6 +3051,13 @@ _comp_compgen_filedir_xspec() ((${#toks[@]})) || return 1 + # Remove . and .. (as well as */. and */..) from suggestions, unless .. or + # */.. was typed explicitly by the user (for users who use tab-completion + # to append a slash after '..') + if [[ $cur != ?(*/).. ]]; then + _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' || return 1 + fi + compopt -o filenames _comp_compgen -RU toks -- -W '"${toks[@]}"' } diff --git a/test/fixtures/_filedir/.dotfile1 b/test/fixtures/_filedir/.dotfile1 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/_filedir/.dotfile2 b/test/fixtures/_filedir/.dotfile2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/_filedir/dotdot/..2016_02_01_15_04_05.123456 b/test/fixtures/_filedir/dotdot/..2016_02_01_15_04_05.123456 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/_filedir/dotdot/..data b/test/fixtures/_filedir/dotdot/..data new file mode 120000 index 00000000000..80557a61c9c --- /dev/null +++ b/test/fixtures/_filedir/dotdot/..data @@ -0,0 +1 @@ +..2016_02_01_15_04_05.123456 \ No newline at end of file diff --git a/test/fixtures/_filedir/dotdot/..folder/.nothing_here b/test/fixtures/_filedir/dotdot/..folder/.nothing_here new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/t/unit/test_unit_compgen_filedir.py b/test/t/unit/test_unit_compgen_filedir.py index 1e3844faa48..aa18868c76d 100644 --- a/test/t/unit/test_unit_compgen_filedir.py +++ b/test/t/unit/test_unit_compgen_filedir.py @@ -6,7 +6,7 @@ import pytest -from conftest import assert_bash_exec, assert_complete +from conftest import assert_bash_exec, assert_complete, bash_env_saved @pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=") @@ -59,7 +59,9 @@ def utf8_ctype(self, bash): return lc_ctype def test_1(self, bash): - assert_bash_exec(bash, "_comp_compgen_filedir >/dev/null") + with bash_env_saved(bash) as bash_env: + bash_env.write_variable("cur", "") + assert_bash_exec(bash, 'cur="" _comp_compgen_filedir >/dev/null') @pytest.mark.parametrize("funcname", "f f2".split()) def test_2(self, bash, functions, funcname): @@ -233,3 +235,47 @@ def test_26(self, bash, functions, funcname): def test_27(self, bash, functions, funcname, utf8_ctype): completion = assert_complete(bash, "%s aé/" % funcname, cwd="_filedir") assert completion == "g" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_1(self, bash, functions, funcname): + """Exclude . and .. when the completion is attempted for '.[TAB]'""" + completion = assert_complete(bash, r"%s ." % funcname, cwd="_filedir") + assert completion == [".dotfile1", ".dotfile2"] + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_2(self, bash, functions, funcname): + """Exclude . and .. when the completion is attempted for 'dir/.[TAB]'""" + completion = assert_complete(bash, r"%s _filedir/." % funcname) + assert completion == [".dotfile1", ".dotfile2"] + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_3(self, bash, functions, funcname): + """Include . when the completion is attempted for '..[TAB]'""" + completion = assert_complete(bash, r"%s .." % funcname, cwd="_filedir") + assert completion == "/" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_4(self, bash, functions, funcname): + """Include . when the completion is attempted for '..[TAB]'""" + completion = assert_complete(bash, r"%s _filedir/.." % funcname) + assert completion == "/" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_29_dotdot(self, bash, functions, funcname): + """Complete files starting with "..". + + These types of files are used by the go kubernetes atomic writer [0], + and presumably other types of systems, and we want to make sure they + will be completed correctly. + + [0] https://pkg.go.dev/k8s.io/kubernetes/pkg/volume/util#AtomicWriter.Write + """ + completion = assert_complete( + bash, r"%s .." % funcname, cwd="_filedir/dotdot/" + ) + assert completion == [ + "../", + "..2016_02_01_15_04_05.123456", + "..data", + "..folder/", + ] diff --git a/test/t/unit/test_unit_expand_glob.py b/test/t/unit/test_unit_expand_glob.py index 64d04a7d6fa..7c1bcde3f9c 100644 --- a/test/t/unit/test_unit_expand_glob.py +++ b/test/t/unit/test_unit_expand_glob.py @@ -22,7 +22,10 @@ def functions(self, bash): def test_match_all(self, bash, functions): output = assert_bash_exec(bash, "__tester '*'", want_output=True) - assert output.strip() == "" + assert ( + output.strip() + == "" + ) def test_match_pattern(self, bash, functions): output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)