Skip to content

fix(java,rsync,scp): handle quoted space in filepaths properly #1357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

akinomyoga
Copy link
Collaborator

@akinomyoga akinomyoga commented Apr 5, 2025

This includes several different fixes (whose changes overlap with each other). See the respective commit messages for the details.

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from 37a9900 to 4847f8d Compare April 5, 2025 08:50
@akinomyoga akinomyoga requested a review from Copilot April 5, 2025 08:51
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.

Files not reviewed (2)
  • completions/java: Language not supported
  • completions/ssh: Language not supported
Comments suppressed due to low confidence (1)

test/t/test_rsync.py:77

  • The cwd value 'scp' appears to be incorrect for an rsync test. It may be a typo and should likely be 'rsync'.
@pytest.mark.complete("rsync -na spaced\ ", cwd="scp")

@akinomyoga
Copy link
Collaborator Author

@Copilot[AI] The language of completions/java and completions/ssh are Bash.

  • The cwd value 'scp' appears to be incorrect for an rsync test. It may be a typo and should likely be 'rsync'.
@pytest.mark.complete("rsync -na spaced\ ", cwd="scp")

This is a good point. Since the rsync completion internally uses the xfunc function that scp provides, I reused the fixture directory of scp. This is intentional but might be confusing to the readers. I'll add a code comment.

@akinomyoga
Copy link
Collaborator Author

I tried the Copilot code review, which was released today, but I'm not sure how to respond to Copilot and get further feedback.

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from 4847f8d to c052268 Compare April 5, 2025 09:04
@akinomyoga akinomyoga requested a review from Copilot April 5, 2025 09:04
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.

Files not reviewed (2)
  • completions/java: Language not supported
  • completions/ssh: Language not supported
Comments suppressed due to low confidence (3)

test/t/test_rsync.py:77

  • The cwd parameter is set to 'scp' in a rsync test; confirm if this is intentional or if it should reference 'rsync' to avoid potential mis-routing of the test context.
@pytest.mark.complete("rsync -na spaced\ ", cwd="scp")

test/t/test_scp.py:191

  • [nitpick] The expected completion string 'ced\ \ conf' is hard to interpret; consider adding a clarifying comment or refactoring the test to indicate the intended transformation explicitly.
assert completion == "ced\ \ conf"

test/t/test_scp.py:195

  • [nitpick] Consider asserting an explicit expected value for completion in 'test_local_path_with_spaces_2' to ensure the test precisely validates the intended behavior.
assert completion

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from c052268 to 89b5600 Compare April 5, 2025 09:09
@akinomyoga
Copy link
Collaborator Author

test/t/test_rsync.py:77

  • The cwd parameter is set to 'scp' in a rsync test; confirm if this is intentional or if it should reference 'rsync' to avoid potential mis-routing of the test context.

I already added the clarifying code comment. So, Copilot does not take account of the code comment.

test/t/test_scp.py:191

  • [nitpick] The expected completion string 'ced\ \ conf' is hard to interpret;

This is a common pattern of the test suite in this project, where the text following the already input part (i.e., "spa" in the present case) is tested. It is impractical to add clarifying comments to all the occurrences of similar tests. Ideally, we can document the global structures and conventions of our test suite somewhere, but we currently don't have the documentation at all. To document it, too many things are not explained.

test/t/test_scp.py:195

  • [nitpick] Consider asserting an explicit expected value for completion in 'test_local_path_with_spaces_2' to ensure the test precisely validates the intended behavior.

Thanks, this is simply a mistake. I fixed it.

@akinomyoga akinomyoga requested a review from Copilot April 5, 2025 09:14
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.

Files not reviewed (2)
  • completions/java: Language not supported
  • completions/ssh: Language not supported
Comments suppressed due to low confidence (1)

test/t/test_rsync.py:77

  • The cwd parameter 'scp' is likely incorrect for an rsync test; it should probably be changed to 'rsync'.
@pytest.mark.complete("rsync -na spaced\ ", cwd="scp")

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch 2 times, most recently from 793b4d8 to b71b56e Compare April 5, 2025 09:45
@akinomyoga akinomyoga requested a review from Copilot April 5, 2025 09:52
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.

Files not reviewed (2)
  • completions/java: Language not supported
  • completions/ssh: Language not supported
Comments suppressed due to low confidence (2)

test/t/test_rsync.py:77

  • The cwd parameter is set to "scp" in a test within test_rsync.py; please verify if this is intentional or if it should be updated to reference the appropriate directory for rsync.
@pytest.mark.complete("rsync -na spaced\ ", cwd="scp")

test/t/test_scp.py:191

  • Double-check that the expected escaped output matches the intended handling of quoted spaces, ensuring consistency with the test case logic.
assert completion == "ced\ \ conf"

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from b71b56e to efb1c13 Compare April 5, 2025 12:22
@akinomyoga
Copy link
Collaborator Author

I added a fix for #1255 and related changes. See the commit messages for the details.

@akinomyoga akinomyoga requested a review from Copilot April 5, 2025 13:43
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 7 out of 13 changed files in this pull request and generated no comments.

Files not reviewed (6)
  • bash_completion: Language not supported
  • completions/java: Language not supported
  • completions/make: Language not supported
  • completions/mutt: Language not supported
  • completions/pkgutil: Language not supported
  • completions/ssh: Language not supported
Comments suppressed due to low confidence (1)

test/t/test_rsync.py:77

  • The 'cwd' parameter is set to 'scp' in an rsync test, which might be incorrect. Consider using 'rsync' as the cwd to better align with the intended test context.
@pytest.mark.complete("rsync -na spaced\\ ", cwd="scp")

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from 38855ad to 372d499 Compare April 5, 2025 13:45
@JohnVillalovos
Copy link

Thanks @akinomyoga I have tested this and now rsync and scp works with directories that have spaces in their names.

For example if I have the two directories: /home/dir with space and /home/dir with target the completion now works. Where before it would get stuck at /home/dir with

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from 372d499 to ebe6554 Compare April 6, 2025 01:13
@akinomyoga
Copy link
Collaborator Author

@JohnVillalovos Thanks for the confirmation!

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from ebe6554 to b456d8d Compare April 6, 2025 09:07
@akinomyoga akinomyoga requested a review from yedayak April 6, 2025 15:14
Copy link
Collaborator

@yedayak yedayak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand everything here, so take it with a grain of salt, but this looks fine to me. Left some comments. This definitely fixes the issues with local files completions, but I still seem to have some issues with remote file:

scp localhost:/home/yedaya/dev/bash-completion/test/fixtures/scp/local_path/backslash-a<TAB>\\\

This happens also with main, so not a blocker.

completions/ssh Outdated
# "compopt +o nospace" instead, but it would suffix a space to directory names
# unexpectedly.
#
# @param $1 dirs_only - If a non-empty value is specified, only directory names
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find other function that work like this in the codebase, maybe it should accept -d or something like that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I tried to implement it in commit 8ca4fa9, but it seems non-trivial how the caller should pass the flag to this function. In commit 8ca4fa9, I'm currently doing

        _comp_cmd_scp__escape_path ${_dirs_only:+'-d'} -- \
            "$_escape_replacement"

I thought we might assign _dirs_only=-d instead of _dirs_only=set, but even in that case, we cannot do "$_dirs_only" because it will produce an empty argument "" without -d. Then we still need to write ${_dirs_only:+"$_dirs_only"}.

Another option might be to make _dirs_only an array and use "${_dirs_only[@]}". However, because of Bash-4.2 nounset bug, we anyway need to write ${_dirs_only[@]+"${_dirs_only[@]}"}.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the current version 8ca4fa9 is fine.

# $1.
#
# @return 0 if $1 is a safe word and the expansion result contains one word at
# least, or 1 otherwise.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems reasonable to me, do you think this might cause breakage in external uses of the function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that is a good point.

After this change, the cases where it failed will continue to fail. Some of the cases where it succeeded and produced an empty result [e.g., REPLY=() for _comp_dequote '$empty_variable'] will start to fail after this change. Since the caller should have considered failing cases even with the current main branch, I expect that increasing another case of failing wouldn't cause a problem.

Rather, an empty REPLY=() would have caused a problem when the caller uses $REPLY after _comp_dequote succeeds.

some=""
if _comp_dequote '$some'; then # -> results in REPLY=()
  use "$REPLY" # <- This failed for "set -u" before the present change
fi

Another change is in the value of REPLY when _comp_dequote failed. Since REPLY was not supposed to be used when _comp_dequote failed (REPLY was actually empty, REPLY=()), I expect the change in REPLY in the failing case wouldn't cause a problem, though I admit that there might be a case that the user expects the empty REPLY in principle. I think this change would rather improve the compatibility in the case where the caller isn't careful enough to check the exit status:

_comp_dequote "$some2"
use "$REPLY" # <- This failed for "set -u", or if "set -u" is not set, an empty value was used.

# are allowed.
# @var[out] REPLY Result. The same as _comp_dequoet.
# @since 2.17
_comp_dequote_incomplete()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a unit test for this will be nice?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! I added tests in dcf662b.

@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch 2 times, most recently from b0566e5 to 8ca4fa9 Compare April 6, 2025 21:34
@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from a8d2912 to 93ebf89 Compare April 7, 2025 01:30
@akinomyoga akinomyoga requested a review from Copilot April 7, 2025 01:30
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 12 out of 19 changed files in this pull request and generated no comments.

Files not reviewed (7)
  • bash_completion: Language not supported
  • completions/java: Language not supported
  • completions/make: Language not supported
  • completions/mutt: Language not supported
  • completions/pkgutil: Language not supported
  • completions/ssh: Language not supported
  • test/t/unit/Makefile.am: Language not supported
Comments suppressed due to low confidence (4)

test/t/test_scp.py:77

  • [nitpick] Consider adding an inline comment explaining the rationale behind the expected output 'ig' to clarify its significance for future maintainers.
assert completion == "ig"

test/t/test_scp.py:81

  • [nitpick] Consider documenting why the expected completion 'n/' is produced in this test to improve clarity for future reviewers.
assert completion == "n/"

test/t/test_rsync.py:85

  • [nitpick] Add a comment to document the variations in expected output based on different rsync versions, so the reasoning behind the dual expectation is clear.
assert completion == r"\ conf" or completion == r"\\\ conf"

test/t/test_scp.py:196

  • [nitpick] Consider ensuring that the use of the pipe '|' character in the filename is intentional for testing special character handling and add an inline comment if needed.
files=["local_path_2-pipe|"],

akinomyoga added a commit to akinomyoga/bash-completion that referenced this pull request Apr 7, 2025
pytest.skip cancels the execution of the current test, so "return" and
subsequent codes arex unreachable.  We do not need to explicitly call
"return" in this context.

scop#1357 (comment)
@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from 9608d1e to 1fd977d Compare April 7, 2025 02:55
@akinomyoga
Copy link
Collaborator Author

test/t/test_rsync.py:85

  • [nitpick] Add a comment to document the variations in expected output based on different rsync versions, so the reasoning behind the dual expectation is clear.
assert completion == r"\ conf" or completion == r"\\\ conf"

35c1af4

test/t/test_scp.py:196

  • [nitpick] Consider ensuring that the use of the pipe '|' character in the filename is intentional for testing special character handling and add an inline comment if needed.
files=["local_path_2-pipe|"],

9608d1e

@akinomyoga akinomyoga requested a review from yedayak April 7, 2025 03:38
@akinomyoga
Copy link
Collaborator Author

@yedayak I added many changes and squashed them into the corresponding commits. The changes after your previous review can be checked on b456d8d..1fd977d.

akinomyoga added a commit to akinomyoga/bash-completion that referenced this pull request Apr 9, 2025
pytest.skip cancels the execution of the current test, so "return" and
subsequent codes arex unreachable.  We do not need to explicitly call
"return" in this context.

scop#1357 (comment)
@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from 1fd977d to b796eb0 Compare April 9, 2025 10:43
akinomyoga added a commit to akinomyoga/bash-completion that referenced this pull request Apr 9, 2025
pytest.skip cancels the execution of the current test, so "return" and
subsequent codes arex unreachable.  We do not need to explicitly call
"return" in this context.

scop#1357 (comment)
@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from b796eb0 to 621fc16 Compare April 9, 2025 10:50
@akinomyoga
Copy link
Collaborator Author

(rebased)

@scop
Copy link
Owner

scop commented Apr 20, 2025

Instead of lumping unrelated but conflicting/overlapping PR's together, especially as many as there are in this one, I'd suggest filing several and basing them off each other. This is a good way to optimize for the reviewers -- ones with multiple unrelated ones tend to grow large, and entail many context switches for the reviewer. For example, I could have easily reviewed and approved a few of these now and we could get them out of the way, but I cannot find time and attention to do it all now, and I'm not sure when I will. (If others do, please go ahead!)

So the first PR would have branch-1 based off main, the second's branch branch-2 based off branch-1 (and set as base in the PR), etc etc. I believe for this to work the PR's need to be branches in this repo and not a fork, so this is an option directly available for people with contributor access.

@akinomyoga
Copy link
Collaborator Author

Thank you. Yes..., I have to agree with you that this PR happened to become too large.

I usually tend to combine multiple changes in a reasonable size of a PR when I introduce many conflicting changes to avoid the complication of PR dependency and conflicts. If every tiny change is processed one by one as a PR, it would take a longer time until I forget the set of changes and their relationships. However, I admit that I failed to decide on the proper size of this PR. After some adjustments, the number of commits increased too much.

I'll split the PR.

@akinomyoga akinomyoga marked this pull request as draft April 20, 2025 10:25
akinomyoga added a commit to akinomyoga/bash-completion that referenced this pull request Apr 20, 2025
pytest.skip cancels the execution of the current test, so "return" and
subsequent codes arex unreachable.  We do not need to explicitly call
"return" in this context.

scop#1357 (comment)
akinomyoga and others added 9 commits April 20, 2025 19:49
* fix(scp): fix a bug that `-F<prefix>[TAB]` did not complete at all
* fix(rsync,ssh,sshfs): do not generate regular files *'\' as dirs
* fix(rsync,scp): remove file-type marks from "ls -F" properly
* refactor(ssh): remove outdated disable=SC2089,SC2090
* refactor(scp): use "_comp_compgen_split -P" to add prefix
* fix(_comp_command_offset): work around nounset
* refactor(scp): rename local var "_dirs{ => _}only" for consistency
* test(cancel,ls,man): remove redundant "return"
Filenames containing a special character are not properly completed
[1].  These completions generate filenames by pathname expanaion using
$cur.  However, $cur contains the word on the command line including
quotaing, such as cur='file\ with\ space.txt' and cur='"a b c.txt"'.
This patch obtains the value of "cur" using _comp_dequote.

[1] scop#1232

This patch also fixes a similar case in completions/java.

Co-authored-by: Yedaya Katsman <[email protected]>
It is more useful to set the literal value of $1 to REPLY even when $1
is an unsafe word.  When the caller wants to use the result only when
REPLY is a safe string and suceceds without failglob, the caller can
reference the exit status $?.  When the caller wants to use any
string, it is more useful if REPLY contains a fallback value.

This patch changes the behavior so that REPLY is set to be the literal
value of $1 even when an unsafe value is specified to $1.  We also
change the exit status to be 0 only when at least one word is
generated by a safe string.
@akinomyoga akinomyoga force-pushed the _comp_xfunc_scp_compgen_local_files branch from 621fc16 to c8de159 Compare April 20, 2025 10:50
@akinomyoga
Copy link
Collaborator Author

I submitted PR #1371.

I believe for this to work the PR's need to be branches in this repo and not a fork, so this is an option directly available for people with contributor access.

Sorry, I missed this part, though I'm afraid that I'm possibly still missing the intent. Is the idea that another collaborator can split the PR (instead of the original submitter)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants