Skip to contentSkip to conversation

completion: escape metacharacters when completing paths

PATCH In review Chayoung You 2 participants 6 messages Dec 26, 2018 – Jan 1, 2019

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 completion: escape metacharacters when completing paths

The following is the description of -Q flag of zsh compadd [1]:

This flag instructs the completion code not to quote any metacharacters in the words when inserting them into the command line.

Let's say there is a file named 'foo bar.txt' in repository, but it's not yet added to the repository. Then the following command triggers a completion:

git add fo<Tab>
git add 'fo<Tab>
git add "fo<Tab>

The completion results in bash:

git add foo\ bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

While them in zsh:

git add foo bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

The main cause of this behavior is __gitcomp_file_direct(). The both implementions of bash and zsh are called with an argument 'foo bar.txt', but only bash adds a backslash before a space on command line.

[1]: http://zsh.sourceforge.net/Doc/Release/Completion-Widgets.html

---
 contrib/completion/git-completion.bash | 4 ++--
 contrib/completion/git-completion.zsh  | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
contrib/completion/git-completion.bash +2 -2
index 9e8ec95c3..816ee3280 100644
@@ -2993,7 +2993,7 @@ if [[ -n ${ZSH_VERSION-} ]] &&
 
 		local IFS=$'\n'
 		compset -P '*[=:]'
-		compadd -Q -f -- ${=1} && _ret=0
+		compadd -f -- ${=1} && _ret=0
 	}
 
 	__gitcomp_file ()
@@ -3002,7 +3002,7 @@ if [[ -n ${ZSH_VERSION-} ]] &&
 
 		local IFS=$'\n'
 		compset -P '*[=:]'
-		compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+		compadd -p "${2-}" -f -- ${=1} && _ret=0
 	}
 
 	_git ()
contrib/completion/git-completion.zsh +2 -2
index 049d6b80f..886bf95d1 100644
@@ -99,7 +99,7 @@ __gitcomp_file_direct ()
 
 	local IFS=$'\n'
 	compset -P '*[=:]'
-	compadd -Q -f -- ${=1} && _ret=0
+	compadd -f -- ${=1} && _ret=0
 }
 
 __gitcomp_file ()
@@ -108,7 +108,7 @@ __gitcomp_file ()
 
 	local IFS=$'\n'
 	compset -P '*[=:]'
-	compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+	compadd -p "${2-}" -f -- ${=1} && _ret=0
 }
 
 __git_zsh_bash_func ()
Signature · 1 line
2.20.1
#1 lore raw
Quoted 1 line · Chayoung You

Subject: Re: [PATCH] completion: escape metacharacters when completing paths

I am not a zsh user, but from the patch text I am getting the impression that this patch is only about zsh. If so, please help readers of "git shortlog" by saying so in the title.

Subject: zsh: complete unquoted paths with spaces correctly

or something like that, perhaps?

Quoted 14 lines

Let's say there is a file named 'foo bar.txt' in repository, but it's not yet added to the repository. Then the following command triggers a completion:

git add fo<Tab>
git add 'fo<Tab>
git add "fo<Tab>

The completion results in bash:

git add foo\ bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

Quoted 5 lines

While them in zsh:

git add foo bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

You leave it unsaid what you think is wrong, and what you think should be the right output, before saying "cause of this is...".

I do not think it is given that "git add fo<TAB>" should be completed to "git add foo\ bar.txt". It would also be OK if the completion produced "git add 'foo bar.txt'", for example. So what your ideal output is is rather important, and by doing that you end up with saying what problem you have with the current output is.

So, say something here, perhaps like...

The first one, where the pathname is not enclosed in quotes, should escape the SP with a backslash, just like bash completion does.

#2 lore raw

PATCH v2 1/1 zsh: complete unquoted paths with spaces correctly

The following is the description of -Q flag of zsh compadd [1]:

This flag instructs the completion code not to quote any metacharacters in the words when inserting them into the command line.

Let's say there is a file named 'foo bar.txt' in repository, but it's not yet added to the repository. Then the following command triggers a completion:

git add fo<Tab>
git add 'fo<Tab>
git add "fo<Tab>

The completion results in bash:

git add foo\ bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

While them in zsh:

git add foo bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

The first one, where the pathname is not enclosed in quotes, should escape the space with a backslash, just like bash completion does. Otherwise, this leads git to think there are two files; foo, and bar.txt.

The main cause of this behavior is __gitcomp_file_direct(). The both implementions of bash and zsh are called with an argument 'foo bar.txt', but only bash adds a backslash before a space on command line.

[1]: http://zsh.sourceforge.net/Doc/Release/Completion-Widgets.html

---
 contrib/completion/git-completion.bash | 4 ++--
 contrib/completion/git-completion.zsh  | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
contrib/completion/git-completion.bash +2 -2
index 9e8ec95c3..816ee3280 100644
@@ -2993,7 +2993,7 @@ if [[ -n ${ZSH_VERSION-} ]] &&
 
 		local IFS=$'\n'
 		compset -P '*[=:]'
-		compadd -Q -f -- ${=1} && _ret=0
+		compadd -f -- ${=1} && _ret=0
 	}
 
 	__gitcomp_file ()
@@ -3002,7 +3002,7 @@ if [[ -n ${ZSH_VERSION-} ]] &&
 
 		local IFS=$'\n'
 		compset -P '*[=:]'
-		compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+		compadd -p "${2-}" -f -- ${=1} && _ret=0
 	}
 
 	_git ()
contrib/completion/git-completion.zsh +2 -2
index 049d6b80f..886bf95d1 100644
@@ -99,7 +99,7 @@ __gitcomp_file_direct ()
 
 	local IFS=$'\n'
 	compset -P '*[=:]'
-	compadd -Q -f -- ${=1} && _ret=0
+	compadd -f -- ${=1} && _ret=0
 }
 
 __gitcomp_file ()
@@ -108,7 +108,7 @@ __gitcomp_file ()
 
 	local IFS=$'\n'
 	compset -P '*[=:]'
-	compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+	compadd -p "${2-}" -f -- ${=1} && _ret=0
 }
 
 __git_zsh_bash_func ()
Signature · 1 line
2.20.1
#3 lore raw

v3 cover completion: handle paths with spaces correctly

I found more issues with completion for git show ref:path, so here I add one more patch.

Chayoung You (2):
zsh: complete unquoted paths with spaces correctly
completion: treat results of git ls-tree as file paths

 contrib/completion/git-completion.bash | 35 +++++++++++---------------
 contrib/completion/git-completion.zsh  |  4 +--
 t/t9902-completion.sh                  | 10 ++++----
 3 files changed, 21 insertions(+), 28 deletions(-)
Signature · 1 line
2.17.1
#4 lore raw

PATCH v3 1/2 zsh: complete unquoted paths with spaces correctly

The following is the description of -Q flag of zsh compadd [1]:

This flag instructs the completion code not to quote any metacharacters in the words when inserting them into the command line.

Let's say there is a file named 'foo bar.txt' in repository, but it's not yet added to the repository. Then the following command triggers a completion:

git add fo<Tab>
git add 'fo<Tab>
git add "fo<Tab>

The completion results in bash:

git add foo\ bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

While them in zsh:

git add foo bar.txt
git add 'foo bar.txt'
git add "foo bar.txt"

The first one, where the pathname is not enclosed in quotes, should escape the space with a backslash, just like bash completion does. Otherwise, this leads git to think there are two files; foo, and bar.txt.

The main cause of this behavior is __gitcomp_file_direct(). The both implementions of bash and zsh are called with an argument 'foo bar.txt', but only bash adds a backslash before a space on command line.

[1]: http://zsh.sourceforge.net/Doc/Release/Completion-Widgets.html

---
 contrib/completion/git-completion.bash | 4 ++--
 contrib/completion/git-completion.zsh  | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)
contrib/completion/git-completion.bash +2 -2
index 9e8ec95c3..816ee3280 100644
@@ -2993,7 +2993,7 @@ if [[ -n ${ZSH_VERSION-} ]] &&
 
 		local IFS=$'\n'
 		compset -P '*[=:]'
-		compadd -Q -f -- ${=1} && _ret=0
+		compadd -f -- ${=1} && _ret=0
 	}
 
 	__gitcomp_file ()
@@ -3002,7 +3002,7 @@ if [[ -n ${ZSH_VERSION-} ]] &&
 
 		local IFS=$'\n'
 		compset -P '*[=:]'
-		compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+		compadd -p "${2-}" -f -- ${=1} && _ret=0
 	}
 
 	_git ()
contrib/completion/git-completion.zsh +2 -2
index 049d6b80f..886bf95d1 100644
@@ -99,7 +99,7 @@ __gitcomp_file_direct ()
 
 	local IFS=$'\n'
 	compset -P '*[=:]'
-	compadd -Q -f -- ${=1} && _ret=0
+	compadd -f -- ${=1} && _ret=0
 }
 
 __gitcomp_file ()
@@ -108,7 +108,7 @@ __gitcomp_file ()
 
 	local IFS=$'\n'
 	compset -P '*[=:]'
-	compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
+	compadd -p "${2-}" -f -- ${=1} && _ret=0
 }
 
 __git_zsh_bash_func ()
Signature · 1 line
2.17.1
#5 lore raw

PATCH v3 2/2 completion: treat results of git ls-tree as file paths

In reply to Chayoung You

Let's say there are files named 'foo bar.txt', and 'abc def/test.txt' in repository. When following commands trigger a completion:

git show HEAD:fo<Tab>
git show HEAD:ab<Tab>

The completion results in bash/zsh:

git show HEAD:foo bar.txt
git show HEAD:abc def/

Where the both of them have an unescaped space in paths, so they'll be misread by git. All entries of git ls-tree either a filename or a directory, so __gitcomp_file() is proper rather than __gitcomp_nl().

Note the commit f12785a3, which handles quoted paths properly. Like this case, we should dequote $cur_ for ?*:* case. For example, let's say there is untracked directory 'abc deg', then trigger a completion:

git show HEAD:abc\ de<Tab>
git show HEAD:'abc de<Tab>
git show HEAD:"abc de<Tab>

should uniquely complete 'abc def', but bash completes 'abc def' and 'abc deg' instead. In zsh, triggering a completion:

git show HEAD:abc\ def/<Tab>

should complete 'test.txt', but nothing comes. The both problems will be resolved by dequoting paths.

__git_complete_revlist_file() passes arguments to __gitcomp_nl() where the first one is a list something like:

abc def/Z
foo bar.txt Z

where Z is the mark of the EOL.

- The trailing space of blob in __git ls-tree | sed. It makes the completion results become:

git show HEAD:foo\ bar.txt\ <CURSOR>

So git will try to find a file named 'foo bar.txt ' instead.

- The trailing slash of tree in __git ls-tree | sed. It makes the completion results on zsh become:

git show HEAD:abc\ def/ <CURSOR>

So that the last space on command like should be removed on zsh to complete filenames under 'abc def/'.

---
 contrib/completion/git-completion.bash | 31 ++++++++++----------------
 t/t9902-completion.sh                  | 10 ++++-----
 2 files changed, 17 insertions(+), 24 deletions(-)
contrib/completion/git-completion.bash +12 -19
index 816ee3280..26ea310fd 100644
@@ -855,7 +855,7 @@ __git_compute_merge_strategies ()
 
 __git_complete_revlist_file ()
 {
-	local pfx ls ref cur_="$cur"
+	local dequoted_word pfx ls ref cur_="$cur"
 	case "$cur_" in
 	*..?*:*)
 		return
@@ -863,14 +863,18 @@ __git_complete_revlist_file ()
 	?*:*)
 		ref="${cur_%%:*}"
 		cur_="${cur_#*:}"
-		case "$cur_" in
+
+		__git_dequote "$cur_"
+
+		case "$dequoted_word" in
 		?*/*)
-			pfx="${cur_%/*}"
-			cur_="${cur_##*/}"
+			pfx="${dequoted_word%/*}"
+			cur_="${dequoted_word##*/}"
 			ls="$ref:$pfx"
 			pfx="$pfx/"
 			;;
 		*)
+			cur_="$dequoted_word"
 			ls="$ref"
 			;;
 		esac
@@ -880,21 +884,10 @@ __git_complete_revlist_file ()
 		*)   pfx="$ref:$pfx" ;;
 		esac
 
-		__gitcomp_nl "$(__git ls-tree "$ls" \
-				| sed '/^100... blob /{
-				           s,^.*	,,
-				           s,$, ,
-				       }
-				       /^120000 blob /{
-				           s,^.*	,,
-				           s,$, ,
-				       }
-				       /^040000 tree /{
-				           s,^.*	,,
-				           s,$,/,
-				       }
-				       s/^.*	//')" \
-			"$pfx" "$cur_" ""
+		__gitcomp_file "$(__git ls-tree "$ls" \
+				| sed 's/^.*	//
+				       s/$//')" \
+			"$pfx" "$cur_"
 		;;
 	*...*)
 		pfx="${cur_%...*}..."
t/t9902-completion.sh +5 -5
index 137fdc9bd..94157e587 100755
@@ -1515,8 +1515,8 @@ test_expect_success 'show completes all refs' '
 
 test_expect_success '<ref>: completes paths' '
 	test_completion "git show mytag:f" <<-\EOF
-	file1 Z
-	file2 Z
+	file1Z
+	file2Z
 	EOF
 '
 
@@ -1525,7 +1525,7 @@ test_expect_success 'complete tree filename with spaces' '
 	git add "name with spaces" &&
 	git commit -m spaces &&
 	test_completion "git show HEAD:nam" <<-\EOF
-	name with spaces Z
+	name with spacesZ
 	EOF
 '
 
@@ -1534,8 +1534,8 @@ test_expect_success 'complete tree filename with metacharacters' '
 	git add "name with \${meta}" &&
 	git commit -m meta &&
 	test_completion "git show HEAD:nam" <<-\EOF
-	name with ${meta} Z
-	name with spaces Z
+	name with ${meta}Z
+	name with spacesZ
 	EOF
 '
 
Signature · 1 line
2.17.1